From 8bab209c71fd481a511f8802780472578194a91c Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:04:21 -0300 Subject: [PATCH 01/30] qRwa --- .DS_Store | Bin 0 -> 6148 bytes core | 1 + qubic-cli | 1 + 3 files changed, 2 insertions(+) create mode 100644 .DS_Store create mode 160000 core create mode 160000 qubic-cli diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3e1039f47940c5b3641f4495ca64a3cbd25d3627 GIT binary patch literal 6148 zcmeHKO-sW-5S?wSO(;SS3VIL%UaR&)TEt7J_2AWr9#m@677fO1Z5w+ih1~VOc=nI@ zcXVcVE7U69q{s}+zWtcD$-ac$9RLvZxKjei1Av82n6k0>#%P>;&Qhi)gD7Z@<50H! zQ2N-W3E3830s3|sXoG}4biaJR7aZGpSEd$X!vnY Date: Mon, 23 Feb 2026 18:55:06 -0300 Subject: [PATCH 02/30] qrwa-test --- .env | 3 + cmake/CompilerSetup.cmake | 344 --- src/contracts/qRWA.h | 153 +- test/CMakeLists.txt | 70 - test/README.md | 113 - test/assets.cpp | 757 ------- test/common_def.cpp | 27 - test/contract_ccf.cpp | 1215 ---------- test/contract_core.cpp | 167 -- test/contract_gqmprop.cpp | 537 ----- test/contract_msvault.cpp | 1258 ----------- test/contract_nostromo.cpp | 1692 -------------- test/contract_qbay.cpp | 1174 ---------- test/contract_qbond.cpp | 444 ---- test/contract_qduel.cpp | 1328 ----------- test/contract_qearn.cpp | 983 -------- test/contract_qip.cpp | 1444 ------------ test/contract_qraffle.cpp | 1576 ------------- test/contract_qrp.cpp | 263 --- test/contract_qrwa.cpp | 1852 --------------- test/contract_qswap.cpp | 810 ------- test/contract_qtf.cpp | 3115 -------------------------- test/contract_qutil.cpp | 1751 --------------- test/contract_qvault.cpp | 874 -------- test/contract_qx.cpp | 253 --- test/contract_rl.cpp | 1283 ----------- test/contract_testex.cpp | 2173 ------------------ test/contract_testing.h | 228 -- test/custom_mining.cpp | 279 --- test/data/custom_revenue.eoe | Bin 43264 -> 0 bytes test/data/samples_20240815.csv | 1025 --------- test/data/scores_addition.csv | 1025 --------- test/data/scores_hyperidentity.csv | 1025 --------- test/execution_fees.cpp | 375 ---- test/file_io.cpp | 684 ------ test/fourq.cpp | 264 --- test/kangaroo_twelve.cpp | 92 - test/logging_test.h | 42 - test/m256.cpp | 274 --- test/math_lib.cpp | 88 - test/network_messages.cpp | 68 - test/oracle_engine.cpp | 1306 ----------- test/oracle_testing.h | 84 - test/packages.config | 4 - test/pending_txs_pool.cpp | 544 ----- test/platform.cpp | 501 ----- test/qpi.cpp | 2313 ------------------- test/qpi_collection.cpp | 1716 -------------- test/qpi_date_time.cpp | 589 ----- test/qpi_hash_map.cpp | 1009 --------- test/quorum_value.cpp | 140 -- test/revenue.cpp | 235 -- test/score.cpp | 910 -------- test/score_addition_reference.h | 869 ------- test/score_cache.cpp | 201 -- test/score_common_reference.h | 108 - test/score_hyperidentity_reference.h | 785 ------- test/score_params.h | 65 - test/score_reference.h | 60 - test/sorting.cpp | 118 - test/spectrum.cpp | 409 ---- test/stable_computor_index.cpp | 214 -- test/stdlib_impl.cpp | 59 - test/test.vcxproj | 204 -- test/test.vcxproj.filters | 81 - test/test.vcxproj.user | 12 - test/test_util.h | 34 - test/tick_storage.cpp | 209 -- test/time.cpp | 313 --- test/tx_status_request.cpp | 224 -- test/uint128.cpp | 199 -- test/utils.h | 103 - test/virtual_memory.cpp | 239 -- test/vote_counter.cpp | 172 -- 74 files changed, 155 insertions(+), 45002 deletions(-) create mode 100644 .env delete mode 100644 cmake/CompilerSetup.cmake delete mode 100644 test/CMakeLists.txt delete mode 100644 test/README.md delete mode 100644 test/assets.cpp delete mode 100644 test/common_def.cpp delete mode 100644 test/contract_ccf.cpp delete mode 100644 test/contract_core.cpp delete mode 100644 test/contract_gqmprop.cpp delete mode 100644 test/contract_msvault.cpp delete mode 100644 test/contract_nostromo.cpp delete mode 100644 test/contract_qbay.cpp delete mode 100644 test/contract_qbond.cpp delete mode 100644 test/contract_qduel.cpp delete mode 100644 test/contract_qearn.cpp delete mode 100644 test/contract_qip.cpp delete mode 100644 test/contract_qraffle.cpp delete mode 100644 test/contract_qrp.cpp delete mode 100644 test/contract_qrwa.cpp delete mode 100644 test/contract_qswap.cpp delete mode 100644 test/contract_qtf.cpp delete mode 100644 test/contract_qutil.cpp delete mode 100644 test/contract_qvault.cpp delete mode 100644 test/contract_qx.cpp delete mode 100644 test/contract_rl.cpp delete mode 100644 test/contract_testex.cpp delete mode 100644 test/contract_testing.h delete mode 100644 test/custom_mining.cpp delete mode 100644 test/data/custom_revenue.eoe delete mode 100644 test/data/samples_20240815.csv delete mode 100644 test/data/scores_addition.csv delete mode 100644 test/data/scores_hyperidentity.csv delete mode 100644 test/execution_fees.cpp delete mode 100644 test/file_io.cpp delete mode 100644 test/fourq.cpp delete mode 100644 test/kangaroo_twelve.cpp delete mode 100644 test/logging_test.h delete mode 100644 test/m256.cpp delete mode 100644 test/math_lib.cpp delete mode 100644 test/network_messages.cpp delete mode 100644 test/oracle_engine.cpp delete mode 100644 test/oracle_testing.h delete mode 100644 test/packages.config delete mode 100644 test/pending_txs_pool.cpp delete mode 100644 test/platform.cpp delete mode 100644 test/qpi.cpp delete mode 100644 test/qpi_collection.cpp delete mode 100644 test/qpi_date_time.cpp delete mode 100644 test/qpi_hash_map.cpp delete mode 100644 test/quorum_value.cpp delete mode 100644 test/revenue.cpp delete mode 100644 test/score.cpp delete mode 100644 test/score_addition_reference.h delete mode 100644 test/score_cache.cpp delete mode 100644 test/score_common_reference.h delete mode 100644 test/score_hyperidentity_reference.h delete mode 100644 test/score_params.h delete mode 100644 test/score_reference.h delete mode 100644 test/sorting.cpp delete mode 100644 test/spectrum.cpp delete mode 100644 test/stable_computor_index.cpp delete mode 100644 test/stdlib_impl.cpp delete mode 100644 test/test.vcxproj delete mode 100644 test/test.vcxproj.filters delete mode 100644 test/test.vcxproj.user delete mode 100644 test/test_util.h delete mode 100644 test/tick_storage.cpp delete mode 100644 test/time.cpp delete mode 100644 test/tx_status_request.cpp delete mode 100644 test/uint128.cpp delete mode 100644 test/utils.h delete mode 100644 test/virtual_memory.cpp delete mode 100644 test/vote_counter.cpp diff --git a/.env b/.env new file mode 100644 index 000000000..627ab55b6 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +SSH server= 95.217.151.140 +Username= root +Password=A5S77mJ90Oxp \ No newline at end of file diff --git a/cmake/CompilerSetup.cmake b/cmake/CompilerSetup.cmake deleted file mode 100644 index 4ed5386c3..000000000 --- a/cmake/CompilerSetup.cmake +++ /dev/null @@ -1,344 +0,0 @@ -# CompilerDetection.cmake -# Central location for compiler and system detection logic - -# --- Platform and Compiler Detection --- -message(STATUS "Detecting compiler and platform...") -message(STATUS "Compiler ID: ${CMAKE_CXX_COMPILER_ID}") -message(STATUS "Compiler Path: ${CMAKE_CXX_COMPILER}") -message(STATUS "System Name: ${CMAKE_SYSTEM_NAME}") - -# Set platform detection variables -set(IS_WINDOWS FALSE CACHE INTERNAL "Windows platform detected") -set(IS_LINUX FALSE CACHE INTERNAL "Linux platform detected") -set(IS_MSVC FALSE CACHE INTERNAL "MSVC compiler detected") -set(IS_CLANG FALSE CACHE INTERNAL "Clang compiler detected") -set(IS_GCC FALSE CACHE INTERNAL "GCC compiler detected") -set(ASM_LANG "" CACHE INTERNAL "Assembly language to use") - -# Detect Windows platform -if(CMAKE_SYSTEM_NAME MATCHES "Windows") - set(IS_WINDOWS TRUE CACHE INTERNAL "Windows platform detected" FORCE) - message(STATUS "Windows platform detected") -endif() - -# Detect Linux platform -if(CMAKE_SYSTEM_NAME MATCHES "Linux") - set(IS_LINUX TRUE CACHE INTERNAL "Linux platform detected" FORCE) - message(STATUS "Linux platform detected") -endif() - -# Detect MSVC compiler -if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - set(IS_MSVC TRUE CACHE INTERNAL "MSVC compiler detected" FORCE) - message(STATUS "MSVC compiler detected") - - # Set assembly language for MSVC - enable_language(ASM_MASM) - set(ASM_LANG ASM_MASM CACHE INTERNAL "Assembly language to use" FORCE) - message(STATUS "Using MASM for assembly") -endif() - -# Detect Clang compiler -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(IS_CLANG TRUE CACHE INTERNAL "Clang compiler detected" FORCE) - message(STATUS "Clang compiler detected") - - # For Clang on Linux, we'll use NASM - if(IS_LINUX) - set(ASM_LANG ASM_NASM CACHE INTERNAL "Assembly language to use" FORCE) - find_program(NASM_EXECUTABLE nasm REQUIRED) - message(STATUS "Using NASM for assembly via custom command") - endif() -endif() - -# Detect GCC compiler -if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set(IS_GCC TRUE CACHE INTERNAL "GCC compiler detected" FORCE) - message(STATUS "GCC compiler detected") - - # For GCC on Linux, we'll use NASM - if(IS_LINUX) - set(ASM_LANG ASM_NASM CACHE INTERNAL "Assembly language to use" FORCE) - find_program(NASM_EXECUTABLE nasm REQUIRED) - message(STATUS "Using NASM for assembly via custom command") - endif() -endif() - -# --- Clear all default flags to use only specified ones --- -set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "Available build types" FORCE) - -message(STATUS "CLEARING CMAKE DEFAULT FLAGS") -# Set all default flag variables to an empty string to take full control. -set(CMAKE_C_FLAGS "" CACHE INTERNAL "") -set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "") -set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERNAL "") - -set(CMAKE_C_FLAGS_DEBUG "" CACHE INTERNAL "") -set(CMAKE_CXX_FLAGS_DEBUG "" CACHE INTERNAL "") -set(CMAKE_EXE_LINKER_FLAGS_DEBUG "" CACHE INTERNAL "") - -set(CMAKE_C_FLAGS_RELEASE "" CACHE INTERNAL "") -set(CMAKE_CXX_FLAGS_RELEASE "" CACHE INTERNAL "") -set(CMAKE_EXE_LINKER_FLAGS_RELEASE "" CACHE INTERNAL "") - -# set(CMAKE_C_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "") -# set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "") -# set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "") - -# set(CMAKE_C_FLAGS_MINSIZEREL "" CACHE INTERNAL "") -# set(CMAKE_CXX_FLAGS_MINSIZEREL "" CACHE INTERNAL "") -# set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "" CACHE INTERNAL "") - -# --- Common Compiler Flags --- - -# Define common flags for all compilers -set(COMMON_C_FLAGS "" CACHE INTERNAL "Common C compiler flags") -set(COMMON_CXX_FLAGS "" CACHE INTERNAL "Common C++ compiler flags") -set(COMMON_DEBUG_FLAGS "" CACHE INTERNAL "Common debug compiler flags") -set(COMMON_RELEASE_FLAGS "" CACHE INTERNAL "Common release compiler flags") -set(COMMON_LINK_FLAGS "" CACHE INTERNAL "Common linker flags") - -# Define EFI-specific flags -set(EFI_C_FLAGS "" CACHE INTERNAL "EFI-specific C compiler flags") -set(EFI_CXX_FLAGS "" CACHE INTERNAL "EFI-specific C++ compiler flags") - -# Define OS-specific flags -set(OS_C_FLAGS "" CACHE INTERNAL "OS-specific C compiler flags") -set(OS_CXX_FLAGS "" CACHE INTERNAL "OS-specific C++ compiler flags") - -# Define platform-specific flags -if(IS_MSVC) - # MSVC-specific common flags - set(COMMON_C_FLAGS "/W4 /GF" CACHE INTERNAL "Common C compiler flags" FORCE) - set(COMMON_CXX_FLAGS "${COMMON_C_FLAGS}" CACHE INTERNAL "Common C++ compiler flags" FORCE) - set(COMMON_DEBUG_FLAGS "" CACHE INTERNAL "Common debug compiler flags" FORCE) - set(COMMON_RELEASE_FLAGS "/GL- /Gw /Oi /Ob2 /O2 /Oy" CACHE INTERNAL "Common release compiler flags" FORCE) - set(COMMON_LINK_FLAGS "" CACHE INTERNAL "Common linker flags" FORCE) - - # MSVC-specific EFI flags - # /W3 # WarningLevel: Level3 - # /permissive- # ConformanceMode: true - # /Zc:wchar_t- # TreatWChar_tAsBuiltInType: false - # /EHs-c- # ExceptionHandling: false - # /GS- # BufferSecurityCheck: false / SDLCheck: false - # /GF # StringPooling: true - # /Gy- # FunctionLevelLinking: false - # /Oi # IntrinsicFunctions: true - # /O2 # Optimization: MaxSpeed - # /Ot # FavorSizeOrSpeed: Speed - # /Ob2 # InlineFunctionExpansion: AnySuitable - # /Oy # OmitFramePointers: true - # /GT # EnableFiberSafeOptimizations: true - # /fp:except- # FloatingPointExceptions: false - # /guard:cf- # ControlFlowGuard: false - # /Gs1638400 # AdditionalOptions: /Gs... - # /Zl # OmitDefaultLibName: true - # /FAcs # AssemblerOutput: All (DISABLED) - # /Fa${CMAKE_INTDIR}/Qubic.asm # AssemblerListingLocation (DISABLED) - set(EFI_C_FLAGS "/W3 /permissive- /Zc:wchar_t- /EHs-c- /GS- /GF /Gy- /Oi /O2 /Ot /Ob2 /Oy /GT /fp:except- /guard:cf- /Gs1638400 /Zl" CACHE INTERNAL "EFI-specific C compiler flags" FORCE) - set(EFI_CXX_FLAGS "${EFI_C_FLAGS}" CACHE INTERNAL "EFI-specific C++ compiler flags" FORCE) - - # /SUBSYSTEM:EFI_APPLICATION # SubSystem: EFI Application - # /ENTRY:efi_main # EntryPointSymbol: efi_main - # /NODEFAULTLIB # IgnoreAllDefaultLibraries: true - # /DEBUG:NONE # GenerateDebugInformation: false - # /OPT:REF # EnableCOMDATFolding: true - # /OPT:ICF # OptimizeReferences: true - # /DYNAMICBASE:NO # RandomizedBaseAddress: false - # /NXCOMPAT:NO # DataExecutionPrevention: false - # /MANIFEST:NO # GenerateManifest: false - # /STACK:131072 # StackReserveSize / StackCommitSize - set(EFI_LINK_FLAGS "/SUBSYSTEM:EFI_APPLICATION /ENTRY:efi_main /NODEFAULTLIB /DEBUG:NONE /OPT:REF /OPT:ICF /MANIFEST:NO /DYNAMICBASE:NO /NXCOMPAT:NO /STACK:131072" CACHE INTERNAL "EFI-specific linker flags" FORCE) - - # MSVC-specific OS flags - set(OS_C_FLAGS "" CACHE INTERNAL "OS-specific C compiler flags" FORCE) - set(OS_CXX_FLAGS "" CACHE INTERNAL "OS-specific C++ compiler flags" FORCE) - -elseif(IS_CLANG OR IS_GCC) - # Clang/GCC-specific common flags - set(COMMON_C_FLAGS "-Wall -Wextra -fshort-wchar" CACHE INTERNAL "Common C compiler flags" FORCE) - set(COMMON_CXX_FLAGS "${COMMON_C_FLAGS}" CACHE INTERNAL "Common C++ compiler flags" FORCE) - set(COMMON_DEBUG_FLAGS "-g" CACHE INTERNAL "Common debug compiler flags" FORCE) - set(COMMON_RELEASE_FLAGS "-O2 -fomit-frame-pointer -fno-lto" CACHE INTERNAL "Common release compiler flags" FORCE) - set(COMMON_LINK_FLAGS "" CACHE INTERNAL "Common linker flags" FORCE) - - # Clang/GCC-specific EFI flags - set(EFI_C_FLAGS "-ffreestanding -mno-red-zone -fno-stack-protector -fno-strict-aliasing -fno-builtin" CACHE INTERNAL "EFI-specific C compiler flags" FORCE) - set(EFI_CXX_FLAGS "${EFI_C_FLAGS} -fno-rtti -fno-exceptions" CACHE INTERNAL "EFI-specific C++ compiler flags" FORCE) - - # Clang/GCC-specific OS flags - set(OS_C_FLAGS "-fno-stack-protector" CACHE INTERNAL "OS-specific C compiler flags" FORCE) - set(OS_CXX_FLAGS "${OS_C_FLAGS}" CACHE INTERNAL "OS-specific C++ compiler flags" FORCE) -endif() - -# --- CPU Instruction Set Flags --- - -# Allow the user to enable AVX-512 -option(ENABLE_AVX512 "Enable AVX-512 instructions" ON) - -# Define CPU instruction set flags -set(CPU_INSTRUCTION_FLAGS "" CACHE INTERNAL "CPU instruction set flags") - -if(IS_MSVC) - if(ENABLE_AVX512) - set(CPU_INSTRUCTION_FLAGS "/arch:AVX512" CACHE INTERNAL "CPU instruction set flags" FORCE) - message(STATUS "MSVC: Enabling AVX-512 (/arch:AVX512)") - else() - set(CPU_INSTRUCTION_FLAGS "/arch:AVX2" CACHE INTERNAL "CPU instruction set flags" FORCE) - message(STATUS "MSVC: Enabling AVX2 (/arch:AVX2)") - message(STATUS "AVX-512 is disabled. If you would like to activate make sure you set ENABLE_AVX512 to ON while running cmake.") - endif() -elseif(IS_CLANG OR IS_GCC) - if(ENABLE_AVX512) - set(CPU_INSTRUCTION_FLAGS "-mavx -mavx2 -mavx512f -mavx512cd -mavx512vl -mavx512bw -mavx512dq" CACHE INTERNAL "CPU instruction set flags" FORCE) - message(STATUS "GCC/Clang: Enabling AVX-512 and AVX/AVX2") - else() - set(CPU_INSTRUCTION_FLAGS "-mavx -mavx2" CACHE INTERNAL "CPU instruction set flags" FORCE) - message(STATUS "GCC/Clang: Enabling AVX/AVX2") - message(STATUS "AVX-512 is disabled. If you would like to activate make sure you set ENABLE_AVX512 to ON while running cmake.") - endif() -endif() - -# --- Test-specific flags --- -set(TEST_SPECIFIC_FLAGS "" CACHE INTERNAL "Test-specific compiler flags") - -if(IS_MSVC) - set(TEST_SPECIFIC_FLAGS "/WX /EHsc" CACHE INTERNAL "Test-specific compiler flags" FORCE) -elseif(IS_CLANG OR IS_GCC) - if(USE_SANITIZER) - set(TEST_SPECIFIC_FLAGS "-Wpedantic -Werror -mrdrnd -Wcast-align -fsanitize=alignment -fno-sanitize-recover=alignment" CACHE INTERNAL "Test-specific compiler flags" FORCE) - set(TEST_SPECIFIC_LINK_FLAGS "-fsanitize=alignment" CACHE INTERNAL "Test-specific linker flags" FORCE) - else() - set(TEST_SPECIFIC_FLAGS "-Wpedantic -Werror -mrdrnd -Wcast-align " CACHE INTERNAL "Test-specific compiler flags" FORCE) - set(TEST_SPECIFIC_LINK_FLAGS "" CACHE INTERNAL "Test-specific linker flags" FORCE) - endif() -endif() - -# --- EFI-specific flags --- -set(EFI_SPECIFIC_FLAGS "" CACHE INTERNAL "EFI-specific compiler flags") - -if(IS_MSVC) - set(EFI_SPECIFIC_FLAGS "" CACHE INTERNAL "EFI-specific compiler flags" FORCE) -elseif(IS_CLANG OR IS_GCC) - set(EFI_SPECIFIC_FLAGS "-fno-rtti -fno-exceptions" CACHE INTERNAL "EFI-specific compiler flags" FORCE) -endif() - -# Function to apply common compiler flags to a target -function(apply_common_compiler_flags target) - if(IS_MSVC) - # Convert space-separated flags to list for MSVC - separate_arguments(C_FLAGS WINDOWS_COMMAND ${COMMON_C_FLAGS}) - separate_arguments(CXX_FLAGS WINDOWS_COMMAND ${COMMON_CXX_FLAGS}) - separate_arguments(DEBUG_FLAGS WINDOWS_COMMAND ${COMMON_DEBUG_FLAGS}) - separate_arguments(RELEASE_FLAGS WINDOWS_COMMAND ${COMMON_RELEASE_FLAGS}) - separate_arguments(CPU_FLAGS WINDOWS_COMMAND ${CPU_INSTRUCTION_FLAGS}) - - target_compile_options(${target} PRIVATE - ${C_FLAGS} - $<$:${CXX_FLAGS}> - $<$:${DEBUG_FLAGS}> - $<$:${RELEASE_FLAGS}> - ${CPU_FLAGS} - ) - target_compile_definitions(${target} PRIVATE - _LIB - UNICODE _UNICODE - $<$:NDEBUG> - ) - message("Apply Common Flags MSVC to " ${target}) - else() - # Convert space-separated flags to list for Clang/GCC - separate_arguments(C_FLAGS UNIX_COMMAND ${COMMON_C_FLAGS}) - separate_arguments(CXX_FLAGS UNIX_COMMAND ${COMMON_CXX_FLAGS}) - separate_arguments(DEBUG_FLAGS UNIX_COMMAND ${COMMON_DEBUG_FLAGS}) - separate_arguments(RELEASE_FLAGS UNIX_COMMAND ${COMMON_RELEASE_FLAGS}) - separate_arguments(CPU_FLAGS UNIX_COMMAND ${CPU_INSTRUCTION_FLAGS}) - - target_compile_options(${target} PRIVATE - ${C_FLAGS} - $<$:${CXX_FLAGS}> - $<$:${DEBUG_FLAGS}> - $<$:${RELEASE_FLAGS}> - ${CPU_FLAGS} - ) - target_compile_definitions(${target} PRIVATE - _LIB - $<$:NDEBUG> - ) - message("Apply Common Flags Clang to " ${target}) - endif() -endfunction() - -# Function to apply OS-specific compiler flags to a target -function(apply_os_compiler_flags target) - apply_common_compiler_flags(${target}) - - if(IS_MSVC) - separate_arguments(OS_C_FLAGS_LIST WINDOWS_COMMAND ${OS_C_FLAGS}) - separate_arguments(OS_CXX_FLAGS_LIST WINDOWS_COMMAND ${OS_CXX_FLAGS}) - - target_compile_options(${target} PRIVATE - ${OS_C_FLAGS_LIST} - $<$:${OS_CXX_FLAGS_LIST}> - ) - message("Apply OS Flags MSVC to " ${target}) - else() - separate_arguments(OS_C_FLAGS_LIST UNIX_COMMAND ${OS_C_FLAGS}) - separate_arguments(OS_CXX_FLAGS_LIST UNIX_COMMAND ${OS_CXX_FLAGS}) - - target_compile_options(${target} PRIVATE - ${OS_C_FLAGS_LIST} - $<$:${OS_CXX_FLAGS_LIST}> - ) - message("Apply OS Flags CLANG to " ${target}) - endif() -endfunction() - -# Function to apply EFI-specific compiler flags to a target -function(apply_efi_compiler_flags target) - apply_common_compiler_flags(${target}) - - if(IS_MSVC) - separate_arguments(EFI_C_FLAGS_LIST WINDOWS_COMMAND ${EFI_C_FLAGS}) - separate_arguments(EFI_CXX_FLAGS_LIST WINDOWS_COMMAND ${EFI_CXX_FLAGS}) - - target_compile_options(${target} PRIVATE - ${EFI_C_FLAGS_LIST} - $<$:${EFI_CXX_FLAGS_LIST}> - ) - - if(EFI_LINK_FLAGS) - set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS ${EFI_LINK_FLAGS}) - endif() - message("Apply Efi Flags MSVC to " ${target}) - else() - separate_arguments(EFI_C_FLAGS_LIST UNIX_COMMAND ${EFI_C_FLAGS}) - separate_arguments(EFI_CXX_FLAGS_LIST UNIX_COMMAND ${EFI_CXX_FLAGS}) - - target_compile_options(${target} PRIVATE - ${EFI_C_FLAGS_LIST} - $<$:${EFI_CXX_FLAGS_LIST}> - ) - message("Apply Efi Flags CLANG to " ${target}) - endif() -endfunction() - -# Function to apply test-specific compiler flags to a target -function(apply_test_compiler_flags target) - apply_os_compiler_flags(${target}) - - if(IS_MSVC) - separate_arguments(TEST_FLAGS WINDOWS_COMMAND ${TEST_SPECIFIC_FLAGS}) - target_compile_options(${target} PRIVATE ${TEST_FLAGS}) - message("Apply Test Flags CLANG to " ${target}) - else() - separate_arguments(TEST_FLAGS UNIX_COMMAND ${TEST_SPECIFIC_FLAGS}) - target_compile_options(${target} PRIVATE ${TEST_FLAGS}) - - if(TEST_SPECIFIC_LINK_FLAGS) - separate_arguments(TEST_LINK_FLAGS UNIX_COMMAND ${TEST_SPECIFIC_LINK_FLAGS}) - target_link_options(${target} PRIVATE ${TEST_LINK_FLAGS}) - endif() - message("Apply Test Flags CLANG to " ${target}) - endif() -endfunction() \ No newline at end of file diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index 59e892f68..eeb7a4dc0 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -16,6 +16,8 @@ constexpr uint64 QRWA_MAX_NEW_ASSET_POLLS_PER_EPOCH = 8; constexpr uint64 QRWA_QMINE_HOLDER_PERCENT = 900; // 90.0% constexpr uint64 QRWA_QRWA_HOLDER_PERCENT = 100; // 10.0% constexpr uint64 QRWA_PERCENT_DENOMINATOR = 1000; // 100.0% +constexpr uint64 QRWA_QMINE_PER_QRWA_SHARE_MIN = 100000ULL; +constexpr uint64 QRWA_CONTRACT_ASSET_NAME = 1096241745ULL; // assetNameFromString("QRWA") // Payout Timing Constants constexpr uint64 QRWA_PAYOUT_DAY = FRIDAY; // Friday @@ -56,6 +58,7 @@ constexpr uint64 QRWA_LOG_TYPE_ADMIN_ACTION = 7; constexpr uint64 QRWA_LOG_TYPE_ERROR = 8; constexpr uint64 QRWA_LOG_TYPE_INCOMING_REVENUE_A = 9; constexpr uint64 QRWA_LOG_TYPE_INCOMING_REVENUE_B = 10; +constexpr uint64 QRWA_LOG_TYPE_INCOMING_REVENUE_DEDICATED = 11; /***************************************************/ @@ -186,10 +189,15 @@ struct QRWA : public ContractBase // Dividend Pools uint64 mRevenuePoolA; // Mined funds from Qubic farm (from SCs) uint64 mRevenuePoolB; // Other dividend funds (from user wallets) + uint64 mDedicatedRevenuePool; // Revenue from designated address // Processed dividend pools awaiting distribution uint64 mQmineDividendPool; // QUs for QMINE holders uint64 mQRWADividendPool; // QUs for qRWA shareholders + uint64 mDedicatedQRWADividendPool; // QUs for eligible qRWA shareholders + + // Dedicated revenue configuration + id mDedicatedRevenueAddress; // Total distributed tracking uint64 mTotalQmineDistributed; @@ -1152,8 +1160,12 @@ struct QRWA : public ContractBase // Initialize revenue pools state.mRevenuePoolA = 0; state.mRevenuePoolB = 0; + state.mDedicatedRevenuePool = 0; state.mQmineDividendPool = 0; state.mQRWADividendPool = 0; + state.mDedicatedQRWADividendPool = 0; + + state.mDedicatedRevenueAddress = state.mCurrentGovParams.mAdminAddress; // Initialize total distributed state.mTotalQmineDistributed = 0; @@ -1680,8 +1692,16 @@ struct QRWA : public ContractBase uint64 totalDistribution; uint64 qminePayout; uint64 qrwaPayout; + uint64 dedicatedQminePayout; + uint64 dedicatedQrwaPayout; uint64 amountPerQRWAShare; uint64 distributedAmount; + uint64 dedicatedEligibleShares; + uint64 dedicatedAmountPerShare; + uint64 dedicatedDistributed; + uint64 qrwaShares; + uint64 requiredQmine; + sint64 qmineBalance; sint64 qminePayoutIndex; id holder; @@ -1697,6 +1717,8 @@ struct QRWA : public ContractBase uint64 payout_u64; uint64 foundEnd; QRWALogger logger; + AssetPossessionIterator qrwaIter; + Asset qrwaAsset; }; END_TICK_WITH_LOCALS() { @@ -1810,6 +1832,17 @@ struct QRWA : public ContractBase state.mRevenuePoolB = 0; } + // Allocate dedicated revenue pool + if (state.mDedicatedRevenuePool > 0) + { + locals.dedicatedQminePayout = div(smul(state.mDedicatedRevenuePool, QRWA_QMINE_HOLDER_PERCENT), QRWA_PERCENT_DENOMINATOR); + locals.dedicatedQrwaPayout = state.mDedicatedRevenuePool - locals.dedicatedQminePayout; + + state.mQmineDividendPool = sadd(state.mQmineDividendPool, locals.dedicatedQminePayout); + state.mDedicatedQRWADividendPool = sadd(state.mDedicatedQRWADividendPool, locals.dedicatedQrwaPayout); + state.mDedicatedRevenuePool = 0; + } + // Distribute QMINE rewards if (state.mQmineDividendPool > 0 && state.mPayoutTotalQmineBegin > 0) { @@ -1937,6 +1970,113 @@ struct QRWA : public ContractBase } } + // Distribute dedicated qRWA rewards to eligible shareholders + if (state.mDedicatedQRWADividendPool > 0) + { + locals.qrwaAsset.issuer = id::zero(); + locals.qrwaAsset.assetName = QRWA_CONTRACT_ASSET_NAME; + locals.dedicatedEligibleShares = 0; + + for (locals.qrwaIter.begin(locals.qrwaAsset); !locals.qrwaIter.reachedEnd(); locals.qrwaIter.next()) + { + locals.qrwaShares = static_cast(locals.qrwaIter.numberOfPossessedShares()); + if (locals.qrwaShares == 0) + { + continue; + } + + locals.holder = locals.qrwaIter.possessor(); + if (locals.holder == SELF) + { + continue; + } + + locals.qmineBalance = qpi.numberOfShares( + state.mQmineAsset, + AssetOwnershipSelect::byOwner(locals.holder), + AssetPossessionSelect::byPossessor(locals.holder) + ); + + if (locals.qmineBalance <= 0) + { + continue; + } + + locals.requiredQmine = smul(locals.qrwaShares, QRWA_QMINE_PER_QRWA_SHARE_MIN); + if (static_cast(locals.qmineBalance) >= locals.requiredQmine) + { + locals.dedicatedEligibleShares = sadd(locals.dedicatedEligibleShares, locals.qrwaShares); + } + } + + if (locals.dedicatedEligibleShares > 0) + { + locals.dedicatedAmountPerShare = div(state.mDedicatedQRWADividendPool, locals.dedicatedEligibleShares); + if (locals.dedicatedAmountPerShare > 0) + { + locals.dedicatedDistributed = 0; + for (locals.qrwaIter.begin(locals.qrwaAsset); !locals.qrwaIter.reachedEnd(); locals.qrwaIter.next()) + { + locals.qrwaShares = static_cast(locals.qrwaIter.numberOfPossessedShares()); + if (locals.qrwaShares == 0) + { + continue; + } + + locals.holder = locals.qrwaIter.possessor(); + if (locals.holder == SELF) + { + continue; + } + + locals.qmineBalance = qpi.numberOfShares( + state.mQmineAsset, + AssetOwnershipSelect::byOwner(locals.holder), + AssetPossessionSelect::byPossessor(locals.holder) + ); + + if (locals.qmineBalance <= 0) + { + continue; + } + + locals.requiredQmine = smul(locals.qrwaShares, QRWA_QMINE_PER_QRWA_SHARE_MIN); + if (static_cast(locals.qmineBalance) < locals.requiredQmine) + { + continue; + } + + locals.payout_u64 = smul(locals.dedicatedAmountPerShare, locals.qrwaShares); + if (locals.payout_u64 > 0) + { + if (qpi.transfer(locals.holder, static_cast(locals.payout_u64)) >= 0) + { + locals.dedicatedDistributed = sadd(locals.dedicatedDistributed, locals.payout_u64); + } + else + { + locals.logger.logType = QRWA_LOG_TYPE_ERROR; + locals.logger.primaryId = locals.holder; + locals.logger.valueA = locals.payout_u64; + locals.logger.valueB = QRWA_STATUS_FAILURE_TRANSFER_FAILED; + LOG_INFO(locals.logger); + } + } + } + + if (state.mDedicatedQRWADividendPool > locals.dedicatedDistributed) + { + state.mDedicatedQRWADividendPool -= locals.dedicatedDistributed; + } + else + { + state.mDedicatedQRWADividendPool = 0; + } + state.mTotalQRWADistributed = sadd(state.mTotalQRWADistributed, locals.dedicatedDistributed); + } + } + } + // Update last payout time state.mLastPayoutTime = qpi.now(); locals.logger.logType = QRWA_LOG_TYPE_DISTRIBUTION; @@ -1955,9 +2095,20 @@ struct QRWA : public ContractBase POST_INCOMING_TRANSFER_WITH_LOCALS() { // Differentiate revenue streams based on source type + // Dedicated address transfers go to the dedicated pool // Only deposit to Pool A if source is QUTIL // All other transfers (users or other contracts) go to Pool B - if (input.sourceId == id(QUTIL_CONTRACT_INDEX, 0, 0, 0)) + if (state.mDedicatedRevenueAddress != NULL_ID && input.sourceId == state.mDedicatedRevenueAddress) + { + state.mDedicatedRevenuePool = sadd(state.mDedicatedRevenuePool, static_cast(input.amount)); + locals.logger.contractId = CONTRACT_INDEX; + locals.logger.logType = QRWA_LOG_TYPE_INCOMING_REVENUE_DEDICATED; + locals.logger.primaryId = input.sourceId; + locals.logger.valueA = input.amount; + locals.logger.valueB = input.type; + LOG_INFO(locals.logger); + } + else if (input.sourceId == id(QUTIL_CONTRACT_INDEX, 0, 0, 0)) { // Source is explicitly QUTIL -> Pool A state.mRevenuePoolA = sadd(state.mRevenuePoolA, static_cast(input.amount)); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 6723ec0a1..000000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,70 +0,0 @@ -cmake_minimum_required(VERSION 3.15) -project(qubic_core_tests CXX C) - -# GoogleTest requires at least C++20 -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG v1.16.0 - ) -# For Windows: Prevent overriding the parent project's compiler/linker settings -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(googletest) - - -get_filename_component(PROJECT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/platform_common) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/platform_os) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/platform_efi) # Currently still needed due to various imports -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src) -include_directories(${PROJECT_ROOT_DIR}) - - -add_executable( - qubic_core_tests - # assets.cpp - # common_def.cpp - # contract_core.cpp - # contract_qearn.cpp - # contract_qvault.cpp - # contract_qx.cpp - # kangaroo_twelve.cpp - m256.cpp - math_lib.cpp - network_messages.cpp - # platform.cpp - # qpi_collection.cpp - # qpi.cpp - # qpi_hash_map.cpp - # score_cache.cpp - # score.cpp - # spectrum.cpp - # stdlib_impl.cpp - # tick_storage.cpp - # tx_status_request.cpp - # vote_counter.cpp -) - -# Apply test-specific compiler flags from the centralized detection module -apply_test_compiler_flags(qubic_core_tests) - -# Add additional test-specific flags if needed -if(IS_CLANG OR IS_GCC) - target_compile_options(qubic_core_tests PRIVATE -mrdrnd) -endif() - - -target_link_libraries( - qubic_core_tests PRIVATE - GTest::gtest_main - platform_common - platform_os -) - -include(GoogleTest) -gtest_discover_tests(qubic_core_tests) \ No newline at end of file diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 85a46fba7..000000000 --- a/test/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Qubic Core Testing - -## Testnet operation - -New features and releases of the Qubic Core must be tested in the target environment (EFI bare metal) in a testnet with multiple nodes. -In this task, developer are supported by the test team, which is led by kavatak and can be initially contacted via the [public test-net channel on Discord](https://discord.com/channels/768887649540243497/1182262429174992937). - -### Testing checklist -Importance Levels: -- **High**: The network and nodes can't function properly, need to fix asap or we should not release. - -- **Medium**: Funds are cryptographically secured but extra services don't work as expected (moneyFlew, state save/load features). These services can be turned off temporarily. - -- **Low**: Not really harmful, eg: display error. Can be fixed later. - - -5 main components inside qubic core: -``` -[Consensus algo (tick processors)] -[Solution scoring system] -[Request processors] + [Networking] -[Smart contract processor] -[Extra services] -``` - -Consensus algo (tick processors): -- [High] No misalignment -- [High] Files that are generated at the end of each epoch must be matched between nodes (universe, spectrum, contractXXXX, `.futureComputor` part of `system`). Use `md5sum` to check if the files match. To compare the list of future computers, use the analyzeSystem tool. -- [High] No stuck for more than 10 mins (not misalignment) -- [Medium] Successful epoch transition. After the transition, fresh node can join with the EFI file with new tick and epoch (ensure `#define START_NETWORK_FROM_SCRATCH 0` is set). -- [Low] All displayed stats are reset after epoch transition. - - -Extra services: -- [High] The tx add on (moneyflew). Set `#define ADDON_TX_STATUS_REQUEST 1` to enable it and verify transaction processing. -- [Medium] The node is able to save tick data and reload when resetting. To do this, set `#define TICK_STORAGE_AUTOSAVE_MODE 1` during compilation. Wait for the state to be saved, or press F8. Restart the node. -- [Medium] All logging works as expected. Including: qu transfer, share transfer, contract message, burning loggings. Use the [qlogging tool](https://github.com/qubic/qlogging) to check the log. - - -Solution scoring system: -- [High] No misalignment on resourceTestingDigest -- [High] Submit 30 valid solutions from different addresses, node shows +30 solutions, deposited coins are returned. Press F2 to see X/Y/Z/W solutions (X: Number of recorded solutions, Y: Number of published solutions, Z: Number of obsolete solutions, W: Total solutions received by the node). -- [High] Submit 30 invalid solutions from different addresses, node doesn't show anything, M burn logs appear. -- [High] Submit 4 valid solutions with 2 invalid solutions from the same address, node shows +4 solutions, 4 deposits are returned, 2 get burned. -- [High] Mining seed changes between mining & idle phases. Use [qubic-cli](https://github.com/qubic/qubic-cli) and the -getsysteminfo command to verify that the RandomMiningSeed value is updated properly between the mining and idle phases. -- [High] Submit 451+ valid solutions for custom mining. Check results using F3 on the node. -- [Medium] Repeatedly submitting 30 valid solutions. Press F2, expect the "Score cache" increases. -- [Medium] Solution processing time is under 1000ms (only on BM avx512). -- [Medium] Can set mining threshold remotely via qubic-cli -setsolutionthreshold. - -Smart contract processor: -- [High] No misalignment on computerDigest/spectrumDigest/universeDigest -- [High] Good stack memory usage (can only know by pressing F2 for now) - -Request processors and networking: -- [High] All threads are healthy. -- [Medium] Success rate of connection to the node: >90% Good. >70% OK. Acceptable >50%. Potentially bug <50% - - -## Testing with Test Example SCs - -To enable testing of components that cannot be covered by unit tests, the Test Example SCs provide interfaces to run tests in a ticking testnet via qubic-cli's testing commands. -At the moment, only the `TESTEXA` SC is used for this purpose. - -### Setup -- Enable the testing SCs in the core code by adding `#define INCLUDE_CONTRACT_TEST_EXAMPLES` before including contract_def.h in qubic.cpp. -- In qubic-cli testUtils.cpp file, make sure that `#define TESTEXA_CONTRACT_INDEX ` and `#define TESTEXA_ADDRESS ""` still match the index and address of the `TESTEXA` SC (it will change when new SCs are added). -- Run a test network with the adapted core code. - -### Tests to perform via qubic-cli -- [High] Run the command `-testqpifunctionsoutput` with a computor seed. The computor seed is required because other entities have a limit of max. 1 pending tx and the command will send 15 txs for future ticks. The command will wait and print whether the txs were included in the respective ticks. Ideally, they should all be included but sometimes there might be missing ones which did not pass the consensus. Afterwards, the command will check if the output of QPI functions saved in the `TESTEXA` state match the TickData and quorum tick votes. -- [High] The command `-testqpifunctionsoutputpast` will perform the same check as the previous test but only for past ticks (no seed required). -A special case is testing the QPI functions output after loading the state from a snapshot. For this, wait until a snapshot is saved, re-start the node from the snapshot, then run the command quickly. Ideally, the ticks tested by the command should include ticks from before and after loading the snapshot. - - -## Google Tests - -For simplified, automated, and isolated testing of components, we use the "test" project. -It is based on the Google Test framework and runs in your OS, facilitating easy debugging within your dev environment. - -### Score test - -The Score Test will compare the generated results with the ground-truth files located in test/data. - -#### Ground-truth files -The ground-truth files will be read by the score test, typically consist of two files: -1. **samples_xxx.csv** contains the input data. - - Each column is hex presentation of mining seeds, public keys and nonces. - - Each row is a sample -2. **scores_xxx.csv** the score running on the sample file - - Column presents score setting - - Row will corresponds to a row in the sample file. For example, the 10th row in scores_xxx.csv is the result of the data in the 10th row of the sample file. - -#### Reading ground-truth files - -The ground-truth files are in CSV format and can be read using various applications such as OpenOffice, MS Excel, and Google Sheets. - -For reference on how to read these files, please see **core/test/utils.h**. - -#### Generate ground-truth files -Ground-truth files can be generated using the tools available in the **core/tools** directory. You can pass the -h argument to get detailed instructions. - -For example, - -- Generate a sample file *samples_1234.csv* with 32 samples and run the reference score computation, saving the result into *score_1234.csv* -``` -score_test_generator.exe -m generator -s samples_1234.csv -n 32 -o score_1234.csv -``` - -- Use an existing sample file *samples_1234.csv*, run the reference score computation, and save the result into *score_1234.csv* -``` -score_test_generator.exe -m generator -s samples_1234.csv -o score_1234.csv -``` diff --git a/test/assets.cpp b/test/assets.cpp deleted file mode 100644 index fc15c4e50..000000000 --- a/test/assets.cpp +++ /dev/null @@ -1,757 +0,0 @@ -#define NO_UEFI - -#define PRINT_TEST_INFO 0 - -#include "gtest/gtest.h" -#include "test_util.h" - -#include "logging_test.h" - -#include "assets/assets.h" -#include "contract_core/contract_exec.h" -#include "contract_core/qpi_spectrum_impl.h" -#include "contract_core/qpi_asset_impl.h" - - - - -class AssetsTest : public AssetStorage, LoggingTest -{ -public: - AssetsTest() - { - initAssets(); - commonBuffers.init(1, universeSizeInBytes); - } - - ~AssetsTest() - { - commonBuffers.deinit(); - deinitAssets(); - } - - static void clearUniverse() - { - memset(assets, 0, ASSETS_CAPACITY * sizeof(assets[0])); - as.indexLists.reset(); - } - - static void checkAssetsConsistency(bool printInfo = false) - { - // check lists - std::map listElementCount; - unsigned int issuanceIdx = indexLists.issuancesFirstIdx; - while (issuanceIdx != NO_ASSET_INDEX) - { - // check issuance - EXPECT_LT(issuanceIdx, ASSETS_CAPACITY); - EXPECT_EQ(assets[issuanceIdx].varStruct.issuance.type, ISSUANCE); - - // check all ownerships of issuance - unsigned int ownershipIdx = indexLists.ownershipsPossessionsFirstIdx[issuanceIdx]; - while (ownershipIdx != NO_ASSET_INDEX) - { - // check ownership - EXPECT_LT(ownershipIdx, ASSETS_CAPACITY); - EXPECT_EQ(assets[ownershipIdx].varStruct.issuance.type, OWNERSHIP); - - // check all possessions of ownership - unsigned int possessionIdx = indexLists.ownershipsPossessionsFirstIdx[ownershipIdx]; - while (possessionIdx != NO_ASSET_INDEX) - { - // check possession - EXPECT_LT(possessionIdx, ASSETS_CAPACITY); - EXPECT_EQ(assets[possessionIdx].varStruct.issuance.type, POSSESSION); - - // count possession of this ownership - ++listElementCount[ownershipIdx]; - - // get next ownership - possessionIdx = indexLists.nextIdx[possessionIdx]; - } - - // count ownerships of this issuance - ++listElementCount[issuanceIdx]; - - // get next ownership - ownershipIdx = indexLists.nextIdx[ownershipIdx]; - } - - // count issuances (use noAssetIndex to identify list) - ++listElementCount[NO_ASSET_INDEX]; - - // get next issuance - issuanceIdx = indexLists.nextIdx[issuanceIdx]; - } - - // count based on assets array - std::map arrayElementCount; - for (int index = 0; index < ASSETS_CAPACITY; index++) - { - switch (assets[index].varStruct.issuance.type) - { - case ISSUANCE: - if (printInfo) - { - char assetName[8]; - memcpy(assetName, assets[index].varStruct.issuance.name, 7); - assetName[7] = 0; - std::cout << "asset " << assetName << " by " << assets[index].varStruct.issuance.publicKey << ": index " << index << std::endl; - } - ++arrayElementCount[NO_ASSET_INDEX]; - break; - case OWNERSHIP: - ++arrayElementCount[assets[index].varStruct.ownership.issuanceIndex]; - break; - case POSSESSION: - ++arrayElementCount[assets[index].varStruct.possession.ownershipIndex]; - break; - } - } - - // check that counts match - EXPECT_EQ(listElementCount.size(), arrayElementCount.size()); - for (auto it1 = listElementCount.begin(), it2 = arrayElementCount.begin(); - it1 != listElementCount.end() && it2 != arrayElementCount.end(); - ++it1, ++it2) - { - EXPECT_EQ(it1->first, it2->first); - EXPECT_EQ(it1->second, it2->second); - } - - // check that number of owned and possessed shares are equal for each issuance - issuanceIdx = indexLists.issuancesFirstIdx; - while (issuanceIdx != NO_ASSET_INDEX) - { - Asset asset(assets[issuanceIdx].varStruct.issuance.publicKey, assetNameFromString(assets[issuanceIdx].varStruct.issuance.name)); - long long numOfSharesOwned = 0, numOfSharesPossessed = 0; - for (AssetOwnershipIterator iter(asset); !iter.reachedEnd(); iter.next()) - { - numOfSharesOwned += iter.numberOfOwnedShares(); - } - for (AssetPossessionIterator iter(asset); !iter.reachedEnd(); iter.next()) - { - numOfSharesPossessed += iter.numberOfPossessedShares(); - } - EXPECT_EQ(numOfSharesPossessed, numOfSharesOwned); - - issuanceIdx = indexLists.nextIdx[issuanceIdx]; - } - } -}; - - -TEST(TestCoreAssets, CheckLoadFile) -{ - AssetsTest test; - if (loadUniverse(L"universe.136")) - { - test.checkAssetsConsistency(true); - } - else - { - std::cout << "Universe file not found. Skipping file test..." << std::endl; - } -} - -struct AssetSharesKey -{ - m256i publicKey; - unsigned int managingContract; - - bool operator < (const AssetSharesKey& rhs) const - { - if (publicKey < rhs.publicKey) - return true; - else if (rhs.publicKey < publicKey) - return false; - else - return managingContract < rhs.managingContract; - } -}; - -struct AssetKey : public Asset -{ - bool operator < (const Asset& rhs) const - { - if (issuer < rhs.issuer) - return true; - else if (rhs.issuer < issuer) - return false; - else - return assetName < rhs.assetName; - } -}; - -struct IssuanceTestData -{ - Asset id; - unsigned short managingContract; - unsigned int universeIdx; - long long numOfShares; - int numOfOwners; - int transferDivisor; - - std::map shares; - std::map ownershipIdx; - std::map possessionIdx; - - void checkIssuance(const AssetIssuanceIterator& iter) const - { - unsigned int idxI = iter.issuanceIndex(); - EXPECT_LT(idxI, ASSETS_CAPACITY); - EXPECT_EQ(idxI, universeIdx); - EXPECT_EQ(assets[idxI].varStruct.issuance.type, ISSUANCE); - EXPECT_EQ((*((unsigned long long*)assets[idxI].varStruct.issuance.name)) & 0xFFFFFFFFFFFFFF, id.assetName); - EXPECT_EQ(assets[idxI].varStruct.issuance.publicKey, id.issuer); - - EXPECT_EQ(iter.issuer(), id.issuer); - EXPECT_EQ(iter.assetName(), id.assetName); - } - - void checkOwnershipAndIssuance(const AssetOwnershipIterator& iter) const - { - unsigned int idxI = iter.issuanceIndex(); - EXPECT_LT(idxI, ASSETS_CAPACITY); - EXPECT_EQ(idxI, universeIdx); - EXPECT_EQ(assets[idxI].varStruct.issuance.type, ISSUANCE); - EXPECT_EQ((*((unsigned long long*)assets[idxI].varStruct.issuance.name)) & 0xFFFFFFFFFFFFFF, id.assetName); - EXPECT_EQ(assets[idxI].varStruct.issuance.publicKey, id.issuer); - - unsigned int idxO = iter.ownershipIndex(); - EXPECT_LT(idxO, ASSETS_CAPACITY); - EXPECT_EQ(assets[idxO].varStruct.ownership.type, OWNERSHIP); - EXPECT_EQ(assets[idxO].varStruct.ownership.issuanceIndex, universeIdx); - unsigned int managingContract = assets[idxO].varStruct.ownership.managingContractIndex; - - const QPI::id& owner = assets[idxO].varStruct.ownership.publicKey; - AssetSharesKey ownerKey = { owner, managingContract }; - const auto sharesIt = shares.find(ownerKey); - ASSERT_NE(sharesIt, shares.end()); - long long numOfShares = sharesIt->second; - EXPECT_EQ(assets[idxO].varStruct.ownership.numberOfShares, numOfShares); - const auto ownershipIdxIt = ownershipIdx.find(ownerKey); - ASSERT_NE(ownershipIdxIt, ownershipIdx.end()); - EXPECT_EQ(idxO, ownershipIdxIt->second); - - EXPECT_EQ(iter.numberOfOwnedShares(), numOfShares); - EXPECT_EQ(iter.issuer(), id.issuer); - EXPECT_EQ(iter.assetName(), id.assetName); - EXPECT_EQ(iter.owner(), owner); - EXPECT_EQ((uint32)iter.ownershipManagingContract(), managingContract); - } - - void checkPossessionAndOwnershipAndIssuance(const AssetPossessionIterator& iter) const - { - checkOwnershipAndIssuance(iter); - - unsigned int idxP = iter.possessionIndex(); - EXPECT_LT(idxP, ASSETS_CAPACITY); - EXPECT_EQ(assets[idxP].varStruct.possession.type, POSSESSION); - EXPECT_EQ(assets[idxP].varStruct.possession.ownershipIndex, iter.ownershipIndex()); - unsigned int managingContract = (int)assets[idxP].varStruct.possession.managingContractIndex; - - const QPI::id& possessor = assets[idxP].varStruct.possession.publicKey; - AssetSharesKey possessorKey = { possessor, managingContract }; - const auto sharesIt = shares.find(possessorKey); - ASSERT_NE(sharesIt, shares.end()); - long long numOfShares = sharesIt->second; - EXPECT_EQ(assets[idxP].varStruct.possession.numberOfShares, numOfShares); - const auto possessionIdxIt = possessionIdx.find(possessorKey); - ASSERT_NE(possessionIdxIt, possessionIdx.end()); - EXPECT_EQ(idxP, possessionIdxIt->second); - - EXPECT_EQ(iter.numberOfPossessedShares(), numOfShares); - EXPECT_EQ(iter.possessor(), possessor); - EXPECT_EQ((uint32)iter.possessionManagingContract(), managingContract); - } -}; - -TEST(TestCoreAssets, AssetIterators) -{ - AssetsTest test; - test.clearUniverse(); - - IssuanceTestData issuances[] = { - { { m256i(1, 2, 3, 4), assetNameFromString("BLUB") }, 1, NO_ASSET_INDEX, 10000, 10, 30 }, - { { m256i::zero(), assetNameFromString("QX") }, 2, NO_ASSET_INDEX, 676, 20, 30 }, - { { m256i(1, 2, 3, 4), assetNameFromString("BLA") }, 3, NO_ASSET_INDEX, 123456789, 200, 30 }, - { { m256i(2, 2, 3, 4), assetNameFromString("BLA") }, 4, NO_ASSET_INDEX, 987654321, 300, 30 }, - { { m256i(1234, 2, 3, 4), assetNameFromString("BLA") }, 5, NO_ASSET_INDEX, 9876543210123ll, 676, 30 }, - { { m256i(1234, 2, 3, 4), assetNameFromString("FOO") }, 6, NO_ASSET_INDEX, 1000000ll, 2, 1 }, - }; - constexpr int issuancesCount = sizeof(issuances) / sizeof(issuances[0]); - m256i unusedPublicKey(9876, 4321, 0, 13579); - - // With empty universe, all iterators should stop right after init - AssetIssuanceIterator iterI; - EXPECT_TRUE(iterI.reachedEnd()); - EXPECT_EQ(iterI.issuanceIndex(), NO_ASSET_INDEX); - for (int i = 0; i < issuancesCount; ++i) - { - AssetOwnershipIterator iterO(issuances[i].id); - EXPECT_TRUE(iterO.reachedEnd()); - EXPECT_EQ(iterO.issuanceIndex(), NO_ASSET_INDEX); - EXPECT_EQ(iterO.ownershipIndex(), NO_ASSET_INDEX); - AssetPossessionIterator iterP(issuances[i].id); - EXPECT_TRUE(iterP.reachedEnd()); - EXPECT_EQ(iterP.issuanceIndex(), NO_ASSET_INDEX); - EXPECT_EQ(iterP.ownershipIndex(), NO_ASSET_INDEX); - EXPECT_EQ(iterP.possessionIndex(), NO_ASSET_INDEX); - } - - // Build universe with multiple owners / possessor per issuance - for (int i = 0; i < issuancesCount; ++i) - { - int firstOwnershipIdx = -1, firstPossessionIdx = -1, issuanceIdx = -1; - EXPECT_EQ(issueAsset(issuances[i].id.issuer, assetNameFromInt64(issuances[i].id.assetName).c_str(), 0, CONTRACT_ASSET_UNIT_OF_MEASUREMENT, - issuances[i].numOfShares, issuances[i].managingContract, &issuanceIdx, &firstOwnershipIdx, &firstPossessionIdx), issuances[i].numOfShares); - issuances[i].universeIdx = issuanceIdx; - - test.checkAssetsConsistency(); - - long long remainingShares = issuances[i].numOfShares; - for (int j = 1; j < issuances[i].numOfOwners; ++j) - { - long long sharesToTransfer = remainingShares / issuances[i].transferDivisor; - id destId(j*10, 9, 8, 7); - int destOwnershipIdx = -1, destPossessionIdx = -1; - EXPECT_TRUE(transferShareOwnershipAndPossession(firstOwnershipIdx, firstPossessionIdx, destId, sharesToTransfer, &destOwnershipIdx, &destPossessionIdx, false)); - AssetSharesKey key{ destId, issuances[i].managingContract }; - issuances[i].shares[key] = sharesToTransfer; - issuances[i].ownershipIdx[key] = destOwnershipIdx; - issuances[i].possessionIdx[key] = destPossessionIdx; - remainingShares -= sharesToTransfer; - } - - AssetSharesKey key{ issuances[i].id.issuer, issuances[i].managingContract }; - issuances[i].shares[key] = remainingShares; - issuances[i].ownershipIdx[key] = firstOwnershipIdx; - issuances[i].possessionIdx[key] = firstPossessionIdx; - - test.checkAssetsConsistency(i == issuancesCount - 1); - } - - { - // Test iterating all issuances with AssetIssuanceIterator - std::map testIssuancesSet; - for (int i = 0; i < issuancesCount; ++i) - { - AssetKey key{ issuances[i].id.issuer, issuances[i].id.assetName }; -#if PRINT_TEST_INFO > 0 - std::cout << issuances[i].id.issuer << ", name " << issuances[i].id.assetName << ", idx " << issuances[i].universeIdx << std::endl; -#endif - testIssuancesSet[key] = &issuances[i]; - } - AssetIssuanceIterator iter; - while (!iter.reachedEnd()) - { - AssetKey key{ iter.issuer(), iter.assetName() }; -#if PRINT_TEST_INFO > 0 - std::cout << iter.issuer() << ", name " << iter.assetName() << ", idx " << iter.issuanceIndex() << std::endl; -#endif - auto testIssuancesSetIt = testIssuancesSet.find(key); - EXPECT_NE(testIssuancesSetIt, testIssuancesSet.end()); - testIssuancesSetIt->second->checkIssuance(iter); - testIssuancesSet.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - EXPECT_EQ(testIssuancesSet.size(), 0); - - // Iterate by issuer (also test reusing the iterator) - auto assetSelect = AssetIssuanceSelect::byIssuer(issuances[0].id.issuer); - for (int i = 0; i < issuancesCount; ++i) - { - if (issuances[i].id.issuer != assetSelect.issuer) - continue; - AssetKey key{ issuances[i].id.issuer, issuances[i].id.assetName }; -#if PRINT_TEST_INFO > 0 - std::cout << issuances[i].id.issuer << ", name " << issuances[i].id.assetName << ", idx " << issuances[i].universeIdx << std::endl; -#endif - testIssuancesSet[key] = &issuances[i]; - } - iter.begin(assetSelect); - while (!iter.reachedEnd()) - { - AssetKey key{ iter.issuer(), iter.assetName() }; -#if PRINT_TEST_INFO > 0 - std::cout << iter.issuer() << ", name " << iter.assetName() << ", idx " << iter.issuanceIndex() << std::endl; -#endif - auto testIssuancesSetIt = testIssuancesSet.find(key); - EXPECT_NE(testIssuancesSetIt, testIssuancesSet.end()); - testIssuancesSetIt->second->checkIssuance(iter); - testIssuancesSet.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - EXPECT_EQ(testIssuancesSet.size(), 0); - - // Iterate by name - assetSelect = AssetIssuanceSelect::byName(assetNameFromString("BLA")); - for (int i = 0; i < issuancesCount; ++i) - { - if (issuances[i].id.assetName != assetSelect.assetName) - continue; - AssetKey key{ issuances[i].id.issuer, issuances[i].id.assetName }; -#if PRINT_TEST_INFO > 0 - std::cout << issuances[i].id.issuer << ", name " << issuances[i].id.assetName << ", idx " << issuances[i].universeIdx << std::endl; -#endif - testIssuancesSet[key] = &issuances[i]; - } - iter.begin(assetSelect); - while (!iter.reachedEnd()) - { - AssetKey key{ iter.issuer(), iter.assetName() }; -#if PRINT_TEST_INFO > 0 - std::cout << iter.issuer() << ", name " << iter.assetName() << ", idx " << iter.issuanceIndex() << std::endl; -#endif - auto testIssuancesSetIt = testIssuancesSet.find(key); - EXPECT_NE(testIssuancesSetIt, testIssuancesSet.end()); - testIssuancesSetIt->second->checkIssuance(iter); - testIssuancesSet.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - EXPECT_EQ(testIssuancesSet.size(), 0); - - // Test iterator to return single issuance - for (int i = 0; i < issuancesCount; ++i) - { - iter.begin({ issuances[i].id.issuer, issuances[i].id.assetName }); - issuances[i].checkIssuance(iter); - EXPECT_FALSE(iter.next()); - } - - // Test issuance iterator with unused key - iter.begin({ unusedPublicKey, assetNameFromString("UNUSED") }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(AssetIssuanceSelect::byIssuer(unusedPublicKey)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(AssetIssuanceSelect::byName(assetNameFromString("UNUSED"))); - EXPECT_TRUE(iter.reachedEnd()); - } - - { - // Test iterating all ownerships with AssetOwnershipIterator (also tests reusing iter) - AssetOwnershipIterator iter(issuances[0].id); - for (int i = 0; i < issuancesCount; ++i) - { - std::map shares = issuances[i].shares; - std::map ownershipIdx = issuances[i].ownershipIdx; - - if (i > 0) - { - iter.begin(issuances[i].id); - } - - while (!iter.reachedEnd()) - { - issuances[i].checkOwnershipAndIssuance(iter); - AssetSharesKey key{ iter.owner(), iter.ownershipManagingContract() }; - shares.erase(key); - ownershipIdx.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - - EXPECT_EQ(shares.size(), 0); - EXPECT_EQ(ownershipIdx.size(), 0); - } - - // Test iterating ownerships with specific owner (only single iteration / record because managing contract hasn't been changed) - for (int i = 0; i < issuancesCount; ++i) - { - for (const auto& ownerOwnershipIdxPair : issuances[i].ownershipIdx) - { - iter.begin(issuances[i].id, AssetOwnershipSelect::byOwner(ownerOwnershipIdxPair.first.publicKey)); - EXPECT_FALSE(iter.reachedEnd()); - EXPECT_EQ(iter.ownershipIndex(), ownerOwnershipIdxPair.second); - issuances[i].checkOwnershipAndIssuance(iter); - EXPECT_FALSE(iter.next()); - EXPECT_TRUE(iter.reachedEnd()); - } - } - - // Test iterating all ownerships with specific managing contract (all at the moment) - for (int i = 0; i < issuancesCount; ++i) - { - std::map shares = issuances[i].shares; - std::map ownershipIdx = issuances[i].ownershipIdx; - - iter.begin(issuances[i].id, AssetOwnershipSelect::byManagingContract(issuances[i].managingContract)); - while (!iter.reachedEnd()) - { - issuances[i].checkOwnershipAndIssuance(iter); - AssetSharesKey key{ iter.owner(), iter.ownershipManagingContract() }; - shares.erase(key); - ownershipIdx.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - - EXPECT_EQ(shares.size(), 0); - EXPECT_EQ(ownershipIdx.size(), 0); - } - - // Test ownership iterator with unused key - iter.begin({ unusedPublicKey, assetNameFromString("UNUSED") }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::byOwner(unusedPublicKey)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::byManagingContract(12345)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect{ id(10, 9, 8, 7), 12345 }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect{ unusedPublicKey, 1 }); - EXPECT_TRUE(iter.reachedEnd()); - } - - { - // Test iterating all possessions with AssetPossessionIterator (also tests reusing iter) - AssetPossessionIterator iter(issuances[0].id); - for (int i = 0; i < issuancesCount; ++i) - { - std::map shares = issuances[i].shares; - std::map possessionIdx = issuances[i].possessionIdx; - - if (i > 0) - { - iter.begin(issuances[i].id); - } - - while (!iter.reachedEnd()) - { - issuances[i].checkPossessionAndOwnershipAndIssuance(iter); - AssetSharesKey key{ iter.possessor(), iter.possessionManagingContract() }; - shares.erase(key); - possessionIdx.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - - EXPECT_EQ(shares.size(), 0); - EXPECT_EQ(possessionIdx.size(), 0); - } - - // Test iterating possessions with specific possessor (only single iteration / record because managing contract hasn't been changed) - for (int i = 0; i < issuancesCount; ++i) - { - for (const auto& possessorPossessionIdxPair : issuances[i].possessionIdx) - { - iter.begin(issuances[i].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(possessorPossessionIdxPair.first.publicKey)); - EXPECT_FALSE(iter.reachedEnd()); - EXPECT_EQ(iter.possessionIndex(), possessorPossessionIdxPair.second); - issuances[i].checkPossessionAndOwnershipAndIssuance(iter); - EXPECT_FALSE(iter.next()); - EXPECT_TRUE(iter.reachedEnd()); - } - } - - // Test possession iterator with unused key - iter.begin({ unusedPublicKey, assetNameFromString("UNUSED") }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::byOwner(unusedPublicKey)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::byManagingContract(12345)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect{ id(10, 9, 8, 7), 12345 }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect{ unusedPublicKey, 1 }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(unusedPublicKey)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(12345)); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect{ id(10, 9, 8, 7), 12345 }); - EXPECT_TRUE(iter.reachedEnd()); - iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect{ unusedPublicKey, 1 }); - EXPECT_TRUE(iter.reachedEnd()); - } - - { - // Test numberOfShares() - for (int i = 0; i < issuancesCount; ++i) - { - // iterate all possession records, compare results of numberOfShares() and numberOfPossessedShares() - std::map ownedShares; - for (AssetPossessionIterator iter(issuances[i].id); !iter.reachedEnd(); iter.next()) - { - long long numOfShares = numberOfShares(issuances[i].id, { iter.owner(), issuances[i].managingContract }, { iter.possessor(), issuances[i].managingContract }); - EXPECT_EQ(numOfShares, iter.numberOfPossessedShares()); - - long long numOfPossessedShares = numberOfPossessedShares(issuances[i].id.assetName, issuances[i].id.issuer, iter.owner(), iter.possessor(), issuances[i].managingContract, issuances[i].managingContract); - EXPECT_EQ(numOfShares, numOfPossessedShares); - - AssetSharesKey key{ iter.owner(), issuances[i].managingContract }; - ownedShares[key] += numOfShares; - } - - // iterate all ownership records, compare results of numberOfShares() and numberOfOwnedShares() - long long totalShares = 0; - for (AssetOwnershipIterator iter(issuances[i].id); !iter.reachedEnd(); iter.next()) - { - long long numOfShares = numberOfShares(issuances[i].id, { iter.owner(), issuances[i].managingContract }); - EXPECT_EQ(numOfShares, iter.numberOfOwnedShares()); - - AssetSharesKey key{ iter.owner(), iter.ownershipManagingContract() }; - EXPECT_EQ(numOfShares, ownedShares[key]); - - totalShares += numOfShares; - } - - EXPECT_EQ(totalShares, numberOfShares(issuances[i].id)); - EXPECT_EQ(totalShares, issuances[i].numOfShares); - } - } - - // check consistency after rebuild/cleanup of hash map - assetsEndEpoch(); - test.checkAssetsConsistency(); - - { - // Test burning of shares - for (int i = 0; i < issuancesCount; ++i) - { - for (int j = 3; j >= 1; j--) - { - // iterate all possession records and burn 1/j part of the shares - long long expectedTotalShares = 0; - for (AssetPossessionIterator iter(issuances[i].id); !iter.reachedEnd(); iter.next()) - { - const long long numOfSharesPossessedInitially = iter.numberOfPossessedShares(); - const long long numOfSharesOwnedInitially = iter.numberOfOwnedShares(); - const long long numOfSharesToBurn = numOfSharesPossessedInitially / j; - const long long numOfSharesPossessedAfterwards = numOfSharesPossessedInitially - numOfSharesToBurn; - const long long numOfSharesOwnedAfterwards = numOfSharesOwnedInitially - numOfSharesToBurn; - - const bool success = transferShareOwnershipAndPossession(iter.ownershipIndex(), iter.possessionIndex(), NULL_ID, numOfSharesToBurn, nullptr, nullptr, false); - - if (isZero(issuances[i].id.issuer)) - { - // burning fails for contract shares - EXPECT_FALSE(success); - EXPECT_EQ(numOfSharesPossessedInitially, iter.numberOfPossessedShares()); - EXPECT_EQ(numOfSharesOwnedInitially, iter.numberOfOwnedShares()); - expectedTotalShares += numOfSharesPossessedInitially; - } - else - { - // burning succeeds for non-contract asset shares - EXPECT_TRUE(success); - EXPECT_EQ(numOfSharesPossessedAfterwards, iter.numberOfPossessedShares()); - EXPECT_EQ(numOfSharesOwnedAfterwards, iter.numberOfOwnedShares()); - expectedTotalShares += numOfSharesPossessedAfterwards; - } - } - EXPECT_EQ(expectedTotalShares, numberOfShares(issuances[i].id)); - } - } - } - - // check consistency after rebuild/cleanup of hash map - assetsEndEpoch(); - test.checkAssetsConsistency(); -} - -TEST(TestCoreAssets, AssetTransferShareManagementRights) -{ - AssetsTest test; - test.clearUniverse(); - - IssuanceTestData issuances[] = { - { { m256i(1, 2, 3, 4), assetNameFromString("BLUB") }, 1, NO_ASSET_INDEX, 10000, 10, 30 }, - { { m256i::zero(), assetNameFromString("QX") }, 1, NO_ASSET_INDEX, 676, 20, 30 }, - { { m256i(1, 2, 3, 4), assetNameFromString("BLA") }, 1, NO_ASSET_INDEX, 123456789, 200, 30 }, - { { m256i(2, 2, 3, 4), assetNameFromString("BLA") }, 1, NO_ASSET_INDEX, 987654321, 300, 30 }, - { { m256i(1234, 2, 3, 4), assetNameFromString("BLA") }, 1, NO_ASSET_INDEX, 9876543210123ll, 676, 30 }, - { { m256i(1234, 2, 3, 4), assetNameFromString("FOO") }, 1, NO_ASSET_INDEX, 1000000ll, 2, 1 }, - }; - constexpr int issuancesCount = sizeof(issuances) / sizeof(issuances[0]); - - // Build universe with multiple managing contracts per issuance - for (int i = 0; i < issuancesCount; ++i) - { - int firstOwnershipIdx = -1, firstPossessionIdx = -1, issuanceIdx = -1; - EXPECT_EQ(issueAsset(issuances[i].id.issuer, assetNameFromInt64(issuances[i].id.assetName).c_str(), 0, CONTRACT_ASSET_UNIT_OF_MEASUREMENT, - issuances[i].numOfShares, issuances[i].managingContract, &issuanceIdx, &firstOwnershipIdx, &firstPossessionIdx), issuances[i].numOfShares); - issuances[i].universeIdx = issuanceIdx; - - test.checkAssetsConsistency(); - - long long remainingShares = issuances[i].numOfShares; - for (int j = 1; j < issuances[i].numOfOwners; ++j) - { - long long sharesToTransfer = remainingShares / issuances[i].transferDivisor; - unsigned int destContractIdx = j % 15; - int destOwnershipIdx = -1, destPossessionIdx = -1; - EXPECT_TRUE(transferShareManagementRights(firstOwnershipIdx, firstPossessionIdx, - destContractIdx, destContractIdx, sharesToTransfer, &destOwnershipIdx, &destPossessionIdx, false)); - AssetSharesKey key{ issuances[i].id.issuer, destContractIdx }; - issuances[i].shares[key] += sharesToTransfer; - issuances[i].ownershipIdx[key] = destOwnershipIdx; - issuances[i].possessionIdx[key] = destPossessionIdx; - remainingShares -= sharesToTransfer; - } - - AssetSharesKey key{ issuances[i].id.issuer, issuances[i].managingContract }; - issuances[i].shares[key] += remainingShares; - issuances[i].ownershipIdx[key] = firstOwnershipIdx; - issuances[i].possessionIdx[key] = firstPossessionIdx; - - test.checkAssetsConsistency(i == issuancesCount - 1); - } - - { - // Test iterating all possessions with AssetPossessionIterator (also tests reusing iter) - AssetPossessionIterator iter(issuances[0].id); - for (int i = 0; i < issuancesCount; ++i) - { - std::map shares = issuances[i].shares; - std::map possessionIdx = issuances[i].possessionIdx; - - if (i > 0) - { - iter.begin(issuances[i].id); - } - - while (!iter.reachedEnd()) - { - issuances[i].checkPossessionAndOwnershipAndIssuance(iter); - AssetSharesKey key{ iter.possessor(), iter.possessionManagingContract() }; - shares.erase(key); - possessionIdx.erase(key); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - - EXPECT_EQ(shares.size(), 0); - EXPECT_EQ(possessionIdx.size(), 0); - } - - // Test iterating possessions with specific possessor (different managing contracts) - for (int i = 0; i < issuancesCount; ++i) - { - iter.begin(issuances[i].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(issuances[i].id.issuer)); - EXPECT_FALSE(iter.reachedEnd()); - while (!iter.reachedEnd()) - { - issuances[i].checkPossessionAndOwnershipAndIssuance(iter); - AssetSharesKey key{ iter.possessor(), iter.possessionManagingContract() }; - EXPECT_NE(issuances[i].ownershipIdx.find(key), issuances[i].ownershipIdx.end()); - EXPECT_EQ(iter.ownershipIndex(), issuances[i].ownershipIdx[key]); - EXPECT_EQ(iter.possessionIndex(), issuances[i].possessionIdx[key]); - EXPECT_EQ((int)iter.ownershipManagingContract(), (int)iter.possessionManagingContract()); - EXPECT_EQ(iter.numberOfOwnedShares(), iter.numberOfPossessedShares()); - EXPECT_EQ(iter.numberOfPossessedShares(), issuances[i].shares[key]); - bool hasNext = iter.next(); - EXPECT_EQ(hasNext, !iter.reachedEnd()); - } - } - } -} - - - - - diff --git a/test/common_def.cpp b/test/common_def.cpp deleted file mode 100644 index 4bc64cff0..000000000 --- a/test/common_def.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#define NO_UEFI -#define DEFINE_VARIABLES_SHARED_BETWEEN_COMPILE_UNITS - -#include "contract_testing.h" -#include "logging_test.h" -#include "oracle_testing.h" -#include "platform/concurrency_impl.h" -#include "platform/profiling.h" - -// Implement non-QPI version of notification trigger function that is defined in qubic.cpp, where it hands over the -// notification to the contract processor for running the incomming transfer callback. -// We don't have separate tick and contract processors in testing, so we run the callback directly. -void notifyContractOfIncomingTransfer(const m256i& source, const m256i& dest, long long amount, unsigned char type) -{ - // Only notify if amount > 0 and dest is contract - if (amount <= 0 || !isPublicKeyOfContract(dest)) - return; - - unsigned int contractIndex = (unsigned int)dest.m256i_u64[0]; - ASSERT(system.epoch >= contractDescriptions[contractIndex].constructionEpoch); - ASSERT(system.epoch < contractDescriptions[contractIndex].destructionEpoch); - - // Run callback system procedure POST_INCOMING_TRANSFER - QpiContextSystemProcedureCall qpiContext(contractIndex, POST_INCOMING_TRANSFER); - QPI::PostIncomingTransfer_input input{ source, amount, type }; - qpiContext.call(input); -} diff --git a/test/contract_ccf.cpp b/test/contract_ccf.cpp deleted file mode 100644 index 655e613a7..000000000 --- a/test/contract_ccf.cpp +++ /dev/null @@ -1,1215 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -#define PRINT_DETAILS 0 - -class CCFChecker : public CCF -{ -public: - void checkSubscriptions(bool printDetails = PRINT_DETAILS) - { - if (printDetails) - { - std::cout << "Active Subscriptions (total capacity: " << activeSubscriptions.capacity() << "):" << std::endl; - for (uint64 i = 0; i < activeSubscriptions.capacity(); ++i) - { - const SubscriptionData& sub = activeSubscriptions.get(i); - if (!isZero(sub.destination)) - { - std::cout << "- Index " << i << ": destination=" << sub.destination - << ", weeksPerPeriod=" << (int)sub.weeksPerPeriod - << ", numberOfPeriods=" << sub.numberOfPeriods - << ", amountPerPeriod=" << sub.amountPerPeriod - << ", startEpoch=" << sub.startEpoch - << ", currentPeriod=" << sub.currentPeriod << std::endl; - } - } - std::cout << "Subscription Proposals (total capacity: " << subscriptionProposals.capacity() << "):" << std::endl; - for (uint64 i = 0; i < subscriptionProposals.capacity(); ++i) - { - const SubscriptionProposalData& prop = subscriptionProposals.get(i); - if (!isZero(prop.proposerId)) - { - std::cout << "- Index " << i << ": proposerId=" << prop.proposerId - << ", destination=" << prop.destination - << ", weeksPerPeriod=" << (int)prop.weeksPerPeriod - << ", numberOfPeriods=" << prop.numberOfPeriods - << ", amountPerPeriod=" << prop.amountPerPeriod - << ", startEpoch=" << prop.startEpoch << std::endl; - } - } - } - } - - const SubscriptionData* getActiveSubscriptionByDestination(const id& destination) - { - for (uint64 i = 0; i < activeSubscriptions.capacity(); ++i) - { - const SubscriptionData& sub = activeSubscriptions.get(i); - if (sub.destination == destination && !isZero(sub.destination)) - return ⊂ - } - return nullptr; - } - - // Helper to find destination from a proposer's subscription proposal - id getDestinationByProposer(const id& proposerId) - { - // Use constant 128 which matches SubscriptionProposalsT capacity - for (uint64 i = 0; i < 128; ++i) - { - const SubscriptionProposalData& prop = subscriptionProposals.get(i); - if (prop.proposerId == proposerId && !isZero(prop.proposerId)) - return prop.destination; - } - return NULL_ID; - } - - bool hasActiveSubscription(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr; - } - - - sint32 getSubscriptionCurrentPeriod(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr ? sub->currentPeriod : -1; - } - - bool getSubscriptionIsActive(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr; - } - - // Overload for backward compatibility - use proposer ID - bool getSubscriptionIsActive(const id& proposerId, bool) - { - return getSubscriptionIsActiveByProposer(proposerId); - } - - uint8 getSubscriptionWeeksPerPeriod(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr ? sub->weeksPerPeriod : 0; - } - - uint32 getSubscriptionNumberOfPeriods(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr ? sub->numberOfPeriods : 0; - } - - sint64 getSubscriptionAmountPerPeriod(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr ? sub->amountPerPeriod : 0; - } - - uint32 getSubscriptionStartEpoch(const id& destination) - { - const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); - return sub != nullptr ? sub->startEpoch : 0; - } - - uint32 countActiveSubscriptions() - { - uint32 count = 0; - for (uint64 i = 0; i < activeSubscriptions.capacity(); ++i) - { - if (!isZero(activeSubscriptions.get(i).destination)) - count++; - } - return count; - } - - // Helper function to check if proposer has a subscription proposal - bool hasSubscriptionProposal(const id& proposerId) - { - // Use constant 128 which matches SubscriptionProposalsT capacity - for (uint64 i = 0; i < 128; ++i) - { - const SubscriptionProposalData& prop = subscriptionProposals.get(i); - if (prop.proposerId == proposerId && !isZero(prop.proposerId)) - return true; - } - return false; - } - - // Helper function for backward compatibility - finds destination from proposer's proposal and checks active subscription - bool hasActiveSubscriptionByProposer(const id& proposerId) - { - id destination = getDestinationByProposer(proposerId); - if (isZero(destination)) - return false; - return hasActiveSubscription(destination); - } - - // Helper function that checks both subscription proposals and active subscriptions by proposer - bool hasSubscription(const id& proposerId) - { - return hasSubscriptionProposal(proposerId) || hasActiveSubscriptionByProposer(proposerId); - } - - // Helper functions that work with proposer ID (for backward compatibility with tests) - bool getSubscriptionIsActiveByProposer(const id& proposerId) - { - return hasActiveSubscriptionByProposer(proposerId); - } - - // Helper to get subscription proposal data by proposer ID - const SubscriptionProposalData* getSubscriptionProposalByProposer(const id& proposerId) - { - // Use constant 128 which matches SubscriptionProposalsT capacity - for (uint64 i = 0; i < 128; ++i) - { - const SubscriptionProposalData& prop = subscriptionProposals.get(i); - if (prop.proposerId == proposerId && !isZero(prop.proposerId)) - return ∝ - } - return nullptr; - } - - uint8 getSubscriptionWeeksPerPeriodByProposer(const id& proposerId) - { - // First check subscription proposal - const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); - if (prop != nullptr) - return prop->weeksPerPeriod; - - // Then check active subscription - id destination = getDestinationByProposer(proposerId); - if (!isZero(destination)) - return getSubscriptionWeeksPerPeriod(destination); - - return 0; - } - - uint32 getSubscriptionNumberOfPeriodsByProposer(const id& proposerId) - { - // First check subscription proposal - const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); - if (prop != nullptr) - return prop->numberOfPeriods; - - // Then check active subscription - id destination = getDestinationByProposer(proposerId); - if (!isZero(destination)) - return getSubscriptionNumberOfPeriods(destination); - - return 0; - } - - sint64 getSubscriptionAmountPerPeriodByProposer(const id& proposerId) - { - // First check subscription proposal - const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); - if (prop != nullptr) - return prop->amountPerPeriod; - - // Then check active subscription - id destination = getDestinationByProposer(proposerId); - if (!isZero(destination)) - return getSubscriptionAmountPerPeriod(destination); - - return 0; - } - - uint32 getSubscriptionStartEpochByProposer(const id& proposerId) - { - // First check subscription proposal - const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); - if (prop != nullptr) - return prop->startEpoch; - - // Then check active subscription - id destination = getDestinationByProposer(proposerId); - if (!isZero(destination)) - return getSubscriptionStartEpoch(destination); - - return 0; - } - - sint32 getSubscriptionCurrentPeriodByProposer(const id& proposerId) - { - // Only check active subscription (currentPeriod doesn't exist in proposals) - id destination = getDestinationByProposer(proposerId); - if (isZero(destination)) - return -1; // No active subscription yet - return getSubscriptionCurrentPeriod(destination); - } -}; - -class ContractTestingCCF : protected ContractTesting -{ -public: - ContractTestingCCF() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(CCF); - callSystemProcedure(CCF_CONTRACT_INDEX, INITIALIZE); - - // Setup computors - for (unsigned long long i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - broadcastedComputors.computors.publicKeys[i] = id(i, 1, 2, 3); - increaseEnergy(id(i, 1, 2, 3), 1000000); - } - } - - ~ContractTestingCCF() - { - checkContractExecCleanup(); - } - - CCFChecker* getState() - { - return (CCFChecker*)contractStates[CCF_CONTRACT_INDEX]; - } - - CCF::SetProposal_output setProposal(const id& originator, const CCF::SetProposal_input& input) - { - CCF::SetProposal_output output; - invokeUserProcedure(CCF_CONTRACT_INDEX, 1, input, output, originator, 1000000); - return output; - } - - CCF::GetProposal_output getProposal(uint32 proposalIndex, const id& subscriptionDestination = NULL_ID) - { - CCF::GetProposal_input input; - input.proposalIndex = (uint16)proposalIndex; - input.subscriptionDestination = subscriptionDestination; - CCF::GetProposal_output output; - callFunction(CCF_CONTRACT_INDEX, 2, input, output); - return output; - } - - CCF::GetVotingResults_output getVotingResults(uint32 proposalIndex) - { - CCF::GetVotingResults_input input; - CCF::GetVotingResults_output output; - - input.proposalIndex = (uint16)proposalIndex; - callFunction(CCF_CONTRACT_INDEX, 4, input, output); - return output; - } - - bool vote(const id& originator, const CCF::Vote_input& input) - { - CCF::Vote_output output; - invokeUserProcedure(CCF_CONTRACT_INDEX, 2, input, output, originator, 0); - return output.okay; - } - - CCF::GetLatestTransfers_output getLatestTransfers() - { - CCF::GetLatestTransfers_output output; - callFunction(CCF_CONTRACT_INDEX, 5, CCF::GetLatestTransfers_input(), output); - return output; - } - - CCF::GetRegularPayments_output getRegularPayments() - { - CCF::GetRegularPayments_output output; - callFunction(CCF_CONTRACT_INDEX, 7, CCF::GetRegularPayments_input(), output); - return output; - } - - CCF::GetProposalFee_output getProposalFee() - { - CCF::GetProposalFee_output output; - callFunction(CCF_CONTRACT_INDEX, 6, CCF::GetProposalFee_input(), output); - return output; - } - - CCF::GetProposalIndices_output getProposalIndices(bool activeProposals, sint32 prevProposalIndex = -1) - { - CCF::GetProposalIndices_input input; - input.activeProposals = activeProposals; - input.prevProposalIndex = prevProposalIndex; - CCF::GetProposalIndices_output output; - callFunction(CCF_CONTRACT_INDEX, 1, input, output); - return output; - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(CCF_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(CCF_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - uint32 setupRegularProposal(const id& proposer, const id& destination, sint64 amount, bool expectSuccess = true) - { - CCF::SetProposal_input input; - setMemory(input, 0); - input.proposal.epoch = system.epoch; - input.proposal.type = ProposalTypes::TransferYesNo; - input.proposal.data.transfer.destination = destination; - input.proposal.data.transfer.amount = amount; - input.isSubscription = false; - - auto output = setProposal(proposer, input); - if (expectSuccess) - EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - else - EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - return output.proposalIndex; - } - - uint32 setupSubscriptionProposal(const id& proposer, const id& destination, sint64 amountPerPeriod, - uint32 numberOfPeriods, uint8 weeksPerPeriod, uint32 startEpoch, bool expectSuccess = true) - { - CCF::SetProposal_input input; - setMemory(input, 0); - input.proposal.epoch = system.epoch; - input.proposal.type = ProposalTypes::TransferYesNo; - input.proposal.data.transfer.destination = destination; - input.proposal.data.transfer.amount = amountPerPeriod; - input.isSubscription = true; - input.weeksPerPeriod = weeksPerPeriod; - input.numberOfPeriods = numberOfPeriods; - input.startEpoch = startEpoch; - input.amountPerPeriod = amountPerPeriod; - - auto output = setProposal(proposer, input); - if (expectSuccess) - EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - else - EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - return output.proposalIndex; - } - - void voteMultipleComputors(uint32 proposalIndex, uint32 votesNo, uint32 votesYes) - { - EXPECT_LE((int)(votesNo + votesYes), (int)NUMBER_OF_COMPUTORS); - const auto proposal = getProposal(proposalIndex); - EXPECT_TRUE(proposal.okay); - - CCF::Vote_input voteInput; - voteInput.proposalIndex = (uint16)proposalIndex; - voteInput.proposalType = proposal.proposal.type; - voteInput.proposalTick = proposal.proposal.tick; - - uint32 compIdx = 0; - for (uint32 i = 0; i < votesNo; ++i, ++compIdx) - { - voteInput.voteValue = 0; // 0 = no vote - EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), voteInput)); - } - for (uint32 i = 0; i < votesYes; ++i, ++compIdx) - { - voteInput.voteValue = 1; // 1 = yes vote - EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), voteInput)); - } - - auto results = getVotingResults(proposalIndex); - EXPECT_TRUE(results.okay); - EXPECT_EQ(results.results.optionVoteCount.get(0), uint32(votesNo)); - EXPECT_EQ(results.results.optionVoteCount.get(1), uint32(votesYes)); - } -}; - -static id ENTITY0(7, 0, 0, 0); -static id ENTITY1(100, 0, 0, 0); -static id ENTITY2(123, 456, 789, 0); -static id ENTITY3(42, 69, 0, 13); -static id ENTITY4(3, 14, 2, 7); - -TEST(ContractCCF, BasicInitialization) -{ - ContractTestingCCF test; - - // Check initial state - auto fee = test.getProposalFee(); - EXPECT_EQ(fee.proposalFee, 1000000u); -} - -TEST(ContractCCF, RegularProposalAndVoting) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - - // Set a regular transfer proposal - increaseEnergy(PROPOSER1, 1000000); - uint32 proposalIndex = test.setupRegularProposal(PROPOSER1, ENTITY1, 10000); - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Get proposal - auto proposal = test.getProposal(proposalIndex); - EXPECT_TRUE(proposal.okay); - EXPECT_EQ(proposal.proposal.data.transfer.destination, ENTITY1); - - // Vote on proposal - test.voteMultipleComputors(proposalIndex, 200, 350); - - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - - // End epoch to process votes - test.endEpoch(); - - // Check that transfer was executed - auto transfers = test.getLatestTransfers(); - bool found = false; - for (uint64 i = 0; i < transfers.capacity(); ++i) - { - if (transfers.get(i).destination == ENTITY1 && transfers.get(i).amount == 10000) - { - found = true; - EXPECT_TRUE(transfers.get(i).success); - break; - } - } - EXPECT_TRUE(found); -} - -TEST(ContractCCF, SubscriptionProposalCreation) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Create a subscription proposal - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 12, 4, system.epoch + 1); // 4 weeks per period (monthly) - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Check that subscription proposal was stored - auto state = test.getState(); - EXPECT_TRUE(state->hasSubscription(PROPOSER1)); - EXPECT_FALSE(state->getSubscriptionIsActiveByProposer(PROPOSER1)); // Not active until accepted - EXPECT_EQ(state->getSubscriptionWeeksPerPeriodByProposer(PROPOSER1), 4u); - EXPECT_EQ(state->getSubscriptionNumberOfPeriodsByProposer(PROPOSER1), 12u); - EXPECT_EQ(state->getSubscriptionAmountPerPeriodByProposer(PROPOSER1), 1000); - EXPECT_EQ(state->getSubscriptionStartEpochByProposer(PROPOSER1), system.epoch + 1); - EXPECT_EQ(state->getSubscriptionCurrentPeriodByProposer(PROPOSER1), -1); - - // Get proposal with subscription data - auto proposal = test.getProposal(proposalIndex, NULL_ID); - EXPECT_TRUE(proposal.okay); - EXPECT_TRUE(proposal.hasSubscriptionProposal); - EXPECT_FALSE(isZero(proposal.subscriptionProposal.proposerId)); -} - -TEST(ContractCCF, SubscriptionProposalVotingAndActivation) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - // Create a subscription proposal starting next epoch - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, system.epoch + 1); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Vote to approve - test.voteMultipleComputors(proposalIndex, 200, 350); - - // End epoch - test.endEpoch(); - - auto state = test.getState(); - - // Check subscription is now active (identified by destination) - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - EXPECT_TRUE(state->getSubscriptionIsActive(ENTITY1)); -} - -TEST(ContractCCF, SubscriptionPaymentProcessing) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - // Create subscription starting in epoch 189, weekly payments - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 500, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Approve proposal - test.voteMultipleComputors(proposalIndex, 200, 350); - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - sint64 initialBalance = getBalance(ENTITY1); - - // Move to start epoch and activate - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // Move to next epoch - should trigger first payment - system.epoch = 190; - test.beginEpoch(); - test.endEpoch(); - - // Check payment was made - sint64 newBalance = getBalance(ENTITY1); - EXPECT_GE(newBalance, initialBalance + 500 + 500); - - // Check regular payments log - auto payments = test.getRegularPayments(); - bool foundPayment = false; - for (uint64 i = 0; i < payments.capacity(); ++i) - { - const auto& payment = payments.get(i); - if (payment.destination == ENTITY1 && payment.amount == 500 && payment.periodIndex == 0) - { - foundPayment = true; - EXPECT_TRUE(payment.success); - break; - } - } - EXPECT_TRUE(foundPayment); - - // Check subscription currentPeriod was updated - auto state = test.getState(); - EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), 1); -} - -TEST(ContractCCF, MultipleSubscriptionPayments) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - // Create monthly subscription (4 epochs per period) - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 3, 4, 189); // 4 weeks per period (monthly) - - test.voteMultipleComputors(proposalIndex, 200, 350); - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - sint64 initialBalance = getBalance(ENTITY1); - - // Activate subscription - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // Move through epochs - should trigger payments at epochs 189, 193, 197 - for (uint32 epoch = 189; epoch <= 197; ++epoch) - { - system.epoch = epoch; - test.beginEpoch(); - test.endEpoch(); - } - - // Should have made 3 payments (periods 0, 1, 2) - sint64 newBalance = getBalance(ENTITY1); - EXPECT_GE(newBalance, initialBalance + 1000 + 1000 + 1000); - - // Check subscription completed all periods - auto state = test.getState(); - EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), -1); -} - -TEST(ContractCCF, PreventMultipleActiveSubscriptions) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - // Create first subscription - uint32 proposalIndex1 = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); - - increaseEnergy(PROPOSER1, 1000000); - // Try to create second subscription for same proposer - should overwrite the previous one - uint32 proposalIndex2 = test.setupSubscriptionProposal( - PROPOSER1, ENTITY2, 2000, 4, 1, 189, true); // 1 week per period (weekly) - EXPECT_EQ((int)proposalIndex2, (int)proposalIndex1); -} - -TEST(ContractCCF, CancelSubscription) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - // Create subscription - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - // Cancel proposal (epoch = 0) - CCF::SetProposal_input cancelInput; - setMemory(cancelInput, 0); - cancelInput.proposal.epoch = 0; - cancelInput.proposal.type = ProposalTypes::TransferYesNo; - cancelInput.isSubscription = true; - cancelInput.weeksPerPeriod = 1; // 1 week per period (weekly) - cancelInput.numberOfPeriods = 4; - cancelInput.startEpoch = 189; - cancelInput.amountPerPeriod = 1000; - auto cancelOutput = test.setProposal(PROPOSER1, cancelInput); - EXPECT_NE((int)cancelOutput.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Check subscription was deactivated - auto state = test.getState(); - EXPECT_FALSE(state->hasSubscriptionProposal(PROPOSER1)); // proposal is canceled, so no subscription proposal -} - -TEST(ContractCCF, SubscriptionValidation) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Test invalid weeksPerPeriod (must be > 0) - CCF::SetProposal_input input; - setMemory(input, 0); - input.proposal.epoch = system.epoch; - input.proposal.type = ProposalTypes::TransferYesNo; - input.proposal.data.transfer.destination = ENTITY1; - input.proposal.data.transfer.amount = 1000; - input.isSubscription = true; - input.weeksPerPeriod = 0; - input.numberOfPeriods = 4; - input.startEpoch = system.epoch; - input.amountPerPeriod = 1000; - - // Test start epoch in past - increaseEnergy(PROPOSER1, 1000000); - input.weeksPerPeriod = 1; // 1 week per period (weekly) - input.startEpoch = system.epoch - 1; // Should be >= current epoch - auto output = test.setProposal(PROPOSER1, input); - EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Test that zero numberOfPeriods is allowed (will cancel subscription when accepted) - increaseEnergy(PROPOSER1, 1000000); - input.weeksPerPeriod = 1; - input.startEpoch = system.epoch; - input.numberOfPeriods = 0; // Allowed - will cancel subscription - input.amountPerPeriod = 1000; - output = test.setProposal(PROPOSER1, input); - EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Test that zero amountPerPeriod is allowed (will cancel subscription when accepted) - increaseEnergy(PROPOSER1, 1000000); - input.numberOfPeriods = 4; - input.amountPerPeriod = 0; // Allowed - will cancel subscription - output = test.setProposal(PROPOSER1, input); - EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); -} - -TEST(ContractCCF, MultipleProposers) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - id PROPOSER2 = broadcastedComputors.computors.publicKeys[1]; - increaseEnergy(PROPOSER2, 1000000); - - // Create subscriptions for different proposers - uint32 proposalIndex1 = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); - uint32 proposalIndex2 = test.setupSubscriptionProposal( - PROPOSER2, ENTITY2, 2000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex2, (int)INVALID_PROPOSAL_INDEX); - - // Both proposals need to first be voted in before the subscriptions become active. - auto state = test.getState(); - EXPECT_FALSE(state->hasActiveSubscription(ENTITY1)); - EXPECT_FALSE(state->hasActiveSubscription(ENTITY2)); - EXPECT_EQ(state->countActiveSubscriptions(), 0u); - - // Vote in both subscription proposals to activate them - test.voteMultipleComputors(proposalIndex1, 200, 400); - test.voteMultipleComputors(proposalIndex2, 200, 400); - - // Increase energy so contract can execute the subscriptions - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 10000000); - test.endEpoch(); - - // Now both should be active subscriptions (by destination) - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - EXPECT_TRUE(state->hasActiveSubscription(ENTITY2)); - EXPECT_EQ(state->countActiveSubscriptions(), 2u); -} - -TEST(ContractCCF, ProposalRejectedNoQuorum) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - uint32 proposalIndex = test.setupRegularProposal(PROPOSER1, ENTITY1, 10000); - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Vote but not enough for quorum - test.voteMultipleComputors(proposalIndex, 100, 200); - - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Transfer should not have been executed - auto transfers = test.getLatestTransfers(); - bool found = false; - for (uint64 i = 0; i < transfers.capacity(); ++i) - { - if (transfers.get(i).destination == ENTITY1 && transfers.get(i).amount == 10000) - { - found = true; - break; - } - } - EXPECT_FALSE(found); -} - -TEST(ContractCCF, ProposalRejectedMoreNoVotes) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - uint32 proposalIndex = test.setupRegularProposal(PROPOSER1, ENTITY1, 10000); - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // More "no" votes than "yes" votes - test.voteMultipleComputors(proposalIndex, 350, 200); - - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Transfer should not have been executed - auto transfers = test.getLatestTransfers(); - bool found = false; - for (uint64 i = 0; i < transfers.capacity(); ++i) - { - if (transfers.get(i).destination == ENTITY1 && transfers.get(i).amount == 10000) - { - found = true; - break; - } - } - EXPECT_FALSE(found); -} - -TEST(ContractCCF, SubscriptionMaxEpochsValidation) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Try to create subscription that exceeds max epochs (52) - // Monthly subscription with 14 periods = 14 * 4 = 56 epochs > 52 - CCF::SetProposal_input input; - setMemory(input, 0); - input.proposal.epoch = system.epoch; - input.proposal.type = ProposalTypes::TransferYesNo; - input.proposal.data.transfer.destination = ENTITY1; - input.proposal.data.transfer.amount = 1000; - input.isSubscription = true; - input.weeksPerPeriod = 4; // 4 weeks per period (monthly) - input.numberOfPeriods = 14; // 14 * 4 = 56 epochs > 52 max - input.startEpoch = system.epoch + 1; - input.amountPerPeriod = 1000; - - auto output = test.setProposal(PROPOSER1, input); - EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Try with valid number (12 months = 48 epochs < 52) - input.numberOfPeriods = 12; - output = test.setProposal(PROPOSER1, input); - EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); -} - -TEST(ContractCCF, SubscriptionExpiration) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Create weekly subscription with 3 periods - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 500, 3, 1, 189); // 1 week per period (weekly) - - test.voteMultipleComputors(proposalIndex, 200, 350); - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - sint64 initialBalance = getBalance(ENTITY1); - - // Activate and process payments - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // Process first payment (epoch 190) - system.epoch = 190; - test.beginEpoch(); - test.endEpoch(); - - // Process second payment (epoch 191) - system.epoch = 191; - test.beginEpoch(); - test.endEpoch(); - - sint64 balanceAfter3Payments = getBalance(ENTITY1); - EXPECT_GE(balanceAfter3Payments, initialBalance + 500 + 500 + 500); - - // Move to epoch 192 - subscription should be expired, no more payments - system.epoch = 192; - test.beginEpoch(); - test.endEpoch(); - - sint64 balanceAfterExpiration = getBalance(ENTITY1); - EXPECT_EQ(balanceAfterExpiration, balanceAfter3Payments); // No new payment -} - -TEST(ContractCCF, GetProposalIndices) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - // Create multiple proposals - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - id PROPOSER2 = broadcastedComputors.computors.publicKeys[1]; - increaseEnergy(PROPOSER2, 1000000); - uint32 proposalIndex1 = test.setupRegularProposal(PROPOSER1, ENTITY1, 1000); - EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); - uint32 proposalIndex2 = test.setupRegularProposal(PROPOSER2, ENTITY2, 2000); - EXPECT_NE((int)proposalIndex2, (int)INVALID_PROPOSAL_INDEX); - - auto output = test.getProposalIndices(true, -1); - - EXPECT_GE((int)output.numOfIndices, 2); - bool found1 = false, found2 = false; - for (uint32 i = 0; i < output.numOfIndices; ++i) - { - if (output.indices.get(i) == proposalIndex1) - found1 = true; - if (output.indices.get(i) == proposalIndex2) - found2 = true; - } - EXPECT_TRUE(found1); - EXPECT_TRUE(found2); -} - -TEST(ContractCCF, SubscriptionSlotReuse) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Create and cancel a subscription - uint32 proposalIndex1 = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); - - // Cancel it - increaseEnergy(PROPOSER1, 1000000); - - CCF::SetProposal_input cancelInput; - setMemory(cancelInput, 0); - cancelInput.proposal.epoch = 0; - cancelInput.proposal.type = ProposalTypes::TransferYesNo; - cancelInput.proposal.data.transfer.destination = ENTITY1; - cancelInput.proposal.data.transfer.amount = 1000; - cancelInput.weeksPerPeriod = 1; // 1 week per period (weekly) - cancelInput.numberOfPeriods = 4; - cancelInput.startEpoch = 189; - cancelInput.amountPerPeriod = 1000; - cancelInput.isSubscription = true; - auto cancelOutput = test.setProposal(PROPOSER1, cancelInput); - EXPECT_NE((int)cancelOutput.proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Create a new subscription - should reuse the slot - increaseEnergy(PROPOSER1, 1000000); - - uint32 proposalIndex2 = test.setupSubscriptionProposal( - PROPOSER1, ENTITY2, 2000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_EQ((int)proposalIndex2, (int)proposalIndex1); - - // Vote in the new subscription proposal to activate it - test.voteMultipleComputors(proposalIndex2, 200, 400); - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Check that subscription was updated (identified by destination) - auto state = test.getState(); - EXPECT_EQ(state->countActiveSubscriptions(), 1u); - EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY2), 2000); // New subscription for ENTITY2 -} - -TEST(ContractCCF, CancelSubscriptionByZeroAmount) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Create and activate a subscription - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Vote to approve - test.voteMultipleComputors(proposalIndex, 200, 350); - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Move to start epoch to activate - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // Verify subscription is active - auto state = test.getState(); - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY1), 1000); - - // Propose cancellation by setting amountPerPeriod to 0 - increaseEnergy(PROPOSER1, 1000000); - CCF::SetProposal_input cancelInput; - setMemory(cancelInput, 0); - cancelInput.proposal.epoch = system.epoch; - cancelInput.proposal.type = ProposalTypes::TransferYesNo; - cancelInput.proposal.data.transfer.destination = ENTITY1; - cancelInput.proposal.data.transfer.amount = 0; - cancelInput.isSubscription = true; - cancelInput.weeksPerPeriod = 1; - cancelInput.numberOfPeriods = 4; - cancelInput.startEpoch = system.epoch + 1; - cancelInput.amountPerPeriod = 0; // Zero amount will cancel subscription - - uint32 cancelProposalIndex = test.setProposal(PROPOSER1, cancelInput).proposalIndex; - EXPECT_NE((int)cancelProposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Vote to approve cancellation - test.voteMultipleComputors(cancelProposalIndex, 200, 350); - // Increase energy for contract - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Verify subscription was deleted - state = test.getState(); - EXPECT_FALSE(state->hasActiveSubscription(ENTITY1)); - EXPECT_EQ(state->countActiveSubscriptions(), 0u); -} - -TEST(ContractCCF, CancelSubscriptionByZeroPeriods) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Create and activate a subscription - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Vote to approve - test.voteMultipleComputors(proposalIndex, 200, 350); - // Increase energy for contract to pay for the proposal - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Move to start epoch to activate - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // Verify subscription is active - auto state = test.getState(); - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - - // Propose cancellation by setting numberOfPeriods to 0 - increaseEnergy(PROPOSER1, 1000000); - CCF::SetProposal_input cancelInput; - setMemory(cancelInput, 0); - cancelInput.proposal.epoch = system.epoch; - cancelInput.proposal.type = ProposalTypes::TransferYesNo; - cancelInput.proposal.data.transfer.destination = ENTITY1; - cancelInput.proposal.data.transfer.amount = 0; - cancelInput.isSubscription = true; - cancelInput.weeksPerPeriod = 1; - cancelInput.numberOfPeriods = 0; // Zero periods will cancel subscription - cancelInput.startEpoch = system.epoch + 1; - cancelInput.amountPerPeriod = 1000; - - uint32 cancelProposalIndex = test.setProposal(PROPOSER1, cancelInput).proposalIndex; - EXPECT_NE((int)cancelProposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Vote to approve cancellation - test.voteMultipleComputors(cancelProposalIndex, 200, 350); - // Increase energy for contract - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Verify subscription was deleted - state = test.getState(); - EXPECT_FALSE(state->hasActiveSubscription(ENTITY1)); - EXPECT_EQ(state->countActiveSubscriptions(), 0u); -} - -TEST(ContractCCF, SubscriptionWithDifferentWeeksPerPeriod) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - - // Create subscription with 2 weeks per period - uint32 proposalIndex = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 6, 2, 189); // 2 weeks per period, 6 periods - EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); - - // Verify proposal data - auto state = test.getState(); - EXPECT_EQ(state->getSubscriptionWeeksPerPeriodByProposer(PROPOSER1), 2u); - EXPECT_EQ(state->getSubscriptionNumberOfPeriodsByProposer(PROPOSER1), 6u); - - // Vote to approve - test.voteMultipleComputors(proposalIndex, 200, 350); - // Increase energy for contract - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - // Still period -1, no payment yet - EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), -1); - - // Move to start epoch - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // period 0, it is the first payment period. - EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), 0); - - // Verify subscription is active with correct weeksPerPeriod - state = test.getState(); - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - EXPECT_EQ(state->getSubscriptionWeeksPerPeriod(ENTITY1), 2u); - EXPECT_EQ(state->getSubscriptionNumberOfPeriods(ENTITY1), 6u); - - system.epoch = 190; - test.beginEpoch(); - test.endEpoch(); - - system.epoch = 191; - test.beginEpoch(); - test.endEpoch(); - - // period 1, it is the second payment period. - EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), 1); - - sint64 balance = getBalance(ENTITY1); - EXPECT_GE(balance, 1000); // Payment was made -} - -TEST(ContractCCF, SubscriptionOverwriteByDestination) -{ - ContractTestingCCF test; - system.epoch = 188; - test.beginEpoch(); - - id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; - increaseEnergy(PROPOSER1, 1000000); - id PROPOSER2 = broadcastedComputors.computors.publicKeys[1]; - increaseEnergy(PROPOSER2, 1000000); - - // PROPOSER1 creates subscription for ENTITY1 - uint32 proposalIndex1 = test.setupSubscriptionProposal( - PROPOSER1, ENTITY1, 1000, 4, 1, 189); - EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); - - // Vote and activate - test.voteMultipleComputors(proposalIndex1, 200, 350); - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - system.epoch = 189; - test.beginEpoch(); - test.endEpoch(); - - // Verify first subscription is active - auto state = test.getState(); - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY1), 1000); - - // PROPOSER2 creates a new subscription proposal for the same destination - // This should overwrite the existing subscription when accepted - uint32 proposalIndex2 = test.setupSubscriptionProposal( - PROPOSER2, ENTITY1, 2000, 6, 2, system.epoch + 1); // Different amount and schedule - EXPECT_NE((int)proposalIndex2, (int)INVALID_PROPOSAL_INDEX); - - // Vote and activate the new subscription - test.voteMultipleComputors(proposalIndex2, 200, 350); - increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); - test.endEpoch(); - - system.epoch = 190; - test.beginEpoch(); - test.endEpoch(); - - // Verify the subscription was overwritten - state = test.getState(); - EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); - EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY1), 2000); // New amount - EXPECT_EQ(state->getSubscriptionWeeksPerPeriod(ENTITY1), 2u); // New schedule - EXPECT_EQ(state->getSubscriptionNumberOfPeriods(ENTITY1), 6u); // New number of periods - EXPECT_EQ(state->countActiveSubscriptions(), 1u); // Still only one subscription per destination -} diff --git a/test/contract_core.cpp b/test/contract_core.cpp deleted file mode 100644 index 5a4e217ac..000000000 --- a/test/contract_core.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#define TRACK_MAX_STACK_BUFFER_SIZE -#include "../src/contract_core/stack_buffer.h" -#include "../src/contract_core/contract_action_tracker.h" - -TEST(TestCoreContractCore, StackBuffer) -{ - StackBuffer s1; - s1.init(); - EXPECT_EQ(s1.capacity(), 120); - EXPECT_EQ(s1.size(), 0); - EXPECT_EQ(s1.maxSizeObserved(), 0); - EXPECT_EQ(s1.failedAllocAttempts(), 0); - EXPECT_FALSE(s1.free()); - - EXPECT_NE(s1.allocate(70), nullptr); // success - EXPECT_EQ(s1.allocate(50), nullptr); // fail - EXPECT_EQ(s1.allocate(100), nullptr); // fail - EXPECT_EQ(s1.allocate(255), nullptr); // fail - EXPECT_EQ(s1.size(), 71); - EXPECT_TRUE(s1.free()); - EXPECT_EQ(s1.size(), 0); - EXPECT_EQ(s1.maxSizeObserved(), 71); - EXPECT_EQ(s1.failedAllocAttempts(), 3); - - EXPECT_NE(s1.allocate(10), nullptr); // success - EXPECT_NE(s1.allocate(20), nullptr); // success - EXPECT_NE(s1.allocate(30), nullptr); // success - EXPECT_NE(s1.allocate(40), nullptr); // success - EXPECT_EQ(s1.size(), 104); - EXPECT_TRUE(s1.free()); - EXPECT_EQ(s1.size(), 63); - EXPECT_NE(s1.allocate(20), nullptr); // success - EXPECT_EQ(s1.size(), 84); - EXPECT_EQ(s1.allocate(50), nullptr); // fail - EXPECT_EQ(s1.allocate(100), nullptr); // fail - EXPECT_EQ(s1.allocate(255), nullptr); // fail - EXPECT_TRUE(s1.free()); - EXPECT_EQ(s1.size(), 63); - EXPECT_TRUE(s1.free()); - EXPECT_EQ(s1.size(), 32); - EXPECT_TRUE(s1.free()); - EXPECT_EQ(s1.size(), 11); - EXPECT_TRUE(s1.free()); - EXPECT_EQ(s1.size(), 0); - EXPECT_EQ(s1.maxSizeObserved(), 104); - EXPECT_EQ(s1.failedAllocAttempts(), 6); - EXPECT_FALSE(s1.free()); - - char* p; - EXPECT_NE(p = s1.allocate(70), nullptr); // success - *p = 100; - EXPECT_EQ(*p, 100); - EXPECT_NE(p = s1.allocate(40), nullptr); // success - *p = 20; - EXPECT_EQ(*p, 20); - EXPECT_EQ(s1.size(), 112); - s1.freeAll(); - EXPECT_EQ(s1.size(), 0); - EXPECT_EQ(s1.maxSizeObserved(), 112); - EXPECT_EQ(s1.failedAllocAttempts(), 6); - - char* ptr; - unsigned char sz; - bool special; - EXPECT_FALSE(s1.unwind(ptr, sz, special)); - - EXPECT_NE(p = s1.allocate(112, false), nullptr); // success - EXPECT_EQ(s1.size(), 113); - EXPECT_EQ(s1.maxSizeObserved(), 113); - EXPECT_TRUE(s1.unwind(ptr, sz, special)); - EXPECT_EQ(sz, 112); - EXPECT_EQ(ptr, p); - EXPECT_EQ(special, false); - - EXPECT_EQ(s1.size(), 0); - EXPECT_EQ(s1.maxSizeObserved(), 113); - EXPECT_FALSE(s1.unwind(ptr, sz, special)); - - EXPECT_EQ(p = s1.allocate(120, true), nullptr); // fail - EXPECT_EQ(s1.failedAllocAttempts(), 7); - - EXPECT_NE(p = s1.allocate(119, true), nullptr); // success - EXPECT_EQ(s1.size(), 120); - EXPECT_EQ(s1.maxSizeObserved(), 120); - EXPECT_TRUE(s1.unwind(ptr, sz, special)); - EXPECT_EQ(sz, 119); - EXPECT_EQ(ptr, p); - EXPECT_EQ(special, true); - - StackBuffer s2; - s2.init(); - EXPECT_EQ(s2.capacity(), 128000); - EXPECT_EQ(s2.size(), 0); - EXPECT_EQ(s2.maxSizeObserved(), 0); - EXPECT_EQ(s2.failedAllocAttempts(), 0); - EXPECT_FALSE(s2.free()); - - constexpr int depth = 10; - unsigned int sz2; - char* ptrArray[depth]; - for (int i = 0; i < depth; ++i) - { - EXPECT_NE(ptrArray[i] = s2.allocate(i * 1000, i % 3 == 0), nullptr); // success - } - for (int i = depth - 1; i >= 0; --i) - { - EXPECT_TRUE(s2.unwind(ptr, sz2, special)); - EXPECT_EQ(sz2, i * 1000); - EXPECT_EQ(ptr, ptrArray[i]); - EXPECT_EQ(special, i % 3 == 0); - } -} - -TEST(TestCoreContractCore, ContractActionTracker) -{ - m256i id0(0, 1, 2, 3); - m256i id1(2, 3, 4, 5); - m256i id2(3, 4, 5, 6); - - ContractActionTracker<6> at; - EXPECT_TRUE(at.allocBuffer()); - at.init(); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), 0); - - EXPECT_TRUE(at.addQuTransfer(id0, id1, 100)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), -100); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 100); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 0); - - EXPECT_TRUE(at.addQuTransfer(id1, id0, 100)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), 0); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 0); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 0); - - EXPECT_TRUE(at.addQuTransfer(id0, id1, 1000)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), -1000); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 1000); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 0); - - EXPECT_TRUE(at.addQuTransfer(id1, id2, 800)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), -1000); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 800); - - EXPECT_TRUE(at.addQuTransfer(id2, id0, 500)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), -500); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 300); - - // Transfer to own address does not change anything - EXPECT_TRUE(at.addQuTransfer(id0, id0, 10000)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), -500); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 300); - - // Fails because action cannot be stored - EXPECT_FALSE(at.addQuTransfer(id2, id0, 500)); - EXPECT_EQ(at.getOverallQuTransferBalance(id0), -500); - EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); - EXPECT_EQ(at.getOverallQuTransferBalance(id2), 300); - - at.freeBuffer(); -} diff --git a/test/contract_gqmprop.cpp b/test/contract_gqmprop.cpp deleted file mode 100644 index bf0854b3b..000000000 --- a/test/contract_gqmprop.cpp +++ /dev/null @@ -1,537 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -#define PRINT_DETAILS 0 - -class GQmPropChecker : public GQMPROP -{ -public: - void checkRevenueDonations( - std::vector* expectedOrder = nullptr, - std::vector* expectedEntries = nullptr, - bool printTable = PRINT_DETAILS) - { - const GQMPROP::RevenueDonationT& revDon = this->revenueDonation; - const GQMPROP::RevenueDonationEntry* cur = nullptr; - const GQMPROP::RevenueDonationEntry* prev = nullptr; - uint64 idxRD = 0; - - if (printTable) - { - std::cout << "Revenue donations table (epoch " << system.epoch << "):" << std::endl; - for (idxRD = 0; idxRD < revDon.capacity(); ++idxRD) - { - cur = &revDon.get(idxRD); - if (isZero(cur->destinationPublicKey)) - break; - std::cout << "- " << idxRD << ": ID " << cur->destinationPublicKey << ", epoch " << cur->firstEpoch << ", amount " << float(cur->millionthAmount) / 1000000.f << std::endl; - } - } - - // check the used part of the table - std::set prevIds; - uint64 idxO = 0; - for (idxRD = 0; idxRD < revDon.capacity(); ++idxRD) - { - cur = &revDon.get(idxRD); - if (isZero(cur->destinationPublicKey)) - { - // done with checking used part of the table - break; - } - if (idxRD > 0) - { - if (cur->destinationPublicKey == prev->destinationPublicKey) - { - // entries of same ID - // -> check order - EXPECT_LT((int)prev->firstEpoch, (int)cur->firstEpoch); - // -> check that we don't have two non-future entries - if (prev->firstEpoch < system.epoch) - { - EXPECT_GE((int)cur->firstEpoch, (int)system.epoch); - } - } - else - { - // next ID - // -> check that we haven't seen it before - EXPECT_EQ(prevIds.find(cur->destinationPublicKey), prevIds.end()); - ++idxO; - } - } - - EXPECT_GE(cur->millionthAmount, 0); - EXPECT_LE(cur->millionthAmount, 1000000); - - if (expectedOrder) - { - EXPECT_LT(idxO, expectedOrder->size()); - EXPECT_EQ(cur->destinationPublicKey, expectedOrder->at(idxO)); - } - if (expectedEntries) - { - EXPECT_LT(idxRD, expectedEntries->size()); - EXPECT_EQ(cur->destinationPublicKey, expectedEntries->at(idxRD).destinationPublicKey); - EXPECT_EQ((int)cur->firstEpoch, (int)expectedEntries->at(idxRD).firstEpoch); - EXPECT_EQ(cur->millionthAmount, expectedEntries->at(idxRD).millionthAmount); - } - - // update prev data for later checks - prev = cur; - prevIds.insert(cur->destinationPublicKey); - } - - // check that all remaining entries are 0 - for (; idxRD < revDon.capacity(); ++idxRD) - { - EXPECT_TRUE(isZero(revDon.get(idxRD).destinationPublicKey)); - EXPECT_EQ((int)revDon.get(idxRD).firstEpoch, 0); - EXPECT_EQ(revDon.get(idxRD).millionthAmount, 0); - } - } -}; - - -class ContractTestingGQmProp : protected ContractTesting -{ -public: - ContractTestingGQmProp() - { - initEmptySpectrum(); - INIT_CONTRACT(GQMPROP); - callSystemProcedure(GQMPROP_CONTRACT_INDEX, INITIALIZE); - - // Setup computors - for (unsigned long long i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - broadcastedComputors.computors.publicKeys[i] = id(i, 1, 2, 3); - increaseEnergy(id(i, 1, 2, 3), 1000000); - } - } - - GQmPropChecker* getState() - { - return (GQmPropChecker*)contractStates[GQMPROP_CONTRACT_INDEX]; - } - - GQMPROP::GetProposal_output getProposal(uint16 proposalIndex) - { - GQMPROP::GetProposal_input input{ proposalIndex }; - GQMPROP::GetProposal_output output; - callFunction(GQMPROP_CONTRACT_INDEX, 2, input, output); - return output; - } - - GQMPROP::GetVotingResults_output getResults(uint16 proposalIndex) - { - GQMPROP::GetVotingResults_input input{ proposalIndex }; - GQMPROP::GetVotingResults_output output; - callFunction(GQMPROP_CONTRACT_INDEX, 4, input, output); - return output; - } - - uint16 setProposal(const id& originator, const GQMPROP::ProposalDataT& proposalData) - { - GQMPROP::SetProposal_input input = proposalData; - GQMPROP::SetProposal_output output; - invokeUserProcedure(GQMPROP_CONTRACT_INDEX, 1, input, output, originator, 0); - return output.proposalIndex; - } - - bool vote(const id& originator, uint16 proposalIndex, const GQMPROP::ProposalDataT& proposalData, sint64 voteValue) - { - GQMPROP::Vote_input input{proposalIndex, proposalData.type, proposalData.tick, voteValue}; - GQMPROP::Vote_output output; - invokeUserProcedure(GQMPROP_CONTRACT_INDEX, 2, input, output, originator, 0); - return output.okay; - } - - // TODO: add other procedures - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(GQMPROP_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void voteMultipleComputors(uint16 proposalIndex, uint16 votesNo, uint16 votesYes1, uint16 votesYes2 = 0) - { - EXPECT_LE(int(votesNo + votesYes1 + votesYes2), NUMBER_OF_COMPUTORS); - const auto proposal = getProposal(proposalIndex); - EXPECT_TRUE(proposal.okay); - uint16 compIdx = 0; - for (uint16 i = 0; i < votesNo; ++i, ++compIdx) - EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), proposalIndex, proposal.proposal, 0)); - for (uint16 i = 0; i < votesYes1; ++i, ++compIdx) - EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), proposalIndex, proposal.proposal, 1)); - for (uint16 i = 0; i < votesYes2; ++i, ++compIdx) - EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), proposalIndex, proposal.proposal, 2)); - auto results = getResults(proposalIndex); - EXPECT_TRUE(results.okay); - EXPECT_EQ(results.results.optionVoteCount.get(0), uint32(votesNo)); - EXPECT_EQ(results.results.optionVoteCount.get(1), uint32(votesYes1)); - EXPECT_EQ(results.results.optionVoteCount.get(2), uint32(votesYes2)); - } - - uint16 setupProposal(uint16 proposer, uint16 type, const id& dest, sint64 amount = 0, uint16 targetEpoch = 0, bool expectSuccess = true) - { - GQMPROP::ProposalDataT proposal; - setMemory(proposal, 0); - proposal.epoch = system.epoch; - proposal.type = type; - switch (ProposalTypes::cls(type)) - { - case ProposalTypes::Class::Transfer: - { - const auto amountCount = ProposalTypes::optionCount(type) - 1; - for (int i = 0; i < amountCount; ++i) - proposal.data.transfer.amounts.set(i, amount + i); - proposal.data.transfer.destination = dest; - break; - } - case ProposalTypes::Class::TransferInEpoch: - proposal.data.transferInEpoch.amount = amount; - proposal.data.transferInEpoch.destination = dest; - proposal.data.transferInEpoch.targetEpoch = targetEpoch; - break; - } - uint16 proposalIdx = setProposal(id(proposer, 1, 2, 3), proposal); - if (expectSuccess) - EXPECT_NE((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); - else - EXPECT_EQ((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); - return proposalIdx; - } -}; - -static id ENTITY0(7, 0, 0, 0); -static id ENTITY1(100, 0, 0, 0); -static id ENTITY2(123, 456, 789, 0); -static id ENTITY3(42, 69, 0, 13); -static id ENTITY4(3, 14, 2, 7); - -TEST(ContractGQmProp, RevDonation) -{ - ContractTestingGQmProp test; - uint16 proposalIndex; - std::vector revDonationOrder; - std::vector revDonationEntries; - - // rev donation from INITALIZE - revDonationOrder.push_back(ENTITY0); - revDonationEntries = { {ENTITY0, 150000, 123} }; - - //------------------------------------------------------------- - // epoch 200 - system.epoch = 200; - test.beginEpoch(); - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // one successful and several unsuccessful proposals (testing insert at end) - proposalIndex = test.setupProposal(0, ProposalTypes::TransferYesNo, ENTITY1, 1000); - test.voteMultipleComputors(proposalIndex, 200, 350); - revDonationOrder.push_back(ENTITY1); - - proposalIndex = test.setupProposal(1, ProposalTypes::TransferYesNo, ENTITY2, 10000); - test.voteMultipleComputors(proposalIndex, 100, 200); // total votes < QUORUM - - proposalIndex = test.setupProposal(2, ProposalTypes::TransferYesNo, ENTITY2, 20000); - test.voteMultipleComputors(proposalIndex, 379, 256); // most noted is "no" - - proposalIndex = test.setupProposal(3, ProposalTypes::TransferThreeAmounts, ENTITY2, 30000); - test.voteMultipleComputors(proposalIndex, 10, 20, 400); // total votes < QUORUM - - proposalIndex = test.setupProposal(4, ProposalTypes::TransferThreeAmounts, ENTITY2, 40000); - test.voteMultipleComputors(proposalIndex, 201, 202, 203); // max voted < QUORUM/2 - - //------------------------------------------------------------- - // epoch 201 - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { {ENTITY0, 150000, 123}, {ENTITY1, 1000, 201} }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // add future revenue donations (testing insert at end and in the middle) - proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY2, 2000, 205); - test.voteMultipleComputors(proposalIndex, 0, 600); - revDonationOrder.push_back(ENTITY2); - - proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY1, 3000, 204); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY1, 4000, 203); - test.voteMultipleComputors(proposalIndex, 300, 350); - - proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY1, 5000, 205); - test.voteMultipleComputors(proposalIndex, 300, 350); - - //------------------------------------------------------------- - // epoch 202 - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { {ENTITY0, 150000, 123}, {ENTITY1, 1000, 201}, {ENTITY1, 4000, 203}, {ENTITY1, 3000, 204}, {ENTITY1, 5000, 205}, {ENTITY2, 2000, 205} }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - //------------------------------------------------------------- - // epoch 203 -> {ENTITY1, 1000, 201} is cleaned up, because {ENTITY1, 4000, 203} is in action now - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 150000, 123}, - {ENTITY1, 4000, 203}, {ENTITY1, 3000, 204}, {ENTITY1, 5000, 205}, - {ENTITY2, 2000, 205} - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // add more dontaions - proposalIndex = test.setupProposal(1, ProposalTypes::TransferThreeAmounts, ENTITY4, 6000); - test.voteMultipleComputors(proposalIndex, 100, 100, 400); // vote for second amount (6001) - revDonationOrder.push_back(ENTITY3); - - proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY3, 7000, 205); - test.voteMultipleComputors(proposalIndex, 100, 500); - revDonationOrder.push_back(ENTITY4); - - // add at front of ENTITY3 - proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY3, 8000, 204); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY3, 9000, 210); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY3, 10000, 207); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(5, ProposalTypes::TransferInEpochYesNo, ENTITY4, 11000, 220); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(6, ProposalTypes::TransferInEpochYesNo, ENTITY4, 12000, 210); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(7, ProposalTypes::TransferInEpochYesNo, ENTITY4, 13000, 208); - test.voteMultipleComputors(proposalIndex, 100, 500); - - // rejected due to non-future epoch - test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY4, 7500, 203, false); - - //------------------------------------------------------------- - // epoch 204 -> {ENTITY1, 4000, 203} is cleaned up, because {ENTITY1, 3000, 204} is in action now - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 150000, 123}, - {ENTITY1, 3000, 204}, {ENTITY1, 5000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 8000, 204}, {ENTITY3, 7000, 205}, {ENTITY3, 10000, 207}, {ENTITY3, 9000, 210}, - {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 11000, 220}, - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // update amounts (feature to overwrite/cancel scheduled changes) - proposalIndex = test.setupProposal(0, ProposalTypes::TransferYesNo, ENTITY3, 14000); // epoch 205 - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY1, 15000, 205); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY4, 16000, 220); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY3, 17000, 207); - test.voteMultipleComputors(proposalIndex, 100, 500); - - // update with higher proposal index overwrites the one with lower index (both ENTITY3 epoch 205) - proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY3, 18000, 205); - test.voteMultipleComputors(proposalIndex, 100, 500); - - //------------------------------------------------------------- - // epoch 205 -> {ENTITY1, 3000, 204} and {ENTITY3, 8000, 204} are cleaned up - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 150000, 123}, - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 18000, 205}, {ENTITY3, 17000, 207}, {ENTITY3, 9000, 210}, - {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // update amount (feature to overwrite/cancel scheduled changes) - proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY3, 19000, 210); - test.voteMultipleComputors(proposalIndex, 100, 500); - - // schedule new rev. donation for ENTITY0 - proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY0, 20000, 210); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY0, 21000, 207); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY0, 22000, 215); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY0, 23000, 208); - test.voteMultipleComputors(proposalIndex, 100, 500); - - //------------------------------------------------------------- - // epoch 206 -> nothing cleaned up (not epoch 206 donation entry gets in action) - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 150000, 123}, {ENTITY0, 21000, 207}, {ENTITY0, 23000, 208}, {ENTITY0, 20000, 210}, {ENTITY0, 22000, 215}, - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 18000, 205}, {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, - {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - //------------------------------------------------------------- - // epoch 207 -> entries from ENTITY0 and ENTITY3 cleaned up - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 21000, 207}, {ENTITY0, 23000, 208}, {ENTITY0, 20000, 210}, {ENTITY0, 22000, 215}, - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, - {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - //------------------------------------------------------------- - // epoch 208 -> entries from ENTITY0 and ENTITY4 cleaned up - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 23000, 208}, {ENTITY0, 20000, 210}, {ENTITY0, 22000, 215}, - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, - {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // fill up the revenue donation table storage completely (2 of 10 are updated, 118 are added, 10 cannot be added - // due to lack of storage) - revDonationEntries = { {ENTITY0, 23000, 208} }; - for (unsigned int i = 0; i < 130; ++i) - { - proposalIndex = test.setupProposal(i, ProposalTypes::TransferInEpochYesNo, ENTITY0, 30000 + i, 210 + i); - test.voteMultipleComputors(proposalIndex, 100, 500); - if (i < 120) - revDonationEntries.push_back({ ENTITY0, 30000 + i, uint16(210 + i) }); - } - revDonationEntries.insert(revDonationEntries.end(), { - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, - {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220} }); - EXPECT_EQ(revDonationEntries.size(), 128); - - //------------------------------------------------------------- - // epoch 209 -> no entries cleaned up - ++system.epoch; - test.beginEpoch(); - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - //------------------------------------------------------------- - // epoch 210 -> 3 entries cleaned up (ENTITY0, ENTITY3, ENTITY4) - ++system.epoch; - test.beginEpoch(); - - revDonationEntries.clear(); - for (unsigned int i = 0; i < 120; ++i) - { - revDonationEntries.push_back({ ENTITY0, 30000 + i, uint16(210 + i) }); - } - revDonationEntries.insert(revDonationEntries.end(), { - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 19000, 210}, - {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220} }); - - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - - //------------------------------------------------------------- - // epoch 211 -> entry removed from ENTITY0 (first entry) - ++system.epoch; - test.beginEpoch(); - revDonationEntries.erase(revDonationEntries.begin()); - EXPECT_EQ(revDonationEntries.size(), 124); - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - // fill up storage at the end - proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY4, 40000, 213); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY4, 41000, 225); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY4, 42000, 215); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY4, 43000, 214); - test.voteMultipleComputors(proposalIndex, 100, 500); - - // will fail due to storage limitation - proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY4, 44000, 230); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(5, ProposalTypes::TransferInEpochYesNo, ENTITY4, 44000, 212); - test.voteMultipleComputors(proposalIndex, 100, 500); - - revDonationEntries.erase(revDonationEntries.end() - 1); - revDonationEntries.insert(revDonationEntries.end(), { - {ENTITY4, 40000, 213}, {ENTITY4, 43000, 214}, {ENTITY4, 42000, 215}, {ENTITY4, 16000, 220}, {ENTITY4, 41000, 225}, - }); - - //------------------------------------------------------------- - // epoch 212 -> entry removed from ENTITY0 (first entry) - ++system.epoch; - test.beginEpoch(); - revDonationEntries.erase(revDonationEntries.begin()); - EXPECT_EQ(revDonationEntries.size(), 127); - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); - - //------------------------------------------------------------- - // run 120 epochs reducing table back to one entry per entity - revDonationEntries = { - {ENTITY0, 30119, 329}, - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 19000, 210}, - {ENTITY4, 41000, 225}, - }; - for (unsigned int i = 0; i < 120; ++i) - { - ++system.epoch; - test.beginEpoch(); - if (i == 119) - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries, true); - else - test.getState()->checkRevenueDonations(&revDonationOrder, nullptr, false); - } - - //------------------------------------------------------------- - // test that IDs are not removed even if donation percentage is 0 - proposalIndex = test.setupProposal(0, ProposalTypes::TransferYesNo, ENTITY0, 0); - test.voteMultipleComputors(proposalIndex, 100, 500); - - proposalIndex = test.setupProposal(1, ProposalTypes::TransferYesNo, ENTITY3, 0); - test.voteMultipleComputors(proposalIndex, 100, 500); - - ++system.epoch; - test.beginEpoch(); - revDonationEntries = { - {ENTITY0, 0, 333}, - {ENTITY1, 15000, 205}, - {ENTITY2, 2000, 205}, - {ENTITY3, 0, 333}, - {ENTITY4, 41000, 225}, - }; - test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); -} diff --git a/test/contract_msvault.cpp b/test/contract_msvault.cpp deleted file mode 100644 index e37260230..000000000 --- a/test/contract_msvault.cpp +++ /dev/null @@ -1,1258 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" -#include "test_util.h" - -static const id OWNER1 = ID(_T, _K, _U, _W, _W, _S, _N, _B, _A, _E, _G, _W, _J, _H, _Q, _J, _D, _F, _L, _G, _Q, _H, _J, _J, _C, _J, _B, _A, _X, _B, _S, _Q, _M, _Q, _A, _Z, _J, _J, _D, _Y, _X, _E, _P, _B, _V, _B, _B, _L, _I, _Q, _A, _N, _J, _T, _I, _D); -static const id OWNER2 = ID(_F, _X, _J, _F, _B, _T, _J, _M, _Y, _F, _J, _H, _P, _B, _X, _C, _D, _Q, _T, _L, _Y, _U, _K, _G, _M, _H, _B, _B, _Z, _A, _A, _F, _T, _I, _C, _W, _U, _K, _R, _B, _M, _E, _K, _Y, _N, _U, _P, _M, _R, _M, _B, _D, _N, _D, _R, _G); -static const id OWNER3 = ID(_K, _E, _F, _D, _Z, _T, _Y, _L, _F, _E, _R, _A, _H, _D, _V, _L, _N, _Q, _O, _R, _D, _H, _F, _Q, _I, _B, _S, _B, _Z, _C, _W, _S, _Z, _X, _Z, _F, _F, _A, _N, _O, _T, _F, _A, _H, _W, _M, _O, _V, _G, _T, _R, _Q, _J, _P, _X, _D); -static const id TEST_VAULT_NAME = ID(_M, _Y, _M, _S, _V, _A, _U, _L, _U, _S, _E, _D, _F, _O, _R, _U, _N, _I, _T, _T, _T, _E, _S, _T, _I, _N, _G, _P, _U, _R, _P, _O, _S, _E, _S, _O, _N, _L, _Y, _U, _N, _I, _T, _T, _E, _S, _C, _O, _R, _E, _S, _M, _A, _R, _T, _T); - -static constexpr uint64 TWO_OF_TWO = 2ULL; -static constexpr uint64 TWO_OF_THREE = 2ULL; - -static const id DESTINATION = id::randomValue(); -static constexpr uint64 QX_ISSUE_ASSET_FEE = 1000000000ull; -static constexpr uint64 QX_MANAGEMENT_TRANSFER_FEE = 100ull; - -class ContractTestingMsVault : protected ContractTesting -{ -public: - ContractTestingMsVault() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(MSVAULT); - callSystemProcedure(MSVAULT_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(MSVAULT_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(MSVAULT_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - MSVAULT::registerVault_output registerVault(uint64 requiredApprovals, id vaultName, const std::vector& owners, uint64 fee) - { - MSVAULT::registerVault_input input; - for (uint64 i = 0; i < MSVAULT_MAX_OWNERS; i++) - { - input.owners.set(i, (i < owners.size()) ? owners[i] : NULL_ID); - } - input.requiredApprovals = requiredApprovals; - input.vaultName = vaultName; - MSVAULT::registerVault_output regOut; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 1, input, regOut, owners[0], fee); - return regOut; - } - - MSVAULT::deposit_output deposit(uint64 vaultId, uint64 amount, const id& from) - { - MSVAULT::deposit_input input; - input.vaultId = vaultId; - increaseEnergy(from, amount); - MSVAULT::deposit_output depOut; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 2, input, depOut, from, amount); - return depOut; - } - - MSVAULT::releaseTo_output releaseTo(uint64 vaultId, uint64 amount, const id& destination, const id& owner, uint64 fee = MSVAULT_RELEASE_FEE) - { - MSVAULT::releaseTo_input input; - input.vaultId = vaultId; - input.amount = amount; - input.destination = destination; - - increaseEnergy(owner, fee); - MSVAULT::releaseTo_output relOut; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 3, input, relOut, owner, fee); - return relOut; - } - - MSVAULT::resetRelease_output resetRelease(uint64 vaultId, const id& owner, uint64 fee = MSVAULT_RELEASE_RESET_FEE) - { - MSVAULT::resetRelease_input input; - input.vaultId = vaultId; - - increaseEnergy(owner, fee); - MSVAULT::resetRelease_output rstOut; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 4, input, rstOut, owner, fee); - return rstOut; - } - - MSVAULT::getVaultName_output getVaultName(uint64 vaultId) const - { - MSVAULT::getVaultName_input input; - MSVAULT::getVaultName_output output; - input.vaultId = vaultId; - callFunction(MSVAULT_CONTRACT_INDEX, 8, input, output); - return output; - } - - MSVAULT::getVaults_output getVaults(const id& pubKey) const - { - MSVAULT::getVaults_input input; - MSVAULT::getVaults_output output; - input.publicKey = pubKey; - callFunction(MSVAULT_CONTRACT_INDEX, 5, input, output); - return output; - } - - MSVAULT::getBalanceOf_output getBalanceOf(uint64 vaultId) const - { - MSVAULT::getBalanceOf_input input; - MSVAULT::getBalanceOf_output output; - input.vaultId = vaultId; - callFunction(MSVAULT_CONTRACT_INDEX, 7, input, output); - return output; - } - - MSVAULT::getReleaseStatus_output getReleaseStatus(uint64 vaultId) const - { - MSVAULT::getReleaseStatus_input input; - MSVAULT::getReleaseStatus_output output; - input.vaultId = vaultId; - callFunction(MSVAULT_CONTRACT_INDEX, 6, input, output); - return output; - } - - MSVAULT::getRevenueInfo_output getRevenueInfo() const - { - MSVAULT::getRevenueInfo_input input; - MSVAULT::getRevenueInfo_output output; - callFunction(MSVAULT_CONTRACT_INDEX, 9, input, output); - return output; - } - - // Helper: find newly created vault by difference - uint64 findNewvaultIdAfterRegister(const id& owner, uint64 prevCount) - { - auto vaultsAfter = getVaults(owner); - if (vaultsAfter.numberOfVaults == prevCount + 1) - { - return (uint64)vaultsAfter.vaultIds.get(prevCount); - } - return -1; - } - - void issueAsset(const id& issuer, const std::string& assetNameStr, sint64 numberOfShares) - { - uint64 assetName = assetNameFromString(assetNameStr.c_str()); - QX::IssueAsset_input input{ assetName, numberOfShares, 0, 0 }; - QX::IssueAsset_output output; - increaseEnergy(issuer, QX_ISSUE_ASSET_FEE); - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QX_ISSUE_ASSET_FEE); - } - - QX::TransferShareOwnershipAndPossession_output transferAsset(const id& from, const id& to, const Asset& asset, uint64_t amount) { - QX::TransferShareOwnershipAndPossession_input input; - input.issuer = asset.issuer; - input.newOwnerAndPossessor = to; - input.assetName = asset.assetName; - input.numberOfShares = amount; - QX::TransferShareOwnershipAndPossession_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, from, 1000000); - return output; - } - - int64_t transferShareManagementRights(const id& from, const Asset& asset, sint64 numberOfShares, uint32 newManagingContractIndex) - { - QX::TransferShareManagementRights_input input; - input.asset = asset; - input.numberOfShares = numberOfShares; - input.newManagingContractIndex = newManagingContractIndex; - QX::TransferShareManagementRights_output output; - output.transferredNumberOfShares = 0; - invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, from, 0); - return output.transferredNumberOfShares; - } - - MSVAULT::revokeAssetManagementRights_output revokeAssetManagementRights(const id& from, const Asset& asset, sint64 numberOfShares) - { - MSVAULT::revokeAssetManagementRights_input input; - input.asset = asset; - input.numberOfShares = numberOfShares; - MSVAULT::revokeAssetManagementRights_output output; - output.transferredNumberOfShares = 0; - output.status = 0; - - // The fee required by QX is 100. Do this to ensure enough fee. - const uint64 fee = 100; - increaseEnergy(from, fee); - - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 25, input, output, from, fee); - return output; - } - - MSVAULT::depositAsset_output depositAsset(uint64 vaultId, const Asset& asset, uint64 amount, const id& from) - { - MSVAULT::depositAsset_input input; - input.vaultId = vaultId; - input.asset = asset; - input.amount = amount; - MSVAULT::depositAsset_output output; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 19, input, output, from, 0); - return output; - } - - MSVAULT::releaseAssetTo_output releaseAssetTo(uint64 vaultId, const Asset& asset, uint64 amount, const id& destination, const id& owner, uint64 fee = MSVAULT_RELEASE_FEE) - { - MSVAULT::releaseAssetTo_input input; - input.vaultId = vaultId; - input.asset = asset; - input.amount = amount; - input.destination = destination; - - increaseEnergy(owner, fee); - MSVAULT::releaseAssetTo_output output; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 20, input, output, owner, fee); - return output; - } - - MSVAULT::resetAssetRelease_output resetAssetRelease(uint64 vaultId, const id& owner, uint64 fee = MSVAULT_RELEASE_RESET_FEE) - { - MSVAULT::resetAssetRelease_input input; - input.vaultId = vaultId; - - increaseEnergy(owner, fee); - MSVAULT::resetAssetRelease_output output; - invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 21, input, output, owner, fee); - return output; - } - - MSVAULT::getVaultAssetBalances_output getVaultAssetBalances(uint64 vaultId) const - { - MSVAULT::getVaultAssetBalances_input input; - input.vaultId = vaultId; - MSVAULT::getVaultAssetBalances_output output; - callFunction(MSVAULT_CONTRACT_INDEX, 22, input, output); - return output; - } - - MSVAULT::getAssetReleaseStatus_output getAssetReleaseStatus(uint64 vaultId) const - { - MSVAULT::getAssetReleaseStatus_input input; - input.vaultId = vaultId; - MSVAULT::getAssetReleaseStatus_output output; - callFunction(MSVAULT_CONTRACT_INDEX, 23, input, output); - return output; - } - - ~ContractTestingMsVault() - { - } -}; - - -TEST(ContractMsVault, RegisterVault_InsufficientFee) -{ - ContractTestingMsVault msVault; - - // Check how many vaults OWNER1 has initially - auto vaultsO1Before = msVault.getVaults(OWNER1); - - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - - // Attempt with insufficient fee - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, 5000ULL); - EXPECT_EQ(regOut.status, 2ULL); // FAILURE_INSUFFICIENT_FEE - - // No new vault should be created - auto vaultsO1After = msVault.getVaults(OWNER1); - EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), - static_cast(vaultsO1Before.numberOfVaults)); -} - -TEST(ContractMsVault, RegisterVault_OneOwner) -{ - ContractTestingMsVault msVault; - auto vaultsO1Before = msVault.getVaults(OWNER1); - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - - // Only one owner => should fail - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 5ULL); // FAILURE_INVALID_PARAMS - - // Should fail, no new vault - auto vaultsO1After = msVault.getVaults(OWNER1); - EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), - static_cast(vaultsO1Before.numberOfVaults)); -} - -TEST(ContractMsVault, RegisterVault_Success) -{ - ContractTestingMsVault msVault; - auto vaultsO1Before = msVault.getVaults(OWNER1); - - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); // SUCCESS - - auto vaultsO1After = msVault.getVaults(OWNER1); - EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), - static_cast(vaultsO1Before.numberOfVaults + 1)); - - // Extract the new vaultId - uint64 vaultId = vaultsO1After.vaultIds.get(vaultsO1Before.numberOfVaults); - // Check vault name - auto nameOut = msVault.getVaultName(vaultId); - EXPECT_EQ(nameOut.vaultName, TEST_VAULT_NAME); - - // Check revenue info - auto revenue = msVault.getRevenueInfo(); - // At least one vault active, revenue should have increased - EXPECT_GE(revenue.numberOfActiveVaults, 1U); - EXPECT_GE(revenue.totalRevenue, MSVAULT_REGISTERING_FEE); - - auto balance = msVault.getBalanceOf(vaultId); - EXPECT_EQ(balance.balance, 0U); -} - -TEST(ContractMsVault, GetVaultName) -{ - ContractTestingMsVault msVault; - - auto vaultsO1Before = msVault.getVaults(OWNER1); - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1After = msVault.getVaults(OWNER1); - EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), - static_cast(vaultsO1Before.numberOfVaults + 1)); - uint64 vaultId = vaultsO1After.vaultIds.get(vaultsO1Before.numberOfVaults); - - auto nameOut = msVault.getVaultName(vaultId); - EXPECT_EQ(nameOut.vaultName, TEST_VAULT_NAME); - - auto invalidNameOut = msVault.getVaultName(9999ULL); - EXPECT_EQ(invalidNameOut.vaultName, NULL_ID); -} - -TEST(ContractMsVault, Deposit_InvalidVault) -{ - ContractTestingMsVault msVault; - // deposit to a non-existent vault - auto beforeBalance = msVault.getBalanceOf(999ULL); - auto depOut = msVault.deposit(999ULL, 5000ULL, OWNER1); - EXPECT_EQ(depOut.status, 3ULL); // FAILURE_INVALID_VAULT - // no change in balance - auto afterBalance = msVault.getBalanceOf(999ULL); - EXPECT_EQ(afterBalance.balance, beforeBalance.balance); -} - -TEST(ContractMsVault, Deposit_Success) -{ - ContractTestingMsVault msVault; - - auto vaultsO1Before = msVault.getVaults(OWNER1); - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1After = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1After.vaultIds.get(vaultsO1Before.numberOfVaults); - - auto balBefore = msVault.getBalanceOf(vaultId); - auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - auto balAfter = msVault.getBalanceOf(vaultId); - EXPECT_EQ(balAfter.balance, balBefore.balance + 10000ULL); -} - -TEST(ContractMsVault, ReleaseTo_NonOwner) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - auto releaseStatusBefore = msVault.getReleaseStatus(vaultId); - - // Non-owner attempt release - auto relOut = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER3); - EXPECT_EQ(relOut.status, 4ULL); // FAILURE_NOT_AUTHORIZED - auto releaseStatusAfter = msVault.getReleaseStatus(vaultId); - - // No approvals should be set - EXPECT_EQ(releaseStatusAfter.amounts.get(0), releaseStatusBefore.amounts.get(0)); - EXPECT_EQ(releaseStatusAfter.destinations.get(0), releaseStatusBefore.destinations.get(0)); -} - -TEST(ContractMsVault, ReleaseTo_InvalidParams) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); - // 2 out of 2 owners - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - auto releaseStatusBefore = msVault.getReleaseStatus(vaultId); - // amount=0 - auto relOut1 = msVault.releaseTo(vaultId, 0ULL, OWNER2, OWNER1); - EXPECT_EQ(relOut1.status, 5ULL); // FAILURE_INVALID_PARAMS - auto releaseStatusAfter1 = msVault.getReleaseStatus(vaultId); - EXPECT_EQ(releaseStatusAfter1.amounts.get(0), releaseStatusBefore.amounts.get(0)); - - // destination NULL_ID - auto relOut2 = msVault.releaseTo(vaultId, 5000ULL, NULL_ID, OWNER1); - EXPECT_EQ(relOut2.status, 5ULL); // FAILURE_INVALID_PARAMS - auto releaseStatusAfter2 = msVault.getReleaseStatus(vaultId); - EXPECT_EQ(releaseStatusAfter2.amounts.get(0), releaseStatusBefore.amounts.get(0)); -} - -TEST(ContractMsVault, ReleaseTo_PartialApproval) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, 100000000ULL); - increaseEnergy(OWNER3, 100000000ULL); - - // 2 out of 3 owners - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 15000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - auto relOut = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER1); - EXPECT_EQ(relOut.status, 9ULL); // PENDING_APPROVAL - - auto status = msVault.getReleaseStatus(vaultId); - // Partial approval means just first owner sets the request - EXPECT_EQ(status.amounts.get(0), 5000ULL); - EXPECT_EQ(status.destinations.get(0), OWNER3); -} - -TEST(ContractMsVault, ReleaseTo_FullApproval) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, 100000000ULL); - increaseEnergy(OWNER2, 100000000ULL); - increaseEnergy(OWNER3, 100000000ULL); - - // 2 out of 3 - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - // OWNER1 requests 5000 Qubics to OWNER3 - auto relOut1 = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER1); - EXPECT_EQ(relOut1.status, 9ULL); // PENDING_APPROVAL - // Not approved yet - - auto relOut2 = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER2); // second approval - EXPECT_EQ(relOut2.status, 1ULL); // SUCCESS - - // After full approval, amount should be released - auto bal = msVault.getBalanceOf(vaultId); - EXPECT_EQ(bal.balance, 10000ULL - 5000ULL); -} - -TEST(ContractMsVault, ReleaseTo_InsufficientBalance) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, 100000000ULL); - increaseEnergy(OWNER2, 100000000ULL); - increaseEnergy(OWNER3, 100000000ULL); - - // 2 out of 2 - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - auto balBefore = msVault.getBalanceOf(vaultId); - // Attempt to release more than balance - auto relOut = msVault.releaseTo(vaultId, 20000ULL, OWNER3, OWNER1); - EXPECT_EQ(relOut.status, 6ULL); // FAILURE_INSUFFICIENT_BALANCE - - // Should fail, balance no change - auto balAfter = msVault.getBalanceOf(vaultId); - EXPECT_EQ(balAfter.balance, balBefore.balance); -} - -TEST(ContractMsVault, ResetRelease_NonOwner) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, 100000000ULL); - increaseEnergy(OWNER2, 100000000ULL); - increaseEnergy(OWNER3, 100000000ULL); - - // 2 out of 2 - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 5000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - auto relOut = msVault.releaseTo(vaultId, 2000ULL, OWNER2, OWNER1); - EXPECT_EQ(relOut.status, 9ULL); // PENDING_APPROVAL - - auto statusBefore = msVault.getReleaseStatus(vaultId); - auto rstOut = msVault.resetRelease(vaultId, OWNER3); // Non owner tries to reset - EXPECT_EQ(rstOut.status, 4ULL); // FAILURE_NOT_AUTHORIZED - auto statusAfter = msVault.getReleaseStatus(vaultId); - - // No change in release requests - EXPECT_EQ(statusAfter.amounts.get(0), statusBefore.amounts.get(0)); - EXPECT_EQ(statusAfter.destinations.get(0), statusBefore.destinations.get(0)); -} - -TEST(ContractMsVault, ResetRelease_Success) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, 100000000ULL); - increaseEnergy(OWNER2, 100000000ULL); - increaseEnergy(OWNER3, 100000000ULL); - - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaultsO1 = msVault.getVaults(OWNER1); - uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); - - auto depOut = msVault.deposit(vaultId, 5000ULL, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - // OWNER2 requests a releaseTo - auto relOut = msVault.releaseTo(vaultId, 2000ULL, OWNER1, OWNER2); - EXPECT_EQ(relOut.status, 9ULL); - - // Now reset by OWNER2 - auto rstOut = msVault.resetRelease(vaultId, OWNER2); - EXPECT_EQ(rstOut.status, 1ULL); - - auto status = msVault.getReleaseStatus(vaultId); - // All cleared - for (uint16 i = 0; i < 3; i++) - { - EXPECT_EQ(status.amounts.get(i), 0ULL); - EXPECT_EQ(status.destinations.get(i), NULL_ID); - } -} - -TEST(ContractMsVault, GetVaults_Multiple) -{ - ContractTestingMsVault msVault; - - increaseEnergy(OWNER1, 100000000ULL); - increaseEnergy(OWNER2, 100000000ULL); - increaseEnergy(OWNER3, 100000000ULL); - - auto vaultsForOwner2Before = msVault.getVaults(OWNER2); - - auto regOut1 = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut1.status, 1ULL); - auto regOut2 = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut2.status, 1ULL); - - auto vaultsForOwner2After = msVault.getVaults(OWNER2); - EXPECT_GE(static_cast(vaultsForOwner2After.numberOfVaults), - static_cast(vaultsForOwner2Before.numberOfVaults + 2U)); -} - -TEST(ContractMsVault, GetRevenue) -{ - ContractTestingMsVault msVault; - const Asset assetTest = { OWNER1, assetNameFromString("TESTREV") }; - - increaseEnergy(OWNER1, 1000000000ULL); - increaseEnergy(OWNER2, 1000000000ULL); - - uint64 expectedRevenue = 0; - auto revenueInfo = msVault.getRevenueInfo(); - EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); - EXPECT_EQ(revenueInfo.numberOfActiveVaults, 0U); - - // Register a vault, generating the first fee - auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - expectedRevenue += MSVAULT_REGISTERING_FEE; - - auto vaults = msVault.getVaults(OWNER1); - uint64 vaultId = vaults.vaultIds.get(0); - - // Deposit QUs to ensure the vault can pay holding fees - const uint64 depositAmount = 10000000; // 10M QUs - auto depOut = msVault.deposit(vaultId, depositAmount, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - // expectedRevenue += state.liveDepositFee; // Fee is currently 0 - - // Generate Qubic-based fees - auto relOut = msVault.releaseTo(vaultId, 1000ULL, DESTINATION, OWNER1); - EXPECT_EQ(relOut.status, 9ULL); // Pending approval - expectedRevenue += MSVAULT_RELEASE_FEE; - auto rstOut = msVault.resetRelease(vaultId, OWNER1); - EXPECT_EQ(rstOut.status, 1ULL); - expectedRevenue += MSVAULT_RELEASE_RESET_FEE; - - // Generate Asset-based fees - msVault.issueAsset(OWNER1, "TESTREV", 10000); - msVault.transferShareManagementRights(OWNER1, assetTest, 5000, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msVault.depositAsset(vaultId, assetTest, 1000, OWNER1); - EXPECT_EQ(depAssetOut.status, 1ULL); - // expectedRevenue += state.liveDepositFee; // Fee is currently 0 - auto relAssetOut = msVault.releaseAssetTo(vaultId, assetTest, 50, DESTINATION, OWNER2); - EXPECT_EQ(relAssetOut.status, 9ULL); // Pending approval - expectedRevenue += MSVAULT_RELEASE_FEE; - auto rstAssetOut = msVault.resetAssetRelease(vaultId, OWNER2); - EXPECT_EQ(rstAssetOut.status, 1ULL); - expectedRevenue += MSVAULT_RELEASE_RESET_FEE; - - // Verify revenue before the first epoch ends - revenueInfo = msVault.getRevenueInfo(); - EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); - - msVault.endEpoch(); - msVault.beginEpoch(); - - // Holding fee from the active vault is collected - expectedRevenue += MSVAULT_HOLDING_FEE; - - revenueInfo = msVault.getRevenueInfo(); - EXPECT_EQ(revenueInfo.numberOfActiveVaults, 1U); - EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); - - // Verify dividends were distributed correctly based on the total revenue so far - uint64 expectedDistribution = (expectedRevenue / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS; - EXPECT_EQ(revenueInfo.totalDistributedToShareholders, expectedDistribution); - - // Make more revenue generation actions in the new epoch - auto relOut2 = msVault.releaseTo(vaultId, 2000ULL, DESTINATION, OWNER2); - EXPECT_EQ(relOut2.status, 9ULL); - expectedRevenue += MSVAULT_RELEASE_FEE; - - auto rstOut2 = msVault.resetRelease(vaultId, OWNER2); - EXPECT_EQ(rstOut2.status, 1ULL); - expectedRevenue += MSVAULT_RELEASE_RESET_FEE; - - // Revoke some of the previously granted management rights. - // This one has a fee, but it is paid to QX, not kept by MsVault. - // Therefore, expectedRevenue should NOT be incremented. - auto revokeOut = msVault.revokeAssetManagementRights(OWNER1, assetTest, 2000); - EXPECT_EQ(revokeOut.status, 1ULL); - - // End the second epoch - msVault.endEpoch(); - msVault.beginEpoch(); - - // Another holding fee is collected - expectedRevenue += MSVAULT_HOLDING_FEE; - - revenueInfo = msVault.getRevenueInfo(); - EXPECT_EQ(revenueInfo.numberOfActiveVaults, 1U); - EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); - - // Verify the new cumulative dividend distribution - expectedDistribution = (expectedRevenue / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS; - EXPECT_EQ(revenueInfo.totalDistributedToShareholders, expectedDistribution); - - // No new transactions in this epoch - msVault.endEpoch(); - msVault.beginEpoch(); - - // A third holding fee is collected - expectedRevenue += MSVAULT_HOLDING_FEE; - - revenueInfo = msVault.getRevenueInfo(); - EXPECT_EQ(revenueInfo.numberOfActiveVaults, 1U); - EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); - - // Verify the final cumulative dividend distribution - expectedDistribution = (expectedRevenue / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS; - EXPECT_EQ(revenueInfo.totalDistributedToShareholders, expectedDistribution); -} - -TEST(ContractMsVault, ManagementRightsVsDirectDeposit) -{ - ContractTestingMsVault msvault; - - // Create an issuer and two users. - const id ISSUER = id::randomValue(); - const id USER_WITH_RIGHTS = id::randomValue(); // This user will do it correctly - const id USER_WITHOUT_RIGHTS = id::randomValue(); // This user will attempt a direct deposit first - - Asset assetTest = { ISSUER, assetNameFromString("ASSET") }; - const sint64 initialDistribution = 50000; - - // Give everyone energy for fees - increaseEnergy(ISSUER, QX_ISSUE_ASSET_FEE + (1000000 * 2)); - increaseEnergy(USER_WITH_RIGHTS, MSVAULT_REGISTERING_FEE + (1000000 * 3)); - increaseEnergy(USER_WITHOUT_RIGHTS, 1000000 * 3); // More energy for the correct attempt later - - // Issue the asset and distribute it to the two users - msvault.issueAsset(ISSUER, "ASSET", initialDistribution * 2); - msvault.transferAsset(ISSUER, USER_WITH_RIGHTS, assetTest, initialDistribution); - msvault.transferAsset(ISSUER, USER_WITHOUT_RIGHTS, assetTest, initialDistribution); - - // Verify initial on-chain balances (both users' shares are managed by QX currently) - EXPECT_EQ(numberOfShares(assetTest, { USER_WITH_RIGHTS, QX_CONTRACT_INDEX }, - { USER_WITH_RIGHTS, QX_CONTRACT_INDEX }), initialDistribution); - EXPECT_EQ(numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }, - { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }), initialDistribution); - - // Create a simple vault owned by USER_WITH_RIGHTS - auto regOut = msvault.registerVault(2, TEST_VAULT_NAME, { USER_WITH_RIGHTS, OWNER1 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaults = msvault.getVaults(USER_WITH_RIGHTS); - uint64 vaultId = vaults.vaultIds.get(0); - - // User with Management Rights - const sint64 sharesToManage1 = 10000; - msvault.transferShareManagementRights(USER_WITH_RIGHTS, assetTest, sharesToManage1, MSVAULT_CONTRACT_INDEX); - - // verify that management rights were transferred successfully - EXPECT_EQ(numberOfShares(assetTest, { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }, - { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }), sharesToManage1); - EXPECT_EQ(numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }), 0); - - // This user now makes multiple deposits - const sint64 deposit1_U1 = 1000; - const sint64 deposit2_U1 = 2500; - auto depAssetOut1 = msvault.depositAsset(vaultId, assetTest, deposit1_U1, USER_WITH_RIGHTS); - EXPECT_EQ(depAssetOut1.status, 1ULL); - - // Verify balances after first deposit - sint64 sc_onchain_balance = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(sc_onchain_balance, deposit1_U1); - sint64 user_managed_balance = numberOfShares(assetTest, { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }, - { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(user_managed_balance, sharesToManage1 - deposit1_U1); - - auto depAssetOut2 = msvault.depositAsset(vaultId, assetTest, deposit2_U1, USER_WITH_RIGHTS); - EXPECT_EQ(depAssetOut2.status, 1ULL); - - // verify balances after second deposit - sc_onchain_balance = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(sc_onchain_balance, deposit1_U1 + deposit2_U1); - sint64 user1_managed_balance = numberOfShares(assetTest, { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }, - { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(user1_managed_balance, sharesToManage1 - deposit1_U1 - deposit2_U1); - - // user without management rights - sint64 sc_balance_before_direct_attempt = sc_onchain_balance; - sint64 user3_balance_before = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }, - { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }); - - // This user attempts to deposit directly - auto depAssetOut3 = msvault.depositAsset(vaultId, assetTest, 500, USER_WITHOUT_RIGHTS); - EXPECT_EQ(depAssetOut3.status, 6ULL); // FAILURE_INSUFFICIENT_BALANCE - - // Verify that no shares were transferred - sint64 sc_balance_after_direct_attempt = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(sc_balance_after_direct_attempt, sc_balance_before_direct_attempt); - - sint64 user3_balance_after = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }, - { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }); - EXPECT_EQ(user3_balance_after, user3_balance_before); // User's balance should be unchanged - - sint64 user3_balance_after_msvault = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }, - { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(user3_balance_after_msvault, 0); - - // the second user now does it the correct way - const sint64 sharesToManage2 = 8000; - msvault.transferShareManagementRights(USER_WITHOUT_RIGHTS, assetTest, sharesToManage2, MSVAULT_CONTRACT_INDEX); - - // Verify their management rights were transferred successfully - EXPECT_EQ(numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }, - { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }), sharesToManage2); - - const sint64 deposit1_U2 = 4000; - auto depAssetOut4 = msvault.depositAsset(vaultId, assetTest, deposit1_U2, USER_WITHOUT_RIGHTS); - EXPECT_EQ(depAssetOut4.status, 1ULL); - - // check the total balance in the smart contract - sint64 final_sc_balance = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - sint64 total_deposited = deposit1_U1 + deposit2_U1 + deposit1_U2; - EXPECT_EQ(final_sc_balance, total_deposited); - - // Also verify the second user's remaining managed balance - sint64 user2_managed_balance = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }, - { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(user2_managed_balance, sharesToManage2 - deposit1_U2); -} - -TEST(ContractMsVault, DepositAsset_Success) -{ - ContractTestingMsVault msvault; - Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; - - // Create a vault and issue an asset to OWNER1 - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + QX_ISSUE_ASSET_FEE); - auto regOut = msvault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - auto vaults = msvault.getVaults(OWNER1); - uint64 vaultId = vaults.vaultIds.get(0); - - msvault.issueAsset(OWNER1, "ASSET", 1000000); - - auto OWNER4 = id::randomValue(); - - auto transfered = msvault.transferShareManagementRights(OWNER1, assetTest, 5000, MSVAULT_CONTRACT_INDEX); - - // Deposit the asset into the vault - auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 500, OWNER1); - EXPECT_EQ(depAssetOut.status, 1ULL); - - // Check the vault's asset balance - auto assetBalances = msvault.getVaultAssetBalances(vaultId); - EXPECT_EQ(assetBalances.status, 1ULL); - EXPECT_EQ(assetBalances.numberOfAssetTypes, 1ULL); - - auto firstAssetBalance = assetBalances.assetBalances.get(0); - EXPECT_EQ(firstAssetBalance.asset.issuer, assetTest.issuer); - EXPECT_EQ(firstAssetBalance.asset.assetName, assetTest.assetName); - EXPECT_EQ(firstAssetBalance.balance, 500ULL); - - // Check SC's shares - sint64 scShares = numberOfShares(assetTest, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(scShares, 500LL); -} - -TEST(ContractMsVault, DepositAsset_MaxTypes) -{ - ContractTestingMsVault msvault; - - // Create a vault - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + QX_ISSUE_ASSET_FEE * (MSVAULT_MAX_ASSET_TYPES + 1)); // Extra energy for fees - auto regOut = msvault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - auto vaults = msvault.getVaults(OWNER1); - uint64 vaultId = vaults.vaultIds.get(0); - - // Deposit the maximum number of different asset types - for (uint64 i = 0; i < MSVAULT_MAX_ASSET_TYPES; i++) - { - std::string assetName = "ASSET" + std::to_string(i); - Asset currentAsset = { OWNER1, assetNameFromString(assetName.c_str()) }; - msvault.issueAsset(OWNER1, assetName, 1000000); - msvault.transferShareManagementRights(OWNER1, currentAsset, 100000, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msvault.depositAsset(vaultId, currentAsset, 1000, OWNER1); - EXPECT_EQ(depAssetOut.status, 1ULL); - } - - // Check if max asset types reached - auto balances = msvault.getVaultAssetBalances(vaultId); - EXPECT_EQ(balances.numberOfAssetTypes, MSVAULT_MAX_ASSET_TYPES); - - // Try to deposit one more asset type - Asset extraAsset = { OWNER1, assetNameFromString("ASSETE") }; - msvault.issueAsset(OWNER1, "ASSETE", 100000); - msvault.transferShareManagementRights(OWNER1, extraAsset, 100000, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msvault.depositAsset(vaultId, extraAsset, 1000, OWNER1); - EXPECT_EQ(depAssetOut.status, 7ULL); // FAILURE_LIMIT_REACHED - - // The number of asset types should not have increased - auto balancesAfter = msvault.getVaultAssetBalances(vaultId); - EXPECT_EQ(balancesAfter.numberOfAssetTypes, MSVAULT_MAX_ASSET_TYPES); -} - -TEST(ContractMsVault, ReleaseAssetTo_FullApproval) -{ - ContractTestingMsVault msvault; - Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; - - // Create a 2-of-3 vault, issue and deposit an asset - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + MSVAULT_RELEASE_FEE + QX_ISSUE_ASSET_FEE + QX_MANAGEMENT_TRANSFER_FEE); - increaseEnergy(OWNER2, MSVAULT_RELEASE_FEE); - auto regOut = msvault.registerVault(TWO_OF_THREE, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - auto vaults = msvault.getVaults(OWNER1); - uint64 vaultId = vaults.vaultIds.get(0); - - msvault.issueAsset(OWNER1, "ASSET", 1000000); - msvault.transferShareManagementRights(OWNER1, assetTest, 800, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 800, OWNER1); - EXPECT_EQ(depAssetOut.status, 1ULL); - - // Deposit funds into the vault to cover the upcoming management transfer fee. - auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - // Check initial balances for the destination - EXPECT_EQ(numberOfShares(assetTest, { DESTINATION, QX_CONTRACT_INDEX }, { DESTINATION, QX_CONTRACT_INDEX }), 0LL); - EXPECT_EQ(numberOfShares(assetTest, { DESTINATION, MSVAULT_CONTRACT_INDEX }, { DESTINATION, MSVAULT_CONTRACT_INDEX }), 0LL); - auto vaultAssetBalanceBefore = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; - EXPECT_EQ(vaultAssetBalanceBefore, 800ULL); - - // Owners approve the release - auto relAssetOut1 = msvault.releaseAssetTo(vaultId, assetTest, 800, DESTINATION, OWNER1); - EXPECT_EQ(relAssetOut1.status, 9ULL); - auto relAssetOut2 = msvault.releaseAssetTo(vaultId, assetTest, 800, DESTINATION, OWNER2); - EXPECT_EQ(relAssetOut2.status, 1ULL); - - // Check final balances - sint64 destBalanceManagedByQx = numberOfShares(assetTest, { DESTINATION, QX_CONTRACT_INDEX }, { DESTINATION, QX_CONTRACT_INDEX }); - sint64 destBalanceManagedByMsVault = numberOfShares(assetTest, { DESTINATION, MSVAULT_CONTRACT_INDEX }, { DESTINATION, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(destBalanceManagedByQx, 800LL); - EXPECT_EQ(destBalanceManagedByMsVault, 0LL); - - auto vaultAssetBalanceAfter = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; - EXPECT_EQ(vaultAssetBalanceAfter, 0ULL); // 800 - 800 - - // The vault's qubic balance should be 0 after paying the management transfer fee - auto vaultQubicBalanceAfter = msvault.getBalanceOf(vaultId); - EXPECT_EQ(vaultQubicBalanceAfter.balance, 0LL); - - // Release status should be reset - auto releaseStatus = msvault.getAssetReleaseStatus(vaultId); - EXPECT_EQ(releaseStatus.amounts.get(0), 0ULL); - EXPECT_EQ(releaseStatus.destinations.get(0), NULL_ID); -} - -TEST(ContractMsVault, ReleaseAssetTo_PartialApproval) -{ - ContractTestingMsVault msvault; - Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; - - // Setup - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + MSVAULT_RELEASE_FEE + QX_MANAGEMENT_TRANSFER_FEE); - auto regOut = msvault.registerVault(TWO_OF_THREE, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaults = msvault.getVaults(OWNER1); - uint64 vaultId = vaults.vaultIds.get(0); - msvault.issueAsset(OWNER1, "ASSET", 1000000); - msvault.transferShareManagementRights(OWNER1, assetTest, 800, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 800, OWNER1); - EXPECT_EQ(depAssetOut.status, 1ULL); - - // Deposit the fee into the vault so it can process release requests. - auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - // Only one owner approves - auto relAssetOut = msvault.releaseAssetTo(vaultId, assetTest, 500, DESTINATION, OWNER1); - EXPECT_EQ(relAssetOut.status, 9ULL); // PENDING_APPROVAL - - // Check release status is pending - auto status = msvault.getAssetReleaseStatus(vaultId); - EXPECT_EQ(status.status, 1ULL); - // Owner 1 is at index 0 - EXPECT_EQ(status.assets.get(0).assetName, assetTest.assetName); - EXPECT_EQ(status.amounts.get(0), 500ULL); - EXPECT_EQ(status.destinations.get(0), DESTINATION); - // Other owner slots are empty - EXPECT_EQ(status.amounts.get(1), 0ULL); - - // Balances should be unchanged - sint64 destinationBalance = numberOfShares(assetTest, { DESTINATION, QX_CONTRACT_INDEX }, - { DESTINATION, QX_CONTRACT_INDEX }); - EXPECT_EQ(destinationBalance, 0LL); - auto vaultBalance = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; - EXPECT_EQ(vaultBalance, 800ULL); -} - -TEST(ContractMsVault, ResetAssetRelease_Success) -{ - ContractTestingMsVault msvault; - Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; - - // Setup - increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + MSVAULT_RELEASE_RESET_FEE + MSVAULT_RELEASE_FEE + QX_MANAGEMENT_TRANSFER_FEE); - auto regOut = msvault.registerVault(TWO_OF_TWO, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - auto vaults = msvault.getVaults(OWNER1); - uint64 vaultId = vaults.vaultIds.get(0); - msvault.issueAsset(OWNER1, "ASSET", 1000000); - msvault.transferShareManagementRights(OWNER1, assetTest, 100, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 100, OWNER1); - EXPECT_EQ(depAssetOut.status, 1ULL); - - auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, OWNER1); - EXPECT_EQ(depOut.status, 1ULL); - - // Propose and then reset a release - auto relAssetOut = msvault.releaseAssetTo(vaultId, assetTest, 50, DESTINATION, OWNER1); - EXPECT_EQ(relAssetOut.status, 9ULL); - - // Check status is pending before reset - auto statusBefore = msvault.getAssetReleaseStatus(vaultId); - EXPECT_EQ(statusBefore.amounts.get(0), 50ULL); - - // Reset the release - auto rstAssetOut = msvault.resetAssetRelease(vaultId, OWNER1); - EXPECT_EQ(rstAssetOut.status, 1ULL); - - // Status should be cleared for that owner - auto statusAfter = msvault.getAssetReleaseStatus(vaultId); - EXPECT_EQ(statusAfter.amounts.get(0), 0ULL); - EXPECT_EQ(statusAfter.destinations.get(0), NULL_ID); - - // Vault balance should be unchanged - auto vaultBalance = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; - EXPECT_EQ(vaultBalance, 100ULL); -} - -TEST(ContractMsVault, FullLifecycle_BalanceVerification) -{ - ContractTestingMsVault msvault; - const id USER = OWNER1; - const id PARTNER = OWNER2; - const id DESTINATION_ACC = OWNER3; - Asset assetTest = { USER, assetNameFromString("ASSET") }; - const sint64 initialShares = 10000; - const sint64 sharesToManage = 5000; - const sint64 sharesToDeposit = 4000; - const sint64 sharesToRelease = 1500; - - // Issue asset and create a type 2 vault - increaseEnergy(USER, MSVAULT_REGISTERING_FEE + QX_ISSUE_ASSET_FEE + QX_MANAGEMENT_TRANSFER_FEE); - increaseEnergy(PARTNER, MSVAULT_RELEASE_FEE); - - msvault.issueAsset(USER, "ASSET", initialShares); - auto regOut = msvault.registerVault(TWO_OF_TWO, TEST_VAULT_NAME, { USER, PARTNER }, MSVAULT_REGISTERING_FEE); - EXPECT_EQ(regOut.status, 1ULL); - - auto vaults = msvault.getVaults(USER); - uint64 vaultId = vaults.vaultIds.get(0); - - // Verify user has full on-chain balance under QX management - sint64 userShares_QX = numberOfShares(assetTest, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }); - EXPECT_EQ(userShares_QX, initialShares); - - // Fund the vault for the future management transfer fee - auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, USER); - EXPECT_EQ(depOut.status, 1ULL); - - // User gives MsVault management rights over a portion of their shares - msvault.transferShareManagementRights(USER, assetTest, sharesToManage, MSVAULT_CONTRACT_INDEX); - - // Verify on-chain balances after management transfer - sint64 userShares_MSVAULT_Managed = numberOfShares(assetTest, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }); - userShares_QX = numberOfShares(assetTest, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }); - EXPECT_EQ(userShares_MSVAULT_Managed, sharesToManage); - EXPECT_EQ(userShares_QX, initialShares - sharesToManage); - - // User deposits the MsVault-managed shares into the vault - auto depAssetOut = msvault.depositAsset(vaultId, assetTest, sharesToDeposit, USER); - EXPECT_EQ(depAssetOut.status, 1ULL); - - // User's on-chain balance of MsVault-managed shares should decrease - userShares_MSVAULT_Managed = numberOfShares(assetTest, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(userShares_MSVAULT_Managed, sharesToManage - sharesToDeposit); // 5000 - 4000 = 1000 - - // MsVault contract's on-chain balance should increase - sint64 scShares_onchain = numberOfShares(assetTest, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(scShares_onchain, sharesToDeposit); - - // Vault's internal balance should match the on-chain balance - auto vaultBalances = msvault.getVaultAssetBalances(vaultId); - EXPECT_EQ(vaultBalances.status, 1ULL); - EXPECT_EQ(vaultBalances.numberOfAssetTypes, 1ULL); - EXPECT_EQ(vaultBalances.assetBalances.get(0).balance, sharesToDeposit); - - // Both owners approve a release to the destination - auto relAssetOut1 = msvault.releaseAssetTo(vaultId, assetTest, sharesToRelease, DESTINATION_ACC, USER); - EXPECT_EQ(relAssetOut1.status, 9ULL); - auto relAssetOut2 = msvault.releaseAssetTo(vaultId, assetTest, sharesToRelease, DESTINATION_ACC, PARTNER); - EXPECT_EQ(relAssetOut2.status, 1ULL); - - // MsVault contract's on-chain balance should decrease - scShares_onchain = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(scShares_onchain, sharesToDeposit - sharesToRelease); - - // Vault's internal balance should be updated correctly - vaultBalances = msvault.getVaultAssetBalances(vaultId); - EXPECT_EQ(vaultBalances.assetBalances.get(0).balance, sharesToDeposit - sharesToRelease); - - // Vault's internal qubic balance should decrease by the fee - EXPECT_EQ(msvault.getBalanceOf(vaultId).balance, 0); - - // Destination's on-chain balance should increase, and it should be managed by QX - sint64 destinationSharesManagedByQx = numberOfShares(assetTest, { DESTINATION_ACC, QX_CONTRACT_INDEX }, { DESTINATION_ACC, QX_CONTRACT_INDEX }); - sint64 destinationSharesManagedByMsVault = numberOfShares(assetTest, { DESTINATION_ACC, MSVAULT_CONTRACT_INDEX }, { DESTINATION_ACC, MSVAULT_CONTRACT_INDEX }); - - EXPECT_EQ(destinationSharesManagedByQx, sharesToRelease); - EXPECT_EQ(destinationSharesManagedByMsVault, 0); -} - -TEST(ContractMsVault, StressTest_MultiUser_MultiAsset) -{ - ContractTestingMsVault msvault; - - // Define users, assets, and vaults - const int USER_COUNT = 16; - const int ASSET_COUNT = 8; - const int VAULT_COUNT = 3; - std::vector users; - std::vector assets; - - for (int i = 0; i < USER_COUNT; ++i) - { - users.push_back(id::randomValue()); - increaseEnergy(users[i], 1000000000000ULL); - } - - for (int i = 0; i < ASSET_COUNT; ++i) - { - // Issue each asset from a different user for variety - id issuer = users[i]; - std::string assetName = "ASSET" + std::to_string(i); - assets.push_back({ issuer, assetNameFromString(assetName.c_str()) }); - msvault.issueAsset(issuer, assetName, 1000000); // Issue 1M of each token - } - - // Create 3 vaults with different sets of 4 owners each - EXPECT_EQ(msvault.registerVault(3, id::randomValue(), { users[0], users[1], users[2], users[3] }, MSVAULT_REGISTERING_FEE).status, 1ULL); - EXPECT_EQ(msvault.registerVault(2, id::randomValue(), { users[4], users[5], users[6], users[7] }, MSVAULT_REGISTERING_FEE).status, 1ULL); - EXPECT_EQ(msvault.registerVault(4, id::randomValue(), { users[8], users[9], users[10], users[11] }, MSVAULT_REGISTERING_FEE).status, 1ULL); - - // Each of the 8 assets is deposited twice by its owner - uint64 targetVaultId = 0; - const sint64 depositAmount = 100; - - for (int i = 0; i < USER_COUNT; ++i) - { - int assetIndex = i % ASSET_COUNT; - Asset assetToDeposit = assets[assetIndex]; - id owner_of_asset = users[assetIndex]; - - msvault.transferShareManagementRights(owner_of_asset, assetToDeposit, depositAmount, MSVAULT_CONTRACT_INDEX); - auto depAssetOut = msvault.depositAsset(targetVaultId, assetToDeposit, depositAmount, owner_of_asset); - EXPECT_EQ(depAssetOut.status, 1ULL); - } - - auto depOut = msvault.deposit(targetVaultId, QX_MANAGEMENT_TRANSFER_FEE, users[0]); - EXPECT_EQ(depOut.status, 1ULL); - - // Check the state of the target vault - auto vaultBalances = msvault.getVaultAssetBalances(targetVaultId); - EXPECT_EQ(vaultBalances.status, 1ULL); - EXPECT_EQ(vaultBalances.numberOfAssetTypes, (uint64_t)ASSET_COUNT); - for (uint64 i = 0; i < ASSET_COUNT; ++i) - { - // Verify each asset has a balance of 200 (deposited twice) - EXPECT_EQ(vaultBalances.assetBalances.get(i).balance, depositAmount * 2); - } - - // From Vault 0, owners 0, 1, and 2 approve a release - const id releaseDestination = users[15]; - const Asset assetToRelease = assets[0]; // Release ASSET_0 - const sint64 releaseAmount = 75; - - // A 3-of-4 vault, so we need 3 approvals - EXPECT_EQ(msvault.releaseAssetTo(targetVaultId, assetToRelease, releaseAmount, releaseDestination, users[0]).status, 9ULL); - EXPECT_EQ(msvault.releaseAssetTo(targetVaultId, assetToRelease, releaseAmount, releaseDestination, users[1]).status, 9ULL); - EXPECT_EQ(msvault.releaseAssetTo(targetVaultId, assetToRelease, releaseAmount, releaseDestination, users[2]).status, 1ULL); - - // Check destination on-chain balance - sint64 destBalance = numberOfShares(assetToRelease, { releaseDestination, QX_CONTRACT_INDEX }, - { releaseDestination, QX_CONTRACT_INDEX }); - EXPECT_EQ(destBalance, releaseAmount); - - // Check vault's internal accounting for the released asset - vaultBalances = msvault.getVaultAssetBalances(targetVaultId); - bool foundReleasedAsset = false; - for (uint64 i = 0; i < vaultBalances.numberOfAssetTypes; ++i) - { - auto bal = vaultBalances.assetBalances.get(i); - if (bal.asset.assetName == assetToRelease.assetName && bal.asset.issuer == assetToRelease.issuer) - { - // Expected balance is (100 * 2) - 75 = 125 - EXPECT_EQ(bal.balance, (depositAmount * 2) - releaseAmount); - foundReleasedAsset = true; - break; - } - } - EXPECT_TRUE(foundReleasedAsset); - - // Check MsVault's on-chain balance for the released asset - sint64 scOnChainBalance = numberOfShares(assetToRelease, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, - { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); - // The total on-chain balance should also be (100 * 2) - 75 = 125 - EXPECT_EQ(scOnChainBalance, (depositAmount * 2) - releaseAmount); -} - -TEST(ContractMsVault, RevokeAssetManagementRights_Success) -{ - ContractTestingMsVault msvault; - - const id USER = OWNER1; - const Asset asset = { USER, assetNameFromString("REVOKE") }; - const sint64 initialShares = 10000; - const sint64 sharesToManage = 4000; - const sint64 sharesToRevoke = 3000; - - // Issue asset and transfer management rights to MsVault - increaseEnergy(USER, QX_ISSUE_ASSET_FEE + 1000000 + 100); // Energy for all fees - msvault.issueAsset(USER, "REVOKE", initialShares); - - // Verify initial state: all shares managed by QX - EXPECT_EQ(numberOfShares(asset, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }), initialShares); - EXPECT_EQ(numberOfShares(asset, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }), 0); - - // User gives MsVault management rights over a portion of their shares - msvault.transferShareManagementRights(USER, asset, sharesToManage, MSVAULT_CONTRACT_INDEX); - - // Verify intermediate state: rights are split between QX and MsVault - EXPECT_EQ(numberOfShares(asset, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }), initialShares - sharesToManage); - EXPECT_EQ(numberOfShares(asset, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }), sharesToManage); - - // User revokes a portion of the managed rights from MsVault. The helper now handles the fee. - auto revokeOut = msvault.revokeAssetManagementRights(USER, asset, sharesToRevoke); - - // Verify the outcome - EXPECT_EQ(revokeOut.status, 1ULL); - EXPECT_EQ(revokeOut.transferredNumberOfShares, sharesToRevoke); - - // The amount managed by MsVault should decrease - sint64 finalManagedByMsVault = numberOfShares(asset, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }); - EXPECT_EQ(finalManagedByMsVault, sharesToManage - sharesToRevoke); // 4000 - 3000 = 1000 - - // The amount managed by QX should increase accordingly - sint64 finalManagedByQx = numberOfShares(asset, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }); - EXPECT_EQ(finalManagedByQx, (initialShares - sharesToManage) + sharesToRevoke); // 6000 + 3000 = 9000 -} diff --git a/test/contract_nostromo.cpp b/test/contract_nostromo.cpp deleted file mode 100644 index 262e5e2d8..000000000 --- a/test/contract_nostromo.cpp +++ /dev/null @@ -1,1692 +0,0 @@ -#define NO_UEFI - -#include -#include - -#include "contract_testing.h" - -static std::mt19937_64 rand64; - -static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) -{ - if(minValue > maxValue) - { - return 0; - } - return minValue + rand64() % (maxValue - minValue); -} - -static id getUser(unsigned long long i) -{ - return id(i, i / 2 + 4, i + 10, i * 3 + 8); -} - -static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) -{ - unsigned long long userCount = random(0, maxNum); - std::vector users; - users.reserve(userCount); - for (unsigned int i = 0; i < userCount; ++i) - { - unsigned long long userIdx = random(0, totalUsers - 1); - users.push_back(getUser(userIdx)); - } - return users; -} - -class NostromoChecker : public NOST -{ -public: - void registerChecker(id registerId, uint32 tierLevel, uint32 indexOfRegister) - { - EXPECT_EQ(users.contains(registerId), 1); - uint8 stateTierLevel; - users.get(registerId, stateTierLevel); - EXPECT_EQ(tierLevel, stateTierLevel); - } - void countOfRegisterChecker(uint32 totalUser) - { - EXPECT_EQ(totalUser, numberOfRegister); - } - void logoutFromTierChecker(id registerId) - { - EXPECT_EQ(users.contains(registerId), 0); - } - void numberOfCreatedProjectChecker(uint32 numberOfProjects) - { - EXPECT_EQ(numberOfProjects, numberOfCreatedProject); - } - void createdProjectChecker(uint32 indexOfProject, id creator, uint64 assetName, uint32 supply, uint32 startYear, uint32 startMonth, uint32 startDay, uint32 startHour, uint32 endYear, uint32 endMonth, uint32 endDay, uint32 endHour) - { - uint32 startDate, endDate; - NOST::packNostromoDate(startYear, startMonth, startDay, startHour, 0, 0, startDate); - NOST::packNostromoDate(endYear, endMonth, endDay, endHour, 0, 0, endDate); - - EXPECT_EQ(tokens.contains(assetName), 1); - EXPECT_EQ(projects.get(indexOfProject).creator, creator); - EXPECT_EQ(projects.get(indexOfProject).isCreatedFundarasing, 0); - EXPECT_EQ(projects.get(indexOfProject).numberOfNo, 0); - EXPECT_EQ(projects.get(indexOfProject).numberOfYes, 0); - EXPECT_EQ(projects.get(indexOfProject).supplyOfToken, supply); - EXPECT_EQ(projects.get(indexOfProject).tokenName, assetName); - EXPECT_EQ(projects.get(indexOfProject).startDate, startDate); - EXPECT_EQ(projects.get(indexOfProject).endDate, endDate); - } - void epochRevenueChecker(uint64 amountOfRevenue) - { - EXPECT_EQ(amountOfRevenue, epochRevenue); - } - void totalPoolWeightChecker(uint32 totalWeight) - { - EXPECT_EQ(totalWeight, totalPoolWeight); - } - void voteInProjectChecker(uint32 indexOfProject, uint32 numberOfYes, uint32 numberOfNo) - { - EXPECT_EQ(projects.get(indexOfProject).numberOfYes, numberOfYes); - EXPECT_EQ(projects.get(indexOfProject).numberOfNo, numberOfNo); - } - void numberOfVotedProjectAndVotedListChecker(id registerId, uint32 numberOfProject, Array votedList) - { - uint32 count; - numberOfVotedProject.get(registerId, count); - EXPECT_EQ(count, numberOfProject); - - Array vote; - voteStatus.get(registerId, vote); - for (uint32 i = 0; i < count; i++) - { - EXPECT_EQ(vote.get(i), votedList.get(i)); - } - } - void countOfFundraisingChecker(uint32 count) - { - EXPECT_EQ(count, numberOfFundraising); - } - void createFundraisingChecker(const id& registerId, - uint64 tokenPrice, - uint64 soldAmount, - uint64 requiredFunds, - - uint32 indexOfProject, - uint32 firstPhaseStartYear, - uint32 firstPhaseStartMonth, - uint32 firstPhaseStartDay, - uint32 firstPhaseStartHour, - uint32 firstPhaseEndYear, - uint32 firstPhaseEndMonth, - uint32 firstPhaseEndDay, - uint32 firstPhaseEndHour, - - uint32 secondPhaseStartYear, - uint32 secondPhaseStartMonth, - uint32 secondPhaseStartDay, - uint32 secondPhaseStartHour, - uint32 secondPhaseEndYear, - uint32 secondPhaseEndMonth, - uint32 secondPhaseEndDay, - uint32 secondPhaseEndHour, - - uint32 thirdPhaseStartYear, - uint32 thirdPhaseStartMonth, - uint32 thirdPhaseStartDay, - uint32 thirdPhaseStartHour, - uint32 thirdPhaseEndYear, - uint32 thirdPhaseEndMonth, - uint32 thirdPhaseEndDay, - uint32 thirdPhaseEndHour, - - uint32 listingStartYear, - uint32 listingStartMonth, - uint32 listingStartDay, - uint32 listingStartHour, - - uint32 cliffEndYear, - uint32 cliffEndMonth, - uint32 cliffEndDay, - uint32 cliffEndHour, - - uint32 vestingEndYear, - uint32 vestingEndMonth, - uint32 vestingEndDay, - uint32 vestingEndHour, - - uint8 threshold, - uint8 TGE, - uint8 stepOfVesting, - - uint32 indexOfFundraising) - { - uint32 firstPhaseStartDate_t, secondPhaseStartDate_t, thirdPhaseStartDate_t, firstPhaseEndDate_t, secondPhaseEndDate_t, thirdPhaseEndDate_t, listingStartDate_t, cliffEndDate_t, vestingEndDate_t; - NOST::packNostromoDate(firstPhaseStartYear, firstPhaseStartMonth, firstPhaseStartDay, firstPhaseStartHour, 0, 0, firstPhaseStartDate_t); - NOST::packNostromoDate(secondPhaseStartYear, secondPhaseStartMonth, secondPhaseStartDay, secondPhaseStartHour, 0, 0, secondPhaseStartDate_t); - NOST::packNostromoDate(thirdPhaseStartYear, thirdPhaseStartMonth, thirdPhaseStartDay, thirdPhaseStartHour, 0, 0, thirdPhaseStartDate_t); - NOST::packNostromoDate(firstPhaseEndYear, firstPhaseEndMonth, firstPhaseEndDay, firstPhaseEndHour, 0, 0, firstPhaseEndDate_t); - NOST::packNostromoDate(secondPhaseEndYear, secondPhaseEndMonth, secondPhaseEndDay, secondPhaseEndHour, 0, 0, secondPhaseEndDate_t); - NOST::packNostromoDate(thirdPhaseEndYear, thirdPhaseEndMonth, thirdPhaseEndDay, thirdPhaseEndHour, 0, 0, thirdPhaseEndDate_t); - NOST::packNostromoDate(listingStartYear, listingStartMonth, listingStartDay, listingStartHour, 0, 0, listingStartDate_t); - NOST::packNostromoDate(cliffEndYear, cliffEndMonth, cliffEndDay, cliffEndHour, 0, 0, cliffEndDate_t); - NOST::packNostromoDate(vestingEndYear, vestingEndMonth, vestingEndDay, vestingEndHour, 0, 0, vestingEndDate_t); - - EXPECT_EQ(registerId, projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).creator); - EXPECT_EQ(tokenPrice, fundaraisings.get(indexOfFundraising).tokenPrice); - - EXPECT_EQ(soldAmount, fundaraisings.get(indexOfFundraising).soldAmount); - EXPECT_EQ(requiredFunds, fundaraisings.get(indexOfFundraising).requiredFunds); - EXPECT_EQ(indexOfProject, fundaraisings.get(indexOfFundraising).indexOfProject); - EXPECT_EQ(firstPhaseStartDate_t, fundaraisings.get(indexOfFundraising).firstPhaseStartDate); - EXPECT_EQ(secondPhaseStartDate_t, fundaraisings.get(indexOfFundraising).secondPhaseStartDate); - EXPECT_EQ(thirdPhaseStartDate_t, fundaraisings.get(indexOfFundraising).thirdPhaseStartDate); - EXPECT_EQ(firstPhaseEndDate_t, fundaraisings.get(indexOfFundraising).firstPhaseEndDate); - EXPECT_EQ(secondPhaseEndDate_t, fundaraisings.get(indexOfFundraising).secondPhaseEndDate); - EXPECT_EQ(thirdPhaseEndDate_t, fundaraisings.get(indexOfFundraising).thirdPhaseEndDate); - EXPECT_EQ(listingStartDate_t, fundaraisings.get(indexOfFundraising).listingStartDate); - EXPECT_EQ(cliffEndDate_t, fundaraisings.get(indexOfFundraising).cliffEndDate); - EXPECT_EQ(vestingEndDate_t, fundaraisings.get(indexOfFundraising).vestingEndDate); - EXPECT_EQ(threshold, fundaraisings.get(indexOfFundraising).threshold); - EXPECT_EQ(TGE, fundaraisings.get(indexOfFundraising).TGE); - EXPECT_EQ(stepOfVesting, fundaraisings.get(indexOfFundraising).stepOfVesting); - - } - uint8 getTierLevel(id registerId) - { - if (users.contains(registerId)) - { - uint8 tierLevel; - users.get(registerId, tierLevel); - return tierLevel; - } - return 0; - } - uint64 getInvestedAmount(uint32 indexOfFundraising, id registerId) - { - investors.get(registerId, tmpInvestedList); - uint32 numberOfProject; - numberOfInvestedProjects.get(registerId, numberOfProject); - - for (uint32 i = 0; i < numberOfProject; i++) - { - if (tmpInvestedList.get(i).indexOfFundraising == indexOfFundraising) - { - return tmpInvestedList.get(i).investedAmount; - } - } - - return 0; - } - uint64 getEpochRevenue() - { - return epochRevenue; - } - void totalRaisedFundChecker(uint32 indexOfFundraising, uint64 raisedFund, uint64 assetName) - { - EXPECT_EQ(raisedFund, fundaraisings.get(indexOfFundraising).raisedFunds); - - if (fundaraisings.get(indexOfFundraising).isCreatedToken) - { - Asset assetInfo; - assetInfo.assetName = assetName; - assetInfo.issuer = id(NOST_CONTRACT_INDEX, 0, 0, 0); - EXPECT_EQ(numberOfShares(assetInfo), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).supplyOfToken); - EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).creator, projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).creator, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).supplyOfToken - fundaraisings.get(indexOfFundraising).soldAmount); - } - } - void endEpochSucceedFundraisingChecker(id creator, uint32 indexOfFundraising, uint64 totalInvestedFund, uint64 originalCreatorBalance, uint64 assetName) - { - EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), creator, creator, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).supplyOfToken - div(totalInvestedFund, fundaraisings.get(indexOfFundraising).tokenPrice)); - EXPECT_EQ(fundaraisings.get(indexOfFundraising).raisedFunds, 0); - } - void endEpochFailedFundraisingChecker(uint32 indexOfFundraising) - { - EXPECT_EQ(fundaraisings.get(indexOfFundraising).raisedFunds, 0); - } - void endEpochVoteStatusClearChecker() - { - id userId; - uint64 tierLevel; - uint64 idx = users.nextElementIndex(NULL_INDEX); - uint32 numberOfProject; - Array votedList; - while (idx != NULL_INDEX) - { - userId = users.key(idx); - tierLevel = users.value(idx); - - EXPECT_EQ(voteStatus.get(userId, votedList), 0); - EXPECT_EQ(numberOfVotedProject.get(userId, numberOfProject), 0); - - idx = users.nextElementIndex(idx); - } - } - void getStatsChecker(uint64 epochRevenu_t, uint64 totalPoolWeight_t, uint32 numberOfCreatedProject_t, uint32 numberOfFundraising_t, uint32 numberOfRegister_t) - { - EXPECT_EQ(epochRevenu_t, epochRevenue); - EXPECT_EQ(totalPoolWeight_t, totalPoolWeight); - EXPECT_EQ(numberOfCreatedProject_t, numberOfCreatedProject); - EXPECT_EQ(numberOfFundraising_t, numberOfFundraising); - EXPECT_EQ(numberOfRegister_t, numberOfRegister); - } - void removeElementAfterClaimChecker(id user) - { - uint32 tp; - EXPECT_EQ(investors.get(user, tmpInvestedList), 0); - EXPECT_EQ(numberOfInvestedProjects.get(user, tp), 0); - } -}; - -class ContractTestingNostromo : protected ContractTesting -{ -public: - ContractTestingNostromo() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(NOST); - callSystemProcedure(NOST_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QUOTTERY); - callSystemProcedure(QUOTTERY_CONTRACT_INDEX, INITIALIZE); - } - NostromoChecker* getState() - { - return (NostromoChecker*)contractStates[NOST_CONTRACT_INDEX]; - } - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(NOST_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - void registerInTier(const id& registerId, - uint32 tierLevel, - uint64 depositeAmount) - { - NOST::registerInTier_input input; - NOST::registerInTier_output output; - - input.tierLevel = tierLevel; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 1, input, output, registerId, depositeAmount); - } - void logoutFromTier(const id& registerId) - { - NOST::logoutFromTier_input input; - NOST::logoutFromTier_output output; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 2, input, output, registerId, 0); - } - void createProject(const id& registerId, - uint64 tokenName, - uint64 supply, - uint32 startYear, - uint32 startMonth, - uint32 startDay, - uint32 startHour, - uint32 endYear, - uint32 endMonth, - uint32 endDay, - uint32 endHour) - { - NOST::createProject_input input; - NOST::createProject_output output; - - input.tokenName = tokenName; - input.supply = supply; - input.startYear = startYear; - input.startMonth = startMonth; - input.startDay = startDay; - input.startHour = startHour; - input.endYear = endYear; - input.endMonth = endMonth; - input.endDay = endDay; - input.endHour = endHour; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 3, input, output, registerId, NOSTROMO_CREATE_PROJECT_FEE); - } - void voteInProject(const id& registerId, - uint32 indexOfProject, - bit decision) - { - NOST::voteInProject_input input; - NOST::voteInProject_output output; - - input.decision = decision; - input.indexOfProject = indexOfProject; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 4, input, output, registerId, 0); - } - void createFundraising(const id& registerId, - uint64 tokenPrice, - uint64 soldAmount, - uint64 requiredFunds, - - uint32 indexOfProject, - uint32 firstPhaseStartYear, - uint32 firstPhaseStartMonth, - uint32 firstPhaseStartDay, - uint32 firstPhaseStartHour, - uint32 firstPhaseEndYear, - uint32 firstPhaseEndMonth, - uint32 firstPhaseEndDay, - uint32 firstPhaseEndHour, - - uint32 secondPhaseStartYear, - uint32 secondPhaseStartMonth, - uint32 secondPhaseStartDay, - uint32 secondPhaseStartHour, - uint32 secondPhaseEndYear, - uint32 secondPhaseEndMonth, - uint32 secondPhaseEndDay, - uint32 secondPhaseEndHour, - - uint32 thirdPhaseStartYear, - uint32 thirdPhaseStartMonth, - uint32 thirdPhaseStartDay, - uint32 thirdPhaseStartHour, - uint32 thirdPhaseEndYear, - uint32 thirdPhaseEndMonth, - uint32 thirdPhaseEndDay, - uint32 thirdPhaseEndHour, - - uint32 listingStartYear, - uint32 listingStartMonth, - uint32 listingStartDay, - uint32 listingStartHour, - - uint32 cliffEndYear, - uint32 cliffEndMonth, - uint32 cliffEndDay, - uint32 cliffEndHour, - - uint32 vestingEndYear, - uint32 vestingEndMonth, - uint32 vestingEndDay, - uint32 vestingEndHour, - - uint8 threshold, - uint8 TGE, - uint8 stepOfVesting) - { - NOST::createFundraising_input input; - NOST::createFundraising_output output; - - input.tokenPrice = tokenPrice; - input.soldAmount = soldAmount; - input.requiredFunds = requiredFunds; - - input.indexOfProject = indexOfProject; - input.firstPhaseStartYear = firstPhaseStartYear; - input.firstPhaseStartMonth = firstPhaseStartMonth; - input.firstPhaseStartDay = firstPhaseStartDay; - input.firstPhaseStartHour = firstPhaseStartHour; - input.firstPhaseEndYear = firstPhaseEndYear; - input.firstPhaseEndMonth = firstPhaseEndMonth; - input.firstPhaseEndDay = firstPhaseEndDay; - input.firstPhaseEndHour = firstPhaseEndHour; - - input.secondPhaseStartYear = secondPhaseStartYear; - input.secondPhaseStartMonth = secondPhaseStartMonth; - input.secondPhaseStartDay = secondPhaseStartDay; - input.secondPhaseStartHour = secondPhaseStartHour; - input.secondPhaseEndYear = secondPhaseEndYear; - input.secondPhaseEndMonth = secondPhaseEndMonth; - input.secondPhaseEndDay = secondPhaseEndDay; - input.secondPhaseEndHour = secondPhaseEndHour; - - input.thirdPhaseStartYear = thirdPhaseStartYear; - input.thirdPhaseStartMonth = thirdPhaseStartMonth; - input.thirdPhaseStartDay = thirdPhaseStartDay; - input.thirdPhaseStartHour = thirdPhaseStartHour; - input.thirdPhaseEndYear = thirdPhaseEndYear; - input.thirdPhaseEndMonth = thirdPhaseEndMonth; - input.thirdPhaseEndDay = thirdPhaseEndDay; - input.thirdPhaseEndHour = thirdPhaseEndHour; - - input.listingStartYear = listingStartYear; - input.listingStartMonth = listingStartMonth; - input.listingStartDay = listingStartDay; - input.listingStartHour = listingStartHour; - - input.cliffEndYear = cliffEndYear; - input.cliffEndMonth = cliffEndMonth; - input.cliffEndDay = cliffEndDay; - input.cliffEndHour = cliffEndHour; - - input.vestingEndYear = vestingEndYear; - input.vestingEndMonth = vestingEndMonth; - input.vestingEndDay = vestingEndDay; - input.vestingEndHour = vestingEndHour; - - input.threshold = threshold; - input.TGE = TGE; - input.stepOfVesting = stepOfVesting; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 5, input, output, registerId, NOSTROMO_QX_TOKEN_ISSUANCE_FEE); - } - void investInProject(const id& investorId, - uint32 indexOfFundraising, - uint64 investmentAmount) - { - NOST::investInProject_input input; - NOST::investInProject_output output; - - input.indexOfFundraising = indexOfFundraising; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 6, input, output, investorId, investmentAmount); - } - uint64 claimToken(const id& claimerId, - uint64 claimAmount, - uint32 indexOfFundraising) - { - NOST::claimToken_input input; - NOST::claimToken_output output; - - input.amount = claimAmount; - input.indexOfFundraising = indexOfFundraising; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 7, input, output, claimerId, 0); - return output.claimedAmount; - } - void upgradeTier(const id& registerId, - uint32 newTierLevel, - uint64 depositAmount) - { - NOST::upgradeTier_input input; - NOST::upgradeTier_output output; - - input.newTierLevel = newTierLevel; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 8, input, output, registerId, depositAmount); - } - sint64 TransferShareManagementRights(const id& user, Asset asset, sint64 numberOfShares, uint32 newManagingContractIndex) - { - NOST::TransferShareManagementRights_input input; - NOST::TransferShareManagementRights_output output; - - input.asset = asset; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(NOST_CONTRACT_INDEX, 9, input, output, user, 100); - - return output.transferredNumberOfShares; - } - NOST::getStats_output getStats() const - { - NOST::getStats_input input; - NOST::getStats_output output; - - callFunction(NOST_CONTRACT_INDEX, 1, input, output); - return output; - } - NOST::getTierLevelByUser_output getTierLevelByUser(const id& registerId) const - { - NOST::getTierLevelByUser_input input; - NOST::getTierLevelByUser_output output; - - input.userId = registerId; - callFunction(NOST_CONTRACT_INDEX, 2, input, output); - return output; - } - NOST::getUserVoteStatus_output getUserVoteStatus(const id& registerId) const - { - NOST::getUserVoteStatus_input input; - NOST::getUserVoteStatus_output output; - - input.userId = registerId; - callFunction(NOST_CONTRACT_INDEX, 3, input, output); - return output; - } - NOST::checkTokenCreatability_output checkTokenCreatability(uint64 tokenName) const - { - NOST::checkTokenCreatability_input input; - NOST::checkTokenCreatability_output output; - - input.tokenName = tokenName; - callFunction(NOST_CONTRACT_INDEX, 4, input, output); - return output; - } - NOST::getNumberOfInvestedProjects_output getNumberOfInvestedProjects(const id& invsetorId) const - { - NOST::getNumberOfInvestedProjects_input input; - NOST::getNumberOfInvestedProjects_output output; - - input.userId = invsetorId; - callFunction(NOST_CONTRACT_INDEX, 5, input, output); - return output; - } - NOST::getProjectByIndex_output getProjectByIndex(uint32 indexOfProject) const - { - NOST::getProjectByIndex_input input; - NOST::getProjectByIndex_output output; - - input.indexOfProject = indexOfProject; - callFunction(NOST_CONTRACT_INDEX, 6, input, output); - return output; - } - NOST::getFundarasingByIndex_output getFundarasingByIndex(uint32 indexOfFundraising) const - { - NOST::getFundarasingByIndex_input input; - NOST::getFundarasingByIndex_output output; - - input.indexOfFundarasing = indexOfFundraising; - callFunction(NOST_CONTRACT_INDEX, 7, input, output); - return output; - } - NOST::getProjectIndexListByCreator_output getProjectIndexListByCreator(const id& creatorId) const - { - NOST::getProjectIndexListByCreator_input input; - NOST::getProjectIndexListByCreator_output output; - - input.creator = creatorId; - callFunction(NOST_CONTRACT_INDEX, 8, input, output); - return output; - } - NOST::getInfoUserInvested_output getInfoUserInvested(const id& investorId) const - { - NOST::getInfoUserInvested_input input; - NOST::getInfoUserInvested_output output; - - input.investorId = investorId; - callFunction(NOST_CONTRACT_INDEX, 9, input, output); - return output; - } - uint64 getMaxClaimAmount(const id& investorId, uint32 indexOfFundraising) const - { - NOST::getMaxClaimAmount_input input; - NOST::getMaxClaimAmount_output output; - - input.investorId = investorId; - input.indexOfFundraising = indexOfFundraising; - callFunction(NOST_CONTRACT_INDEX, 10, input, output); - return output.amount; - } -}; - -TEST(TestContractNostromo, registerAndLogoutAndUpgradeFromTierChecker) -{ - ContractTestingNostromo nostromoTestCaseA; - - std::map duplicatedUser; - auto registers = getRandomUsers(10000, 10000); - - uint32 countOfRegister = 0, totalPoolWeight = 0; - uint64 totalDepositedQubic = 0, totalLogoutFeeAmount = 0; - - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - uint32 tierLevel = (uint32)random(1, 5); - uint64 depositeAmount, upgradeDeltaDepositeAmount; - switch (tierLevel) - { - case 1: - depositeAmount = NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT; - upgradeDeltaDepositeAmount = NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT - NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT; - totalLogoutFeeAmount += NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT * NOSTROMO_TIER_CHESTBURST_UNSTAKE_FEE / 100; - totalPoolWeight += NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT; - break; - case 2: - depositeAmount = NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT; - upgradeDeltaDepositeAmount = NOSTROMO_TIER_DOG_STAKE_AMOUNT - NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT; - totalLogoutFeeAmount += NOSTROMO_TIER_DOG_STAKE_AMOUNT * NOSTROMO_TIER_DOG_UNSTAKE_FEE / 100; - totalPoolWeight += NOSTROMO_TIER_DOG_POOL_WEIGHT; - break; - case 3: - depositeAmount = NOSTROMO_TIER_DOG_STAKE_AMOUNT; - upgradeDeltaDepositeAmount = NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT - NOSTROMO_TIER_DOG_STAKE_AMOUNT; - totalLogoutFeeAmount += NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT * NOSTROMO_TIER_XENOMORPH_UNSTAKE_FEE / 100; - totalPoolWeight += NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT; - break; - case 4: - depositeAmount = NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT; - upgradeDeltaDepositeAmount = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT - NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT; - totalLogoutFeeAmount += NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT * NOSTROMO_TIER_WARRIOR_UNSTAKE_FEE / 100; - totalPoolWeight += NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; - break; - case 5: - depositeAmount = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT; - totalLogoutFeeAmount += NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT * NOSTROMO_TIER_WARRIOR_UNSTAKE_FEE / 100; - totalPoolWeight += NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; - break; - default: - break; - } - // Register Tier - totalDepositedQubic += depositeAmount; - increaseEnergy(user, depositeAmount); - nostromoTestCaseA.registerInTier(user, tierLevel, depositeAmount); - nostromoTestCaseA.getState()->registerChecker(user, tierLevel, countOfRegister); - // Upgrade Tier - totalDepositedQubic += upgradeDeltaDepositeAmount; - increaseEnergy(user, upgradeDeltaDepositeAmount); - nostromoTestCaseA.upgradeTier(user, tierLevel + 1, upgradeDeltaDepositeAmount); - - if (tierLevel == 5) - { - nostromoTestCaseA.getState()->registerChecker(user, tierLevel, countOfRegister); - } - else - { - nostromoTestCaseA.getState()->registerChecker(user, tierLevel + 1, countOfRegister); - } - - duplicatedUser[user] = 1; - countOfRegister++; - } - nostromoTestCaseA.getState()->countOfRegisterChecker(countOfRegister); - nostromoTestCaseA.getState()->epochRevenueChecker(0); - nostromoTestCaseA.getState()->totalPoolWeightChecker(totalPoolWeight); - EXPECT_EQ(totalDepositedQubic, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - // Logout From Tier - nostromoTestCaseA.logoutFromTier(user); - duplicatedUser[user] = 1; - nostromoTestCaseA.getState()->logoutFromTierChecker(user); - } - EXPECT_EQ(totalLogoutFeeAmount, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - nostromoTestCaseA.getState()->countOfRegisterChecker(0); - nostromoTestCaseA.getState()->epochRevenueChecker(totalLogoutFeeAmount); - nostromoTestCaseA.getState()->totalPoolWeightChecker(0); -} - -TEST(TestContractNostromo, createProjectAndVoteInProjectChecker) -{ - ContractTestingNostromo nostromoTestCaseB; - - auto registers = getRandomUsers(1000, 1000); - - // Register in each Tiers - increaseEnergy(registers[0], NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); - nostromoTestCaseB.registerInTier(registers[0], 1, NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT); - - increaseEnergy(registers[1], NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); - nostromoTestCaseB.registerInTier(registers[1], 2, NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT); - - increaseEnergy(registers[2], NOSTROMO_TIER_DOG_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); - nostromoTestCaseB.registerInTier(registers[2], 3, NOSTROMO_TIER_DOG_STAKE_AMOUNT); - - increaseEnergy(registers[3], NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); - nostromoTestCaseB.registerInTier(registers[3], 4, NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT); - - increaseEnergy(registers[4], NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); - nostromoTestCaseB.registerInTier(registers[4], 5, NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT); - - setMemory(utcTime, 0); - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 12; - utcTime.Hour = 0; - updateQpiTime(); - - uint64 assetName = assetNameFromString("AAAA"); - - // This creation should be failed because there is no qualified to create the project. - nostromoTestCaseB.createProject(registers[0], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(0); - nostromoTestCaseB.getState()->epochRevenueChecker(0); - EXPECT_EQ(getBalance(registers[0]), NOSTROMO_CREATE_PROJECT_FEE); - - // This creation should be failed because there is no qualified to create the project. - assetName = assetNameFromString("BBBB"); - nostromoTestCaseB.createProject(registers[1], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(0); - nostromoTestCaseB.getState()->epochRevenueChecker(0); - EXPECT_EQ(getBalance(registers[1]), NOSTROMO_CREATE_PROJECT_FEE); - - // This creation should be failed because there is no qualified to create the project. - assetName = assetNameFromString("CCCC"); - nostromoTestCaseB.createProject(registers[2], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(0); - nostromoTestCaseB.getState()->epochRevenueChecker(0); - EXPECT_EQ(getBalance(registers[2]), NOSTROMO_CREATE_PROJECT_FEE); - - - //This creation should be succeed because there is a qualified to create the project. - assetName = assetNameFromString("DDDD"); - nostromoTestCaseB.createProject(registers[3], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(1); - nostromoTestCaseB.getState()->epochRevenueChecker(NOSTROMO_CREATE_PROJECT_FEE); - nostromoTestCaseB.getState()->createdProjectChecker(0, registers[3], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - EXPECT_EQ(getBalance(registers[3]), 0); - - // This creation should be succeed because there is a qualified to create the project. - assetName = assetNameFromString("EEEE"); - nostromoTestCaseB.createProject(registers[4], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(2); - nostromoTestCaseB.getState()->epochRevenueChecker(NOSTROMO_CREATE_PROJECT_FEE * 2); - nostromoTestCaseB.getState()->createdProjectChecker(1, registers[4], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - EXPECT_EQ(getBalance(registers[4]), 0); - - // checkTokenCreatability function checker - EXPECT_EQ(nostromoTestCaseB.checkTokenCreatability(assetName).result, 1); - assetName = assetNameFromString("ABCD"); - EXPECT_EQ(nostromoTestCaseB.checkTokenCreatability(assetName).result, 0); - - setMemory(utcTime, 0); - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 13; - utcTime.Hour = 0; - updateQpiTime(); - - Array votedList; - - nostromoTestCaseB.voteInProject(registers[0], 0, 0); - votedList.set(0, 0); - nostromoTestCaseB.voteInProject(registers[1], 0, 1); - nostromoTestCaseB.voteInProject(registers[2], 0, 1); - nostromoTestCaseB.voteInProject(registers[3], 0, 1); - nostromoTestCaseB.voteInProject(registers[4], 0, 0); - - nostromoTestCaseB.getState()->voteInProjectChecker(0, 3, 2); - nostromoTestCaseB.getState()->numberOfVotedProjectAndVotedListChecker(registers[0], 1, votedList); - - // This vote should be failed. - nostromoTestCaseB.voteInProject(registers[0], 0, 0); - nostromoTestCaseB.getState()->voteInProjectChecker(0, 3, 2); - nostromoTestCaseB.getState()->numberOfVotedProjectAndVotedListChecker(registers[0], 1, votedList); - - // This vote should be succeed. - nostromoTestCaseB.voteInProject(registers[0], 1, 0); - votedList.set(1, 1); - nostromoTestCaseB.getState()->voteInProjectChecker(1, 0, 1); - nostromoTestCaseB.getState()->numberOfVotedProjectAndVotedListChecker(registers[0], 2, votedList); - - nostromoTestCaseB.voteInProject(registers[1], 1, 1); - nostromoTestCaseB.voteInProject(registers[2], 1, 1); - nostromoTestCaseB.voteInProject(registers[3], 1, 1); - nostromoTestCaseB.voteInProject(registers[4], 1, 1); - nostromoTestCaseB.getState()->voteInProjectChecker(1, 4, 1); -} - -TEST(TestContractNostromo, createFundraisingAndInvestInProjectAndClaimTokenChecker) -{ - uint64 epochRevenu_t = 0; - uint32 numberOfCreatedProject_t = 0; - uint32 numberOfFundraising_t = 0;; - - ContractTestingNostromo nostromoTestCaseC; - - auto registers = getRandomUsers(10000, 10000); - - setMemory(utcTime, 0); - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 11; - utcTime.Hour = 0; - updateQpiTime(); - - increaseEnergy(registers[0], NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE + NOSTROMO_QX_TOKEN_ISSUANCE_FEE); - nostromoTestCaseC.registerInTier(registers[0], 5, NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT); - uint64 assetName = assetNameFromString("GGGG"); - nostromoTestCaseC.createProject(registers[0], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); - - // getProjectByIndex function Checker - NOST::getProjectByIndex_output getProjectByIndex_output = nostromoTestCaseC.getProjectByIndex(0); - - EXPECT_EQ(getProjectByIndex_output.project.creator, registers[0]); - uint32 tmpDate; - NOST::packNostromoDate(25, 6, 15, 0, 0, 0, tmpDate); - EXPECT_EQ(getProjectByIndex_output.project.endDate , tmpDate); - EXPECT_EQ(getProjectByIndex_output.project.isCreatedFundarasing , 0); - EXPECT_EQ(getProjectByIndex_output.project.numberOfNo, 0); - EXPECT_EQ(getProjectByIndex_output.project.numberOfYes, 0); - NOST::packNostromoDate(25, 6, 13, 0, 0, 0, tmpDate); - EXPECT_EQ(getProjectByIndex_output.project.startDate, tmpDate); - EXPECT_EQ(getProjectByIndex_output.project.supplyOfToken, 21000000); - EXPECT_EQ(getProjectByIndex_output.project.tokenName, assetName); - - numberOfCreatedProject_t++; - epochRevenu_t += 100000000; - - std::map duplicatedUser; - uint64 totalPoolWeight = NOSTROMO_TIER_WARRIOR_POOL_WEIGHT, totalDepositedQubic = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT; - uint32 countOfRegister = 0; - - for (const auto& user : registers) - { - if (countOfRegister == 0) - { - countOfRegister++; - continue; - } - - if (duplicatedUser[user]) - { - continue; - } - uint8 tierLevel = (uint8)random(1, 5); - uint64 depositeAmount, userPoolWeight; - switch (tierLevel) - { - case 1: - depositeAmount = NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT; - totalPoolWeight += NOSTROMO_TIER_FACEHUGGER_POOL_WEIGHT; - userPoolWeight = NOSTROMO_TIER_FACEHUGGER_POOL_WEIGHT; - break; - case 2: - depositeAmount = NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT; - totalPoolWeight += NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT; - userPoolWeight = NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT; - break; - case 3: - depositeAmount = NOSTROMO_TIER_DOG_STAKE_AMOUNT; - totalPoolWeight += NOSTROMO_TIER_DOG_POOL_WEIGHT; - userPoolWeight = NOSTROMO_TIER_DOG_POOL_WEIGHT; - break; - case 4: - depositeAmount = NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT; - totalPoolWeight += NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT; - userPoolWeight = NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT; - break; - case 5: - depositeAmount = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT; - totalPoolWeight += NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; - userPoolWeight = NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; - break; - default: - break; - } - - // Register Tier - totalDepositedQubic += depositeAmount; - increaseEnergy(user, depositeAmount); - nostromoTestCaseC.registerInTier(user, tierLevel, depositeAmount); - - duplicatedUser[user] = 1; - countOfRegister++; - - // getTierLevelByUser function Checker - EXPECT_EQ(nostromoTestCaseC.getTierLevelByUser(user).tierLevel, tierLevel); - } - - // Vote in Project - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 14; - utcTime.Hour = 0; - updateQpiTime(); - - uint32 Ynumber = 0, Nnumber = 0; - duplicatedUser.clear(); - - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - bit decision = (bit)random(0, 3); - if (decision) - { - Ynumber++; - } - else - { - Nnumber++; - } - - nostromoTestCaseC.voteInProject(user, 0, decision); - duplicatedUser[user] = 1; - } - nostromoTestCaseC.getState()->voteInProjectChecker(0, Ynumber, Nnumber); - - // Create the Fundraising - // This fundraising should not be created because the voting is not finished yet. - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 14; - utcTime.Hour = 0; - updateQpiTime(); - - nostromoTestCaseC.createFundraising(registers[0], 100, 2000000, 150000000, 0, - 25, 6, 17, 0, - 25, 6, 25, 0, - 25, 6, 28, 0, - 25, 7, 1, 0, - 25, 7, 10, 0, - 25, 7, 15, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12); - - nostromoTestCaseC.getState()->countOfFundraisingChecker(0); - - // It should be created. - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 16; - utcTime.Hour = 0; - updateQpiTime(); - - nostromoTestCaseC.createFundraising(registers[0], 100000, 2000000, 150000000000, 0, - 25, 6, 17, 0, - 25, 6, 25, 0, - 25, 6, 28, 0, - 25, 7, 1, 0, - 25, 7, 10, 0, - 25, 7, 15, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12); - numberOfFundraising_t++; - - nostromoTestCaseC.getState()->countOfFundraisingChecker(1); - nostromoTestCaseC.getState()->createFundraisingChecker(registers[0], 100000, 2000000, 150000000000, 0, - 25, 6, 17, 0, - 25, 6, 25, 0, - 25, 6, 28, 0, - 25, 7, 1, 0, - 25, 7, 10, 0, - 25, 7, 15, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12, 0); - - // getFundarasingByIndex function checker - NOST::getFundarasingByIndex_output getFundarasingByIndex_output = nostromoTestCaseC.getFundarasingByIndex(0); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.indexOfProject, 0); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.isCreatedToken, 0); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.raisedFunds, 0); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.requiredFunds, 150000000000); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.soldAmount, 2000000); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.stepOfVesting, 12); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.TGE, 10); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.threshold, 20); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.tokenPrice, 100000); - NOST::packNostromoDate(25, 6, 17, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.firstPhaseStartDate, tmpDate); - NOST::packNostromoDate(25, 6, 25, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.firstPhaseEndDate, tmpDate); - NOST::packNostromoDate(25, 6, 28, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.secondPhaseStartDate, tmpDate); - NOST::packNostromoDate(25, 7, 1, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.secondPhaseEndDate, tmpDate); - NOST::packNostromoDate(25, 7, 10, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.thirdPhaseStartDate, tmpDate); - NOST::packNostromoDate(25, 7, 15, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.thirdPhaseEndDate, tmpDate); - NOST::packNostromoDate(25, 7, 25, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.listingStartDate, tmpDate); - NOST::packNostromoDate(25, 7, 27, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.cliffEndDate, tmpDate); - NOST::packNostromoDate(26, 7, 27, 0, 0, 0, tmpDate); - EXPECT_EQ(getFundarasingByIndex_output.fundarasing.vestingEndDate, tmpDate); - - // Phase 1 Investment - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 17; - utcTime.Hour = 1; - updateQpiTime(); - - uint64 facehuggerMaxInvestAmount = 180000000000 * NOSTROMO_TIER_FACEHUGGER_POOL_WEIGHT / totalPoolWeight; - uint64 chestburstMaxInvestAmount = 180000000000 * NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT / totalPoolWeight; - uint64 dogMaxInvestAmount = 180000000000 * NOSTROMO_TIER_DOG_POOL_WEIGHT / totalPoolWeight; - uint64 xenomorphMaxInvestAmount = 180000000000 * NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT / totalPoolWeight; - uint64 warriorMaxInvestAmount = 180000000000 * NOSTROMO_TIER_WARRIOR_POOL_WEIGHT / totalPoolWeight; - - uint64 totalInvestedAmount = 0; - duplicatedUser.clear(); - uint32 ct = 0; - uint32 overDeposit = 1000; // it should be ignored - uint64 originalSCBalance = getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0)); - - std::map investedAmountMP; - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - ct++; - continue; - } - ct++; - increaseEnergy(user, 180000000000); - uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); - - if (ct >= 4000) - { - // Phase 2 Investment - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 29; - utcTime.Hour = 0; - updateQpiTime(); - } - - switch (tierLevel) - { - case 1: - if (ct < 4000) - { - totalInvestedAmount += facehuggerMaxInvestAmount; - investedAmountMP[user] += facehuggerMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 0, facehuggerMaxInvestAmount + overDeposit); - break; - case 2: - if (ct < 4000) - { - totalInvestedAmount += chestburstMaxInvestAmount; - investedAmountMP[user] += chestburstMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 0, chestburstMaxInvestAmount + overDeposit); - break; - case 3: - if (ct < 4000) - { - totalInvestedAmount += dogMaxInvestAmount; - investedAmountMP[user] += dogMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 0, dogMaxInvestAmount + overDeposit); - break; - case 4: - totalInvestedAmount += xenomorphMaxInvestAmount; - investedAmountMP[user] += xenomorphMaxInvestAmount; - nostromoTestCaseC.investInProject(user, 0, xenomorphMaxInvestAmount + overDeposit); - break; - case 5: - totalInvestedAmount += warriorMaxInvestAmount; - investedAmountMP[user] += warriorMaxInvestAmount; - nostromoTestCaseC.investInProject(user, 0, warriorMaxInvestAmount + overDeposit); - break; - - default: - break; - } - - duplicatedUser[user] = 1; - } - - nostromoTestCaseC.getState()->totalRaisedFundChecker(0, totalInvestedAmount, assetName); - EXPECT_EQ(originalSCBalance + totalInvestedAmount - NOSTROMO_QX_TOKEN_ISSUANCE_FEE, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - - // Phase 3 Investment - utcTime.Year = 2025; - utcTime.Month = 7; - utcTime.Day = 11; - utcTime.Hour = 0; - updateQpiTime(); - - uint64 amount = 10000000; - duplicatedUser.clear(); - ct = 0; - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - ct++; - uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); - increaseEnergy(user, amount); - nostromoTestCaseC.investInProject(user, 0, amount); - if (totalInvestedAmount + amount < 180000000000) - { - totalInvestedAmount += amount; - investedAmountMP[user] += amount; - - // getNumberOfInvestedProjects function checker - NOST::getNumberOfInvestedProjects_output getNumberOfInvestedProjects_output = nostromoTestCaseC.getNumberOfInvestedProjects(user); - - EXPECT_EQ(getNumberOfInvestedProjects_output.numberOfInvestedProjects, 1); - } - duplicatedUser[user] = 1; - } - - nostromoTestCaseC.getState()->totalRaisedFundChecker(0, totalInvestedAmount, assetName); - EXPECT_EQ(originalSCBalance + totalInvestedAmount - NOSTROMO_QX_TOKEN_ISSUANCE_FEE, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - - // getMaxClaimAmount function checker - utcTime.Year = 2025; - utcTime.Month = 7; - utcTime.Day = 26; - utcTime.Hour = 0; - updateQpiTime(); - - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - EXPECT_EQ(nostromoTestCaseC.getMaxClaimAmount(user, 0), investedAmountMP[user] / 100000 * 10 / 100); - - duplicatedUser[user] = 1; - } - - utcTime.Year = 2025; - utcTime.Month = 8; - utcTime.Day = 5; - utcTime.Hour = 0; - updateQpiTime(); - - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - EXPECT_EQ(nostromoTestCaseC.getMaxClaimAmount(user, 0), investedAmountMP[user] / 100000 * (10 + 7) / 100); - - duplicatedUser[user] = 1; - } - - utcTime.Year = 2026; - utcTime.Month = 8; - utcTime.Day = 5; - utcTime.Hour = 0; - updateQpiTime(); - - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - EXPECT_EQ(nostromoTestCaseC.getMaxClaimAmount(user, 0), investedAmountMP[user] / 100000); - - duplicatedUser[user] = 1; - } - - // claimToken Checker - std::map claimedAmountMP; - for (uint32 i = 1; i <= 12; i++) - { - if (i >= 6) - { - utcTime.Year = 2026; - } - utcTime.Month = (7 + i) % 12; - if (utcTime.Month == 0) utcTime.Month = 12; - utcTime.Day = 5; - utcTime.Hour = 0; - updateQpiTime(); - - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - uint64 investedAmount = nostromoTestCaseC.getState()->getInvestedAmount(0, user); - uint64 claimAmount = investedAmount / 100000 / 12; - claimedAmountMP[user] += nostromoTestCaseC.claimToken(user, claimAmount, 0); - - duplicatedUser[user] = 1; - } - } - - // getInfoUserInvested function checker - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - duplicatedUser[user] = 1; - - NOST::getInfoUserInvested_output getInfoUserInvested_output = nostromoTestCaseC.getInfoUserInvested(user); - EXPECT_EQ(getInfoUserInvested_output.listUserInvested.get(0).indexOfFundraising, 0); - EXPECT_EQ(getInfoUserInvested_output.listUserInvested.get(0).investedAmount, investedAmountMP[user]); - EXPECT_EQ(getInfoUserInvested_output.listUserInvested.get(0).claimedAmount, claimedAmountMP[user]); - } - - // Checking to remove element after claiming the max amount - utcTime.Year = 2026; - utcTime.Month = 8; - utcTime.Day = 5; - utcTime.Hour = 0; - updateQpiTime(); - - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - uint64 claimAmount = nostromoTestCaseC.getMaxClaimAmount(user, 0) - claimedAmountMP[user]; - claimedAmountMP[user] += nostromoTestCaseC.claimToken(user, claimAmount, 0); - - duplicatedUser[user] = 1; - - nostromoTestCaseC.getState()->removeElementAfterClaimChecker(user); - } - - ct = 0; - duplicatedUser.clear(); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - if (ct == 0) - { - EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), user, user, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX) - 19000000, claimedAmountMP[user]); - } - else - { - EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), user, user, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX), claimedAmountMP[user]); - } - ct++; - duplicatedUser[user] = 1; - } - - // transferShareManagementRights Checker - increaseEnergy(registers[0], 1000000); - - Asset asset; - asset.assetName = assetName; - asset.issuer = id(NOST_CONTRACT_INDEX, 0, 0, 0); - EXPECT_EQ(nostromoTestCaseC.TransferShareManagementRights(registers[0], asset, 10000, QX_CONTRACT_INDEX), 10000); - EXPECT_EQ(numberOfPossessedShares(asset.assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), registers[0], registers[0], QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 10000); - - // EndEpochSucceedFundraising Checker - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 20; - utcTime.Hour = 0; - updateQpiTime(); - - increaseEnergy(registers[0], NOSTROMO_CREATE_PROJECT_FEE); - assetName = assetNameFromString("AAAA"); - nostromoTestCaseC.createProject(registers[0], assetName, 21000000, 25, 6, 22, 0, 25, 6, 25, 0); - numberOfCreatedProject_t++; - epochRevenu_t += 100000000; - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 23; - utcTime.Hour = 0; - updateQpiTime(); - - Ynumber = 0; Nnumber = 0; - duplicatedUser.clear(); - - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - bit decision = (bit)random(0, 3); - if (decision) - { - Ynumber++; - } - else - { - Nnumber++; - } - - nostromoTestCaseC.voteInProject(user, 1, decision); - duplicatedUser[user] = 1; - - // getUserVoteStatus function Checker - NOST::getUserVoteStatus_output getUserVoteStatus_output = nostromoTestCaseC.getUserVoteStatus(user); - EXPECT_EQ(getUserVoteStatus_output.numberOfVotedProjects, 2); - EXPECT_EQ(getUserVoteStatus_output.projectIndexList.get(0), 0); - EXPECT_EQ(getUserVoteStatus_output.projectIndexList.get(1), 1); - } - nostromoTestCaseC.getState()->voteInProjectChecker(1, Ynumber, Nnumber); - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 26; - utcTime.Hour = 0; - updateQpiTime(); - increaseEnergy(registers[0], NOSTROMO_QX_TOKEN_ISSUANCE_FEE); - - nostromoTestCaseC.createFundraising(registers[0], 100000, 2000000, 150000000000, 1, - 25, 6, 27, 0, - 25, 7, 5, 0, - 25, 7, 8, 0, - 25, 7, 10, 0, - 25, 7, 20, 0, - 25, 7, 23, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12); - numberOfFundraising_t++; - - nostromoTestCaseC.getState()->countOfFundraisingChecker(2); - nostromoTestCaseC.getState()->createFundraisingChecker(registers[0], 100000, 2000000, 150000000000, 1, - 25, 6, 27, 0, - 25, 7, 5, 0, - 25, 7, 8, 0, - 25, 7, 10, 0, - 25, 7, 20, 0, - 25, 7, 23, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12, 1); - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 27; - utcTime.Hour = 1; - updateQpiTime(); - - uint64 totalInvestedAmount_2 = 0; - duplicatedUser.clear(); - ct = 0; - originalSCBalance = getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0)); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - ct++; - continue; - } - ct++; - increaseEnergy(user, 180000000000); - uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); - - if (ct >= 4000) - { - - // Phase 2 Investment - utcTime.Year = 2025; - utcTime.Month = 7; - utcTime.Day = 9; - utcTime.Hour = 0; - updateQpiTime(); - } - - switch (tierLevel) - { - case 1: - if (ct < 4000) - { - totalInvestedAmount_2 += facehuggerMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 1, facehuggerMaxInvestAmount); - break; - case 2: - if (ct < 4000) - { - totalInvestedAmount_2 += chestburstMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 1, chestburstMaxInvestAmount); - break; - case 3: - if (ct < 4000) - { - totalInvestedAmount_2 += dogMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 1, dogMaxInvestAmount); - break; - case 4: - totalInvestedAmount_2 += xenomorphMaxInvestAmount; - nostromoTestCaseC.investInProject(user, 1, xenomorphMaxInvestAmount); - break; - case 5: - totalInvestedAmount_2 += warriorMaxInvestAmount; - nostromoTestCaseC.investInProject(user, 1, warriorMaxInvestAmount); - break; - - default: - break; - } - - duplicatedUser[user] = 1; - } - - nostromoTestCaseC.getState()->totalRaisedFundChecker(1, totalInvestedAmount_2, assetName); - EXPECT_EQ(originalSCBalance + totalInvestedAmount_2 - NOSTROMO_QX_TOKEN_ISSUANCE_FEE, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - - // getStats function Checker - nostromoTestCaseC.getState()->getStatsChecker(epochRevenu_t, totalPoolWeight, numberOfCreatedProject_t, numberOfFundraising_t, countOfRegister); - - utcTime.Year = 2025; - utcTime.Month = 7; - utcTime.Day = 24; - utcTime.Hour = 0; - updateQpiTime(); - - uint64 originalCreatorBalance = getBalance(registers[0]); - nostromoTestCaseC.endEpoch(); - EXPECT_EQ(getBalance(registers[0]) - originalCreatorBalance, totalInvestedAmount - div(totalInvestedAmount * 5, 100ULL) + totalInvestedAmount_2 - div(totalInvestedAmount_2 * 5, 100ULL)); - nostromoTestCaseC.getState()->endEpochSucceedFundraisingChecker(registers[0], 1, totalInvestedAmount_2, originalCreatorBalance, assetName); - - // EndEpochFailedFundraising Checker - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 20; - utcTime.Hour = 0; - updateQpiTime(); - - increaseEnergy(registers[0], NOSTROMO_CREATE_PROJECT_FEE); - assetName = assetNameFromString("BBBB"); - nostromoTestCaseC.createProject(registers[0], assetName, 21000000, 25, 6, 22, 0, 25, 6, 25, 0); - numberOfCreatedProject_t++; - epochRevenu_t += 100000000; - - // getProjectIndexListByCreator function checker - NOST::getProjectIndexListByCreator_output getProjectIndexListByCreator_output = nostromoTestCaseC.getProjectIndexListByCreator(registers[0]); - for (uint32 i = 0; i < 128; i++) - { - if (i < 3) - { - EXPECT_EQ(getProjectIndexListByCreator_output.indexListForProjects.get(i), i); - } - else { - EXPECT_EQ(getProjectIndexListByCreator_output.indexListForProjects.get(i), 262144); - } - } - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 23; - utcTime.Hour = 0; - updateQpiTime(); - - Ynumber = 0; Nnumber = 0; - duplicatedUser.clear(); - - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - continue; - } - - bit decision = (bit)random(0, 3); - if (decision) - { - Ynumber++; - } - else - { - Nnumber++; - } - - nostromoTestCaseC.voteInProject(user, 2, decision); - duplicatedUser[user] = 1; - } - nostromoTestCaseC.getState()->voteInProjectChecker(2, Ynumber, Nnumber); - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 26; - utcTime.Hour = 0; - updateQpiTime(); - increaseEnergy(registers[0], NOSTROMO_QX_TOKEN_ISSUANCE_FEE); - - nostromoTestCaseC.createFundraising(registers[0], 100000, 2000000, 150000000000, 2, - 25, 6, 27, 0, - 25, 7, 5, 0, - 25, 7, 8, 0, - 25, 7, 10, 0, - 25, 7, 20, 0, - 25, 7, 23, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12); - numberOfFundraising_t++; - - nostromoTestCaseC.getState()->countOfFundraisingChecker(3); - nostromoTestCaseC.getState()->createFundraisingChecker(registers[0], 100000, 2000000, 150000000000, 2, - 25, 6, 27, 0, - 25, 7, 5, 0, - 25, 7, 8, 0, - 25, 7, 10, 0, - 25, 7, 20, 0, - 25, 7, 23, 0, - 25, 7, 25, 0, - 25, 7, 27, 0, - 26, 7, 27, 0, - 20, 10, 12, 2); - - utcTime.Year = 2025; - utcTime.Month = 6; - utcTime.Day = 27; - utcTime.Hour = 1; - updateQpiTime(); - - uint64 totalInvestedAmount_3 = 0; - duplicatedUser.clear(); - ct = 0; - originalSCBalance = getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0)); - for (const auto& user : registers) - { - if (duplicatedUser[user]) - { - ct++; - continue; - } - ct++; - increaseEnergy(user, 180000000000); - uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); - - if (ct >= 4000) - { - - // Phase 2 Investment - utcTime.Year = 2025; - utcTime.Month = 7; - utcTime.Day = 9; - utcTime.Hour = 0; - updateQpiTime(); - } - - bit sg = 0; - switch (tierLevel) - { - case 1: - if (ct < 4000) - { - if (totalInvestedAmount_3 + facehuggerMaxInvestAmount > 120000000000) - { - sg = 1; - break; - } - totalInvestedAmount_3 += facehuggerMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 2, facehuggerMaxInvestAmount); - break; - case 2: - if (ct < 4000) - { - if (totalInvestedAmount_3 + chestburstMaxInvestAmount > 120000000000) - { - sg = 1; - break; - } - totalInvestedAmount_3 += chestburstMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 2, chestburstMaxInvestAmount); - break; - case 3: - if (ct < 4000) - { - if (totalInvestedAmount_3 + dogMaxInvestAmount > 120000000000) - { - sg = 1; - break; - } - totalInvestedAmount_3 += dogMaxInvestAmount; - } - nostromoTestCaseC.investInProject(user, 2, dogMaxInvestAmount); - break; - case 4: - if (totalInvestedAmount_3 + xenomorphMaxInvestAmount > 120000000000) - { - sg = 1; - break; - } - totalInvestedAmount_3 += xenomorphMaxInvestAmount; - nostromoTestCaseC.investInProject(user, 2, xenomorphMaxInvestAmount); - break; - case 5: - if (totalInvestedAmount_3 + warriorMaxInvestAmount > 120000000000) - { - sg = 1; - break; - } - totalInvestedAmount_3 += warriorMaxInvestAmount; - nostromoTestCaseC.investInProject(user, 2, warriorMaxInvestAmount); - break; - - default: - break; - } - - if (sg) - { - break; - } - - duplicatedUser[user] = 1; - } - - nostromoTestCaseC.getState()->totalRaisedFundChecker(2, totalInvestedAmount_3, assetName); - - utcTime.Year = 2025; - utcTime.Month = 7; - utcTime.Day = 24; - utcTime.Hour = 0; - updateQpiTime(); - - originalCreatorBalance = getBalance(registers[0]); - EXPECT_EQ(originalSCBalance + totalInvestedAmount_3, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - - uint64 epochRevenue = nostromoTestCaseC.getState()->getEpochRevenue(); - uint64 teamFee = div(epochRevenue, 10ULL); - epochRevenue -= teamFee; - nostromoTestCaseC.endEpoch(); - - EXPECT_EQ(originalSCBalance + totalInvestedAmount_3 - teamFee - (div(epochRevenue, 676ULL) * 676), getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); - nostromoTestCaseC.getState()->endEpochFailedFundraisingChecker(2); - nostromoTestCaseC.getState()->endEpochVoteStatusClearChecker(); -} diff --git a/test/contract_qbay.cpp b/test/contract_qbay.cpp deleted file mode 100644 index 677a79a9d..000000000 --- a/test/contract_qbay.cpp +++ /dev/null @@ -1,1174 +0,0 @@ -#define NO_UEFI - -#include -#include - -#include "contract_testing.h" - -const id MARKETPLACE_OWNER = ID(_R, _K, _D, _H, _C, _M, _R, _J, _Y, _C, _G, _K, _P, _D, _U, _Y, _R, _X, _G, _D, _Y, _Z, _C, _I, _Z, _I, _T, _A, _H, _Y, _O, _V, _G, _I, _U, _T, _K, _N, _D, _T, _E, _H, _P, _C, _C, _L, _W, _L, _Z, _X, _S, _H, _N, _F, _P, _D); -const id CFB_ISSUER = ID(_C, _F, _B, _M, _E, _M, _Z, _O, _I, _D, _E, _X, _Q, _A, _U, _X, _Y, _Y, _S, _Z, _I, _U, _R, _A, _D, _Q, _L, _A, _P, _W, _P, _M, _N, _J, _X, _Q, _S, _N, _V, _Q, _Z, _A, _H, _Y, _V, _O, _P, _Y, _U, _K, _K, _J, _B, _J, _U, _C); -static constexpr uint64 QBAY_ISSUE_ASSET_FEE = 1000000000ULL; -static constexpr uint64 QBAY_TOKEN_TRANSFER_FEE = 1000000ULL; -static constexpr sint64 QBAY_CREATED_CFB_AMOUNT = 1000000000000ULL; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_2_200 = 100; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_201_1000 = 200; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_1001_2000 = 400; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_2001_3000 = 600; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_3001_4000 = 800; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_4001_5000 = 1000; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_5001_6000 = 1200; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_6001_7000 = 1400; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_7001_8000 = 1600; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_8001_9000 = 1800; -constexpr uint32 QBAY_FEE_COLLECTION_CREATE_9001_10000 = 2000; - -static std::mt19937_64 rand64; - -static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) -{ - if(minValue > maxValue) - { - return 0; - } - return minValue + rand64() % (maxValue - minValue); -} - -static id getUser(unsigned long long i) -{ - return id(i, i / 2 + 4, i + 10, i * 3 + 8); -} - -static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) -{ - unsigned long long userCount = random(0, maxNum); - std::vector users; - users.reserve(userCount); - for (unsigned int i = 0; i < userCount; ++i) - { - unsigned long long userIdx = random(0, totalUsers - 1); - users.push_back(getUser(userIdx)); - } - return users; -} - -static Array getRandomURI() -{ - Array URI; - - for(sint32 i = 0 ; i < 64; i++) - { - uint8 t = (uint8)random(0, 127); - if((t >= 48 && t <= 57) || (t >= 65 && t <= 90) || (t >= 97 && t <= 122)) - { - URI.set(i, t); - continue; - } - i--; - } - - return URI; -} - -class QBAYChecker : public QBAY -{ -public: - - void stateVriableChecker(uint64 gt_priceOfCFB, uint64 gt_priceOfQubic, uint64 gt_numberOfNFTIncoming, uint32 gt_numberOfCollection, uint32 gt_numberOfNFT, bit gt_statusOfMarketPlace) - { - EXPECT_EQ(gt_priceOfCFB, priceOfCFB); - EXPECT_EQ(gt_priceOfQubic, priceOfQubic); - EXPECT_EQ(gt_numberOfNFTIncoming, numberOfNFTIncoming); - EXPECT_EQ(gt_numberOfCollection, numberOfCollection); - EXPECT_EQ(gt_numberOfNFT, numberOfNFT); - EXPECT_EQ(gt_statusOfMarketPlace, statusOfMarketPlace); - } - - void createCollectionChecker(id user, uint64 priceForDropMint, uint32 countOfNFT, uint32 royalty, uint32 maxSizePerOneId, bit typeOfCollection, uint32 countOfCollection, Array& URI) - { - EXPECT_EQ(Collections.get(countOfCollection - 1).creator, user); - EXPECT_EQ(Collections.get(countOfCollection - 1).currentSize, countOfNFT); - EXPECT_EQ(Collections.get(countOfCollection - 1).maxSizeHoldingPerOneId, maxSizePerOneId); - EXPECT_EQ(Collections.get(countOfCollection - 1).priceForDropMint, priceForDropMint); - EXPECT_EQ(Collections.get(countOfCollection - 1).royalty, royalty); - EXPECT_EQ(Collections.get(countOfCollection - 1).typeOfCollection, typeOfCollection); - for(uint8 i = 0; i < 64; i++) - { - if(i >= QBAY_LENGTH_OF_URI) - { - EXPECT_EQ(Collections.get(countOfCollection - 1).URI.get(i), 0); - } - else { - EXPECT_EQ(Collections.get(countOfCollection - 1).URI.get(i), URI.get(i)); - } - } - } - - void mintChecker(id user, uint32 royalty, uint32 collectionId, Array URI, bit typeOfMint, uint32 idOfNFT) - { - EXPECT_EQ(NFTs.get(idOfNFT).creator, user); - EXPECT_EQ(NFTs.get(idOfNFT).possessor, user); - if(typeOfMint == 0) - { - EXPECT_EQ(NFTs.get(idOfNFT).royalty, Collections.get(collectionId).royalty); - } - else { - EXPECT_EQ(NFTs.get(idOfNFT).royalty, royalty); - } - for(uint8 i = 0; i < 64; i++) - { - if(i >= QBAY_LENGTH_OF_URI) - { - EXPECT_EQ(NFTs.get(idOfNFT).URI.get(i), 0); - } - else { - EXPECT_EQ(NFTs.get(idOfNFT).URI.get(i), URI.get(i)); - } - } - } - - void transferChecker(id newUser, uint32 NFTId) - { - EXPECT_EQ(NFTs.get(NFTId).possessor, newUser); - } - - void listInMarketChecker(uint32 NFTId, uint64 price) - { - EXPECT_EQ(NFTs.get(NFTId).statusOfSale, 1); - EXPECT_EQ(NFTs.get(NFTId).salePrice, price); - } - - void buyChecker(id oldPossesor, id newPossessor, uint32 NFTId, uint64 price, bit typfOfPayment, uint64 initialBalanceOfCreator, uint64 initialBalanceOfPossesor, uint64 initialBalanceOfMarket, bit isSameCreatorAndPossesor) - { - EXPECT_EQ(NFTs.get(NFTId).possessor, newPossessor); - if(typfOfPayment == 0) - { - if(isSameCreatorAndPossesor == 1) - { - EXPECT_EQ(initialBalanceOfPossesor + price - div(price * 25ULL, 1000ULL), getBalance(oldPossesor)); - EXPECT_EQ(initialBalanceOfMarket + div(price * 25ULL, 1000ULL), getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0))); - } - else { - EXPECT_EQ(initialBalanceOfCreator + div(price * NFTs.get(NFTId).royalty * 1ULL, 100ULL), getBalance(NFTs.get(NFTId).creator)); - EXPECT_EQ(initialBalanceOfPossesor + price - div(price * (NFTs.get(NFTId).royalty * 10 + 30) * 1ULL, 1000ULL), getBalance(oldPossesor)); - } - } - else - { - if(isSameCreatorAndPossesor == 1) - { - EXPECT_EQ(initialBalanceOfPossesor + price - div(price * 25ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); - EXPECT_EQ(initialBalanceOfMarket + div(price * 25ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); - } - else - { - EXPECT_EQ(initialBalanceOfCreator + div(price * NFTs.get(NFTId).royalty * 1ULL, 100ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, NFTs.get(NFTId).creator, NFTs.get(NFTId).creator, QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, NFTs.get(NFTId).creator, NFTs.get(NFTId).creator, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); - EXPECT_EQ(initialBalanceOfPossesor + price - div(price * NFTs.get(NFTId).royalty * 1ULL, 100ULL) - div(price * 20ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); - EXPECT_EQ(initialBalanceOfMarket + div(price * 20ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); - } - } - } - - void cancelSaleChecker(uint32 NFTId) - { - EXPECT_EQ(NFTs.get(NFTId).salePrice, QBAY_SALE_PRICE); - EXPECT_EQ(NFTs.get(NFTId).statusOfSale, 0); - } - - void listInExchangeChecker(id user, uint32 possessedNFT, uint32 anotherNFT) - { - EXPECT_EQ(NFTs.get(anotherNFT).NFTidForExchange, possessedNFT); - EXPECT_EQ(NFTs.get(anotherNFT).statusOfExchange, 1); - } - - void possesorChecker(id user, uint32 possessedNFT) - { - EXPECT_EQ(NFTs.get(possessedNFT).possessor, user); - } - - void cancelExchangeChecker(uint32 NFTId) - { - EXPECT_EQ(NFTs.get(NFTId).NFTidForExchange, QBAY_MAX_NUMBER_NFT); - EXPECT_EQ(NFTs.get(NFTId).statusOfExchange, 0); - } - - void makeOfferChecker(uint32 NFTId, id user, bit paymentMethod, uint64 askPrice) - { - EXPECT_EQ(NFTs.get(NFTId).askUser, user); - EXPECT_EQ(NFTs.get(NFTId).paymentMethodOfAsk, paymentMethod); - EXPECT_EQ(NFTs.get(NFTId).askMaxPrice, askPrice); - } - - void acceptOfferChecker(id user, uint32 NFTId) - { - EXPECT_LT(NFTs.get(NFTId).askUser, user); - EXPECT_EQ(NFTs.get(NFTId).askMaxPrice, 0); - EXPECT_EQ(NFTs.get(NFTId).statusOfAsk, 0); - } - - void cancelOfferChecker(uint32 NFTId) - { - EXPECT_EQ(NFTs.get(NFTId).askUser, NULL_ID); - EXPECT_EQ(NFTs.get(NFTId).askMaxPrice, 0); - } - - void createAuctionChecker(uint32 NFTId, uint64 price, bit gt_statusOfAuction, bit gt_paymentMethodOfAuction, id gt_creatorOfAuction, uint32 startYear, uint32 startMonth, uint32 startDay, uint32 startHour, uint32 endYear, uint32 endMonth, uint32 endDay, uint32 endHour) - { - uint32 startTime, endTime; - QBAY::packQbayDate(startYear, startMonth, startDay, startHour, 0, 0, startTime); - QBAY::packQbayDate(endYear, endMonth, endDay, endHour, 0, 0, endTime); - EXPECT_EQ(NFTs.get(NFTId).startTimeOfAuction, startTime); - EXPECT_EQ(NFTs.get(NFTId).endTimeOfAuction, endTime); - EXPECT_EQ(NFTs.get(NFTId).currentPriceOfAuction, price); - EXPECT_EQ(NFTs.get(NFTId).statusOfAuction, gt_statusOfAuction); - EXPECT_EQ(NFTs.get(NFTId).paymentMethodOfAuction, gt_paymentMethodOfAuction); - EXPECT_EQ(NFTs.get(NFTId).creatorOfAuction, gt_creatorOfAuction); - } - - void bidOnAuctionChecker(id user, uint32 NFTId, uint64 price) - { - EXPECT_EQ(NFTs.get(NFTId).possessor, user); - EXPECT_EQ(NFTs.get(NFTId).currentPriceOfAuction, price); - EXPECT_EQ(NFTs.get(NFTId).statusOfAuction, 2); - } - - void profitChecker(uint64 amountOfQubic, uint64 amountOfCFB) - { - EXPECT_EQ(amountOfQubic, earnedQubic); - EXPECT_EQ(amountOfCFB, earnedCFB); - } - - void getNumberOfNFTForUserChecker(getNumberOfNFTForUser_output output, uint32 numberOfNFT) - { - EXPECT_EQ(output.numberOfNFT, numberOfNFT); - } - - void getInfoOfNFTUserPossessedChecker(getInfoOfNFTUserPossessed_output output, uint32 idOfNFT) - { - EXPECT_EQ(output.creator, NFTs.get(idOfNFT).creator); - EXPECT_EQ(output.possessor, NFTs.get(idOfNFT).possessor); - EXPECT_EQ(output.askMaxPrice, NFTs.get(idOfNFT).askMaxPrice); - EXPECT_EQ(output.askUser, NFTs.get(idOfNFT).askUser); - EXPECT_EQ(output.creatorOfAuction, NFTs.get(idOfNFT).creatorOfAuction); - EXPECT_EQ(output.currentPriceOfAuction, NFTs.get(idOfNFT).currentPriceOfAuction); - EXPECT_EQ(output.statusOfSale, NFTs.get(idOfNFT).statusOfSale); - EXPECT_EQ(output.statusOfAsk, NFTs.get(idOfNFT).statusOfAsk); - EXPECT_EQ(output.statusOfAuction, NFTs.get(idOfNFT).statusOfAuction); - EXPECT_EQ(output.statusOfExchange, NFTs.get(idOfNFT).statusOfExchange); - EXPECT_EQ(output.royalty, NFTs.get(idOfNFT).royalty); - EXPECT_EQ(output.salePrice, NFTs.get(idOfNFT).salePrice); - EXPECT_EQ(output.NFTidForExchange, NFTs.get(idOfNFT).NFTidForExchange); - EXPECT_EQ(output.paymentMethodOfAsk, NFTs.get(idOfNFT).paymentMethodOfAsk); - for(uint32 i = 0 ; i < 64; i++) - { - EXPECT_EQ(output.URI.get(i), NFTs.get(idOfNFT).URI.get(i)); - } - } - - void getInfoOfMarketplaceChecker(getInfoOfMarketplace_output output) - { - EXPECT_EQ(output.earnedCFB, earnedCFB); - EXPECT_EQ(output.earnedQubic, earnedQubic); - EXPECT_EQ(output.numberOfCollection, numberOfCollection); - EXPECT_EQ(output.numberOfNFT, numberOfNFT); - EXPECT_EQ(output.numberOfNFTIncoming, numberOfNFTIncoming); - EXPECT_EQ(output.priceOfCFB, priceOfCFB); - EXPECT_EQ(output.priceOfQubic, priceOfQubic); - EXPECT_EQ(output.statusOfMarketPlace, statusOfMarketPlace); - } - - void getInfoOfCollectionByCreatorChecker(getInfoOfCollectionByCreator_output output, uint32 idOfCollection) - { - EXPECT_EQ(output.currentSize, Collections.get(idOfCollection).currentSize); - EXPECT_EQ(output.idOfCollection, idOfCollection); - EXPECT_EQ(output.maxSizeHoldingPerOneId, Collections.get(idOfCollection).maxSizeHoldingPerOneId); - EXPECT_EQ(output.priceForDropMint, Collections.get(idOfCollection).priceForDropMint); - EXPECT_EQ(output.royalty, Collections.get(idOfCollection).royalty); - EXPECT_EQ(output.typeOfCollection, Collections.get(idOfCollection).typeOfCollection); - for(uint32 i = 0 ; i < 64; i++) - { - EXPECT_EQ(output.URI.get(i), Collections.get(idOfCollection).URI.get(i)); - } - } - - void getInfoOfCollectionByIdChecker(getInfoOfCollectionById_output output, uint32 idOfCollection) - { - EXPECT_EQ(output.creator, Collections.get(idOfCollection).creator); - EXPECT_EQ(output.currentSize, Collections.get(idOfCollection).currentSize); - EXPECT_EQ(output.maxSizeHoldingPerOneId, Collections.get(idOfCollection).maxSizeHoldingPerOneId); - EXPECT_EQ(output.priceForDropMint, Collections.get(idOfCollection).priceForDropMint); - EXPECT_EQ(output.royalty, Collections.get(idOfCollection).royalty); - EXPECT_EQ(output.typeOfCollection, Collections.get(idOfCollection).typeOfCollection); - for(uint32 i = 0 ; i < 64; i++) - { - EXPECT_EQ(output.URI.get(i), Collections.get(idOfCollection).URI.get(i)); - } - } - - void getIncomingAuctionsChecker(getIncomingAuctions_output output, uint32 offset, uint32 count) - { - for(uint32 i = offset, k = 0; i < count; i++, k++) - { - EXPECT_EQ(NFTs.get(output.NFTId.get(k)).statusOfAuction, 1); - } - } - - void getInfoOfNFTByIdChecker(getInfoOfNFTById_output output, uint32 idOfNFT) - { - EXPECT_EQ(output.creator, NFTs.get(idOfNFT).creator); - EXPECT_EQ(output.possessor, NFTs.get(idOfNFT).possessor); - EXPECT_EQ(output.askMaxPrice, NFTs.get(idOfNFT).askMaxPrice); - EXPECT_EQ(output.askUser, NFTs.get(idOfNFT).askUser); - EXPECT_EQ(output.creatorOfAuction, NFTs.get(idOfNFT).creatorOfAuction); - EXPECT_EQ(output.currentPriceOfAuction, NFTs.get(idOfNFT).currentPriceOfAuction); - EXPECT_EQ(output.statusOfSale, NFTs.get(idOfNFT).statusOfSale); - EXPECT_EQ(output.statusOfAsk, NFTs.get(idOfNFT).statusOfAsk); - EXPECT_EQ(output.statusOfAuction, NFTs.get(idOfNFT).statusOfAuction); - EXPECT_EQ(output.statusOfExchange, NFTs.get(idOfNFT).statusOfExchange); - EXPECT_EQ(output.royalty, NFTs.get(idOfNFT).royalty); - EXPECT_EQ(output.salePrice, NFTs.get(idOfNFT).salePrice); - EXPECT_EQ(output.NFTidForExchange, NFTs.get(idOfNFT).NFTidForExchange); - EXPECT_EQ(output.paymentMethodOfAsk, NFTs.get(idOfNFT).paymentMethodOfAsk); - for(uint32 i = 0 ; i < 64; i++) - { - EXPECT_EQ(output.URI.get(i), NFTs.get(idOfNFT).URI.get(i)); - } - } - - uint64 getDropMintPrice(uint32 collectionId) - { - return Collections.get(collectionId).priceForDropMint; - } - - id getCreatorOfNFT(uint32 NFTId) - { - return NFTs.get(NFTId).creator; - } - - id getPossessorOfNFT(uint32 NFTId) - { - return NFTs.get(NFTId).possessor; - } - -}; - -class ContractTestingQBAY : protected ContractTesting -{ -public: - ContractTestingQBAY() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QBAY); - callSystemProcedure(QBAY_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - ~ContractTestingQBAY() - { - } - - QBAYChecker* getState() - { - return (QBAYChecker*)contractStates[QBAY_CONTRACT_INDEX]; - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QBAY_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - QBAY::getNumberOfNFTForUser_output getNumberOfNFTForUser(id user) const - { - QBAY::getNumberOfNFTForUser_input input; - QBAY::getNumberOfNFTForUser_output output; - - input.user = user; - - callFunction(QBAY_CONTRACT_INDEX, 1, input, output); - return output; - } - - QBAY::getInfoOfNFTUserPossessed_output getInfoOfNFTUserPossessed(id user, uint32 NFTNumber) const - { - QBAY::getInfoOfNFTUserPossessed_input input; - QBAY::getInfoOfNFTUserPossessed_output output; - - input.user = user; - input.NFTNumber = NFTNumber; - - callFunction(QBAY_CONTRACT_INDEX, 2, input, output); - return output; - } - - QBAY::getInfoOfMarketplace_output getInfoOfMarketplace() const - { - QBAY::getInfoOfMarketplace_input input; - QBAY::getInfoOfMarketplace_output output; - - callFunction(QBAY_CONTRACT_INDEX, 3, input, output); - return output; - } - - QBAY::getInfoOfCollectionByCreator_output getInfoOfCollectionByCreator(id creator, uint32 orderOfCollection) const - { - QBAY::getInfoOfCollectionByCreator_input input; - QBAY::getInfoOfCollectionByCreator_output output; - - input.creator = creator; - input.orderOfCollection = orderOfCollection; - - callFunction(QBAY_CONTRACT_INDEX, 4, input, output); - return output; - } - - QBAY::getInfoOfCollectionById_output getInfoOfCollectionById(uint32 idOfCollection) const - { - QBAY::getInfoOfCollectionById_input input; - QBAY::getInfoOfCollectionById_output output; - - input.idOfCollection = idOfCollection; - - callFunction(QBAY_CONTRACT_INDEX, 5, input, output); - return output; - } - - QBAY::getIncomingAuctions_output getIncomingAuctions(uint32 offset, uint32 count) const - { - QBAY::getIncomingAuctions_input input; - QBAY::getIncomingAuctions_output output; - - input.count = count; - input.offset = offset; - - callFunction(QBAY_CONTRACT_INDEX, 6, input, output); - return output; - } - - QBAY::getInfoOfNFTById_output getInfoOfNFTById(uint32 NFTId) const - { - QBAY::getInfoOfNFTById_input input; - QBAY::getInfoOfNFTById_output output; - - input.NFTId = NFTId; - - callFunction(QBAY_CONTRACT_INDEX, 7, input, output); - return output; - } - - QBAY::getUserCreatedCollection_output getUserCreatedCollection(id user, uint32 offset, uint32 count) const - { - QBAY::getUserCreatedCollection_input input; - QBAY::getUserCreatedCollection_output output; - - input.user = user; - input.offset = offset; - input.count = count; - - callFunction(QBAY_CONTRACT_INDEX, 8, input, output); - return output; - } - - QBAY::getUserCreatedNFT_output getUserCreatedNFT(id user, uint32 offset, uint32 count) const - { - QBAY::getUserCreatedNFT_input input; - QBAY::getUserCreatedNFT_output output; - - input.user = user; - input.offset = offset; - input.count = count; - - callFunction(QBAY_CONTRACT_INDEX, 9, input, output); - return output; - } - - QBAY::settingCFBAndQubicPrice_output settingCFBAndQubicPrice(const id& marketOwnerAdress, uint64 CFBPrice, uint64 QubicPrice) - { - QBAY::settingCFBAndQubicPrice_input input; - QBAY::settingCFBAndQubicPrice_output output; - - input.CFBPrice = CFBPrice; - input.QubicPrice = QubicPrice; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 1, input, output, marketOwnerAdress, 0); - - return output; - } - - QBAY::createCollection_output createCollection(const id& user, uint64 priceForDropMint, uint32 volume, uint32 royalty, uint32 maxSizePerOneId, bit typeOfCollection, Array& URI) - { - QBAY::createCollection_input input; - QBAY::createCollection_output output; - - input.maxSizePerOneId = maxSizePerOneId; - input.priceForDropMint = priceForDropMint; - input.volume = volume; - input.royalty = royalty; - input.typeOfCollection = typeOfCollection; - - for(uint32 i = 0 ; i < 64; i++) - { - if(i >= QBAY_LENGTH_OF_URI) - { - input.URI.set(i, 0); - } - else - { - input.URI.set(i, URI.get(i)); - } - } - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 2, input, output, user, 0); - - return output; - } - - QBAY::mint_output mint(const id& user, uint32 royalty, uint32 collectionId, Array URI, bit typeOfMint, uint64 mintFee) - { - QBAY::mint_input input; - QBAY::mint_output output; - - input.collectionId = collectionId; - input.royalty = royalty; - input.typeOfMint = typeOfMint; - - for(uint32 i = 0 ; i < 64; i++) - { - input.URI.set(i, URI.get(i)); - } - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 3, input, output, user, mintFee); - - return output; - } - - QBAY::mintOfDrop_output mintOfDrop(const id& user, uint32 collectionId, Array URI, uint64 mintOfDropFee) - { - QBAY::mintOfDrop_input input; - QBAY::mintOfDrop_output output; - - input.collectionId = collectionId; - - for(uint32 i = 0; i < 64; i++) - { - input.URI.set(i, URI.get(i)); - } - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 4, input, output, user, mintOfDropFee); - - return output; - } - - QBAY::transfer_output transfer(const id& user, uint32 NFTid, id receiver, uint64 transferFee) - { - QBAY::transfer_input input; - QBAY::transfer_output output; - - input.NFTid = NFTid; - input.receiver = receiver; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 5, input, output, user, transferFee); - - return output; - } - - QBAY::listInMarket_output listInMarket(const id& user, uint64 price, uint32 NFTid) - { - QBAY::listInMarket_input input; - QBAY::listInMarket_output output; - - input.NFTid = NFTid; - input.price = price; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 6, input, output, user, 0); - - return output; - } - - QBAY::buy_output buy(const id& user, uint32 NFTid, bit methodOfPayment, uint64 salePrice) - { - QBAY::buy_input input; - QBAY::buy_output output; - - input.methodOfPayment = methodOfPayment; - input.NFTid = NFTid; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 7, input, output, user, salePrice); - - return output; - } - - QBAY::cancelSale_output cancelSale(const id& user, uint32 NFTid) - { - QBAY::cancelSale_input input; - QBAY::cancelSale_output output; - - input.NFTid = NFTid; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 8, input, output, user, 0); - - return output; - } - - QBAY::listInExchange_output listInExchange(const id& user, uint32 possessedNFT, uint32 anotherNFT) - { - QBAY::listInExchange_input input; - QBAY::listInExchange_output output; - - input.anotherNFT = anotherNFT; - input.possessedNFT = possessedNFT; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 9, input, output, user, 0); - - return output; - } - - QBAY::cancelExchange_output cancelExchange(const id& user, uint32 possessedNFT, uint32 anotherNFT) - { - QBAY::cancelExchange_input input; - QBAY::cancelExchange_output output; - - input.possessedNFT = possessedNFT; - input.anotherNFT = anotherNFT; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 10, input, output, user, 0); - - return output; - } - - QBAY::makeOffer_output makeOffer(const id& user, sint64 askPrice, uint32 NFTid, bit paymentMethod) - { - QBAY::makeOffer_input input; - QBAY::makeOffer_output output; - - input.askPrice = askPrice; - input.NFTid = NFTid; - input.paymentMethod = paymentMethod; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 11, input, output, user, paymentMethod == 0?askPrice:0); - - return output; - } - - QBAY::acceptOffer_output acceptOffer(const id& user, uint32 NFTid) - { - QBAY::acceptOffer_input input; - QBAY::acceptOffer_output output; - - input.NFTid = NFTid; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 12, input, output, user, 0); - - return output; - } - - QBAY::cancelOffer_output cancelOffer(const id& user, uint32 NFTid) - { - QBAY::cancelOffer_input input; - QBAY::cancelOffer_output output; - - input.NFTid = NFTid; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 13, input, output, user, 0); - - return output; - } - - QBAY::createTraditionalAuction_output createTraditionalAuction(const id& user, uint64 minPrice, uint32 NFTId, bit paymentMethodOfAuction, uint32 startYear, uint32 startMonth, uint32 startDay, uint32 startHour, uint32 endYear, uint32 endMonth, uint32 endDay, uint32 endHour) - { - QBAY::createTraditionalAuction_input input; - QBAY::createTraditionalAuction_output output; - - input.startYear = startYear; - input.startMonth = startMonth; - input.startDay = startDay; - input.startHour = startHour; - input.endYear = startYear; - input.endMonth = endMonth; - input.endDay = endDay; - input.endHour = endHour; - input.minPrice = minPrice; - input.NFTId = NFTId; - input.paymentMethodOfAuction = paymentMethodOfAuction; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 14, input, output, user, 0); - - return output; - } - - QBAY::bidOnTraditionalAuction_output bidOnTraditionalAuction(const id& user, uint64 price, uint32 NFTId, bit paymentMethod) - { - QBAY::bidOnTraditionalAuction_input input; - QBAY::bidOnTraditionalAuction_output output; - - input.NFTId = NFTId; - input.paymentMethod = paymentMethod; - input.price = price; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 15, input, output, user, price); - - return output; - } - - QBAY::changeStatusOfMarketPlace_output changeStatusOfMarketPlace(const id& user, bit status) - { - QBAY::changeStatusOfMarketPlace_input input; - QBAY::changeStatusOfMarketPlace_output output; - - input.status = status; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 17, input, output, user, 0); - - return output; - } - - QBAY::TransferShareManagementRights_output qbayTransferShareManagementRights(const id& user, sint64 numberOfShares, uint32 newManagingContractIndex, uint64 fee) - { - QBAY::TransferShareManagementRights_input input; - QBAY::TransferShareManagementRights_output output; - - input.asset.assetName = QBAY_CFB_NAME; - input.asset.issuer = CFB_ISSUER; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QBAY_CONTRACT_INDEX, 16, input, output, user, fee); - - return output; - } - - sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) - { - QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QBAY_ISSUE_ASSET_FEE); - return output.issuedNumberOfShares; - } - - sint64 TransferShareOwnershipAndPossession(const id& issuer, uint64 assetName, sint64 numberOfShares, id newOwnerAndPossesor) - { - QX::TransferShareOwnershipAndPossession_input input; - QX::TransferShareOwnershipAndPossession_output output; - - input.assetName = assetName; - input.issuer = issuer; - input.newOwnerAndPossessor = newOwnerAndPossesor; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, issuer, QBAY_TOKEN_TRANSFER_FEE); - - return output.transferredNumberOfShares; - } - - sint64 TransferShareManagementRights(const id& issuer, uint64 assetName, uint32 newManagingContractIndex, sint64 numberOfShares, id currentOwner) - { - QX::TransferShareManagementRights_input input; - QX::TransferShareManagementRights_output output; - - input.asset.assetName = assetName; - input.asset.issuer = issuer; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, currentOwner, 0); - - return output.transferredNumberOfShares; - } - - -}; - -TEST(TestContractQBAY, testingAllProceduresAndFunctions) -{ - ContractTestingQBAY pfp; - - uint64 cfbPrice = random(1, 1000000); - uint64 qubicPrice = random(1, 1000000); - uint64 earnedQubic = 0; - uint64 earnedCFB = 0; - uint64 collectedShareHolderFee = 0; - uint32 totalIncommingNFTNumber = 0; - uint32 totalPriceForCollectionCreating = 0; - uint32 numberOfCollectionCreated = 0; - uint32 numberOfNFTCreated = 0; - - increaseEnergy(MARKETPLACE_OWNER, 1); - - pfp.settingCFBAndQubicPrice(MARKETPLACE_OWNER, cfbPrice, qubicPrice); - pfp.changeStatusOfMarketPlace(MARKETPLACE_OWNER, 1); - pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 1); - - uint64 assetName = assetNameFromString("CFB"); - - increaseEnergy(CFB_ISSUER, QBAY_ISSUE_ASSET_FEE); - EXPECT_EQ(pfp.issueAsset(CFB_ISSUER, assetName, QBAY_CREATED_CFB_AMOUNT, 0, 0), QBAY_CREATED_CFB_AMOUNT); - - auto users = getRandomUsers(10000, 10000); - - increaseEnergy(CFB_ISSUER, QBAY_TOKEN_TRANSFER_FEE); - // EXPECT_EQ(users[0], users[1]); - increaseEnergy(users[0], QBAY_TOKEN_TRANSFER_FEE); - pfp.TransferShareOwnershipAndPossession(CFB_ISSUER, assetName, 10000000000, users[0]); - - Array URI; - for(uint32 i = 0 ; i < 64; i++) - { - URI.set(i, getRandomURI().get(i)); - } - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_9001_10000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_9001_10000); - pfp.createCollection(users[0], 0, 10, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 10000, 10, 100, 1, 1, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_9001_10000; - totalIncommingNFTNumber += 10000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_9001_10000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_8001_9000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_8001_9000); - pfp.createCollection(users[0], 0, 9, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 9000, 10, 100, 1, 2, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_8001_9000; - totalIncommingNFTNumber += 9000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_8001_9000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_7001_8000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_7001_8000); - pfp.createCollection(users[0], 0, 8, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 8000, 10, 100, 1, 3, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_7001_8000; - totalIncommingNFTNumber += 8000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_7001_8000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_6001_7000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_6001_7000); - pfp.createCollection(users[0], 0, 7, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 7000, 10, 100, 1, 4, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_6001_7000; - totalIncommingNFTNumber += 7000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_6001_7000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_5001_6000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_5001_6000); - pfp.createCollection(users[0], 0, 6, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 6000, 10, 100, 1, 5, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_5001_6000; - totalIncommingNFTNumber += 6000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_5001_6000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_4001_5000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_4001_5000); - pfp.createCollection(users[0], 0, 5, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 5000, 10, 100, 1, 6, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_4001_5000; - totalIncommingNFTNumber += 5000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_4001_5000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_3001_4000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_3001_4000); - pfp.createCollection(users[0], 0, 4, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 4000, 10, 100, 1, 7, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_3001_4000; - totalIncommingNFTNumber += 4000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_3001_4000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_2001_3000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_2001_3000); - pfp.createCollection(users[0], 0, 3, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 3000, 10, 100, 1, 8, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_2001_3000; - totalIncommingNFTNumber += 3000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_2001_3000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_1001_2000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_1001_2000); - pfp.createCollection(users[0], 0, 2, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 2000, 10, 100, 1, 9, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_1001_2000; - totalIncommingNFTNumber += 2000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_1001_2000 * cfbPrice; - - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_201_1000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_201_1000); - pfp.createCollection(users[0], 0, 1, 10, 100, 1, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 1000, 10, 100, 1, 10, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_201_1000; - totalIncommingNFTNumber += 1000; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_201_1000 * cfbPrice; - - // Collection for Drop. collection id: 10 - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_2_200, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_2_200); - pfp.createCollection(users[0], 0, 0, 10, 100, 0, URI); - pfp.getState()->createCollectionChecker(users[0], 0, 200, 10, 100, 0, 11, URI); - totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_2_200; - totalIncommingNFTNumber += 200; - numberOfCollectionCreated++; - earnedCFB += QBAY_FEE_COLLECTION_CREATE_2_200 * cfbPrice; - - // getting the id of collection user created - - auto getUserCreatedCollection_output = pfp.getUserCreatedCollection(users[0], 0, 10); - for(uint32 i = 0 ; i < 11; i++) - { - EXPECT_EQ(getUserCreatedCollection_output.collectionId.get(i), 10 - i); - } - - // checking the infos of collection by creator - auto getInfoOfCollectionByCreator_output = pfp.getInfoOfCollectionByCreator(users[0], 1); - pfp.getState()->getInfoOfCollectionByCreatorChecker(getInfoOfCollectionByCreator_output, 0); - - // checking the infos of collection by id - auto getInfoOfCollectionById_output = pfp.getInfoOfCollectionById(0); - pfp.getState()->getInfoOfCollectionByIdChecker(getInfoOfCollectionById_output, 0); - - EXPECT_EQ(numberOfPossessedShares(assetName, CFB_ISSUER, id(12, 0, 0, 0), id(12, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), cfbPrice * totalPriceForCollectionCreating); - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic); - // mint the NFT using collection - - pfp.mint(users[0], 0, 0, URI, 0, 0); - numberOfNFTCreated++; - pfp.getState()->mintChecker(users[0], 0, 0, URI, 0, numberOfNFTCreated - 1); - pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 1); - - // mint the single NFT - increaseEnergy(users[0], QBAY_SINGLE_NFT_CREATE_FEE); - pfp.mint(users[0], 10, 0, URI, 1, QBAY_SINGLE_NFT_CREATE_FEE); - numberOfNFTCreated++; - totalIncommingNFTNumber++; - earnedQubic += QBAY_SINGLE_NFT_CREATE_FEE; - pfp.getState()->mintChecker(users[0], 10, 0, URI, 1, numberOfNFTCreated - 1); - pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 1); - - auto getUserCreatedNFT_output = pfp.getUserCreatedNFT(users[0], 0, 2); - for(uint32 i = 0 ; i < 2; i++) - { - EXPECT_EQ(getUserCreatedNFT_output.NFTId.get(i), 1 - i); - } - - // checking if 2 NFTs are minted by users[0] - auto getNumberOfNFTForUser_output = pfp.getNumberOfNFTForUser(users[0]); - pfp.getState()->getNumberOfNFTForUserChecker(getNumberOfNFTForUser_output, 2); - - // checking the info of users[0]'s first NFT - auto getInfoOfNFTUserPossessed_output = pfp.getInfoOfNFTUserPossessed(users[0], 1); - pfp.getState()->getInfoOfNFTUserPossessedChecker(getInfoOfNFTUserPossessed_output, 0); - - // checking the info of NFT by id - - auto getInfoOfNFTById_output = pfp.getInfoOfNFTById(0); - pfp.getState()->getInfoOfNFTByIdChecker(getInfoOfNFTById_output, 0); - - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic); - - // dropMint - increaseEnergy(users[0], pfp.getState()->getDropMintPrice(10)); - pfp.mintOfDrop(users[0], 10, URI, pfp.getState()->getDropMintPrice(10)); - numberOfNFTCreated++; - pfp.getState()->mintChecker(users[0], 10, 10, URI, 0, numberOfNFTCreated - 1); - - // transfer NFT - - increaseEnergy(users[1], 1); - pfp.transfer(users[0], 0, users[1], 0); - pfp.getState()->transferChecker(users[1], 0); - - // listInMarket - - pfp.listInMarket(users[1], 100000, 0); - pfp.getState()->listInMarketChecker(0, 100000); - - // buy with $Qubic - // small price - - increaseEnergy(users[2], 100000); - uint64 initialBalanceOfCreator = getBalance(pfp.getState()->getCreatorOfNFT(0)); - uint64 initialBalanceOfPossesor = getBalance(pfp.getState()->getPossessorOfNFT(0)); - uint64 initialBalanceOfMarket = getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)); - id oldPossesor = pfp.getState()->getPossessorOfNFT(0); - - uint64 gt_marketFee = div(100000 * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); - uint64 gt_shareHolderFee = div(100000 * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); - - pfp.buy(users[2], 0, 0, 100000); - pfp.getState()->buyChecker(oldPossesor, users[2], 0, 100000, 0, initialBalanceOfCreator, initialBalanceOfPossesor, initialBalanceOfMarket, pfp.getState()->getCreatorOfNFT(0) == pfp.getState()->getPossessorOfNFT(0)); - earnedQubic += gt_marketFee; - collectedShareHolderFee += gt_shareHolderFee; - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - - // big price - - pfp.listInMarket(users[2], 10000000, 0); - pfp.getState()->listInMarketChecker(0, 10000000); - - increaseEnergy(users[3], 10000000); - initialBalanceOfCreator = getBalance(pfp.getState()->getCreatorOfNFT(0)); - initialBalanceOfPossesor = getBalance(pfp.getState()->getPossessorOfNFT(0)); - initialBalanceOfMarket = getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)); - oldPossesor = pfp.getState()->getPossessorOfNFT(0); - - pfp.buy(users[3], 0, 0, 10000000); - gt_marketFee = div(10000000 * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); - gt_shareHolderFee = div(10000000 * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); - pfp.getState()->buyChecker(oldPossesor, users[3], 0, 10000000, 0, initialBalanceOfCreator, initialBalanceOfPossesor, initialBalanceOfMarket, pfp.getState()->getCreatorOfNFT(0) == pfp.getState()->getPossessorOfNFT(0)); - earnedQubic += gt_marketFee; - collectedShareHolderFee += gt_shareHolderFee; - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - - // buy with $CFB - - pfp.listInMarket(users[3], 10000000, 0); - pfp.getState()->listInMarketChecker(0, 10000000); - - initialBalanceOfCreator = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getCreatorOfNFT(0), pfp.getState()->getCreatorOfNFT(0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getCreatorOfNFT(0), pfp.getState()->getCreatorOfNFT(0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX); - initialBalanceOfPossesor = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getPossessorOfNFT(0), pfp.getState()->getPossessorOfNFT(0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getPossessorOfNFT(0), pfp.getState()->getPossessorOfNFT(0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX); - initialBalanceOfMarket = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX); - oldPossesor = pfp.getState()->getPossessorOfNFT(0); - - increaseEnergy(users[4], 10000000); - increaseEnergy(CFB_ISSUER, QBAY_TOKEN_TRANSFER_FEE); - EXPECT_EQ(pfp.TransferShareOwnershipAndPossession(CFB_ISSUER, assetName, 10000000000, users[4]), 10000000000); - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, div(10000000ULL, qubicPrice) * cfbPrice, users[4]), div(10000000ULL, qubicPrice) * cfbPrice); - - pfp.buy(users[4], 0, 1, 0); - earnedCFB += div(div(10000000ULL, qubicPrice) * cfbPrice * QBAY_FEE_NFT_SALE_MARKET, 1000ULL); - - pfp.getState()->buyChecker(oldPossesor, users[4], 0, div(10000000ULL, qubicPrice) * cfbPrice, 1, initialBalanceOfCreator, initialBalanceOfPossesor, initialBalanceOfMarket, pfp.getState()->getCreatorOfNFT(0) == pfp.getState()->getPossessorOfNFT(0)); - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - // cancelSale - - pfp.listInMarket(users[4], 10000000, 0); - pfp.getState()->listInMarketChecker(0, 10000000); - - pfp.cancelSale(users[4], 0); - pfp.getState()->cancelSaleChecker(0); - - // listInExchange - - increaseEnergy(users[4], 10000000); - increaseEnergy(users[0], 10000000); - - pfp.listInExchange(users[0], 1, 0); - pfp.getState()->listInExchangeChecker(users[0], 1, 0); - - pfp.listInExchange(users[4], 0, 1); - pfp.getState()->possesorChecker(users[0], 0); - pfp.getState()->possesorChecker(users[4], 1); - - // cancelExchange - - pfp.listInExchange(users[4], 1, 0); - pfp.cancelExchange(users[4], 1, 0); - pfp.getState()->cancelExchangeChecker(0); - - // makeOffer with $Qubic - - pfp.makeOffer(users[4], 10000000, 0, 0); - pfp.getState()->makeOfferChecker(0, users[4], 0, 10000000); - increaseEnergy(users[5], 100000000); - pfp.makeOffer(users[5], 100000000, 0, 0); - pfp.getState()->makeOfferChecker(0, users[5], 0, 100000000); - - // makeOffer with $CFB in high price - - sint64 askPrice = (div(1000000000ULL, qubicPrice) + 1) * cfbPrice; - increaseEnergy(CFB_ISSUER, 100000000); - EXPECT_EQ(pfp.TransferShareOwnershipAndPossession(CFB_ISSUER, assetName, askPrice, users[4]), askPrice); - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, askPrice, users[4]), askPrice); - - pfp.makeOffer(users[4], askPrice, 0, 1); - pfp.getState()->makeOfferChecker(0, users[4], 1, askPrice); - - // acceptOffer - - pfp.acceptOffer(users[0], 0); - pfp.getState()->acceptOfferChecker(users[0], 0); - earnedCFB += div(askPrice * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); - - // cancelOffer - - pfp.makeOffer(users[5], 100000000, 0, 0); - pfp.getState()->makeOfferChecker(0, users[5], 0, 100000000); - pfp.cancelOffer(users[5], 0); - pfp.getState()->cancelOfferChecker(0); - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - - // createTraditionalAuction - setMemory(utcTime, 0); - utcTime.Year = 2025; - utcTime.Month = 12; - utcTime.Day = 31; - utcTime.Hour = 0; - updateQpiTime(); - pfp.createTraditionalAuction(users[4], 10000000, 0, 0, 26, 1, 1, 0, 26, 1, 5, 0); - pfp.getState()->createAuctionChecker(0, 10000000, 1, 0, users[4], 26, 1, 1, 0, 26, 1, 5, 0); - - // getting the info of Auction - auto getIncomingAuctions_output = pfp.getIncomingAuctions(0, 1); - pfp.getState()->getIncomingAuctionsChecker(getIncomingAuctions_output, 0, 1); - - setMemory(utcTime, 0); - utcTime.Year = 2026; - utcTime.Month = 1; - utcTime.Day = 3; - utcTime.Hour = 0; - updateQpiTime(); - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - - // bidOnAuction - pfp.bidOnTraditionalAuction(users[5], 12000000, 0, 0); - pfp.getState()->bidOnAuctionChecker(users[5], 0, 12000000); - gt_marketFee = div(12000000 * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); - gt_shareHolderFee = div(12000000 * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); - - earnedQubic += gt_marketFee; - collectedShareHolderFee += gt_shareHolderFee; - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - - increaseEnergy(users[6], 110000000); - pfp.bidOnTraditionalAuction(users[6], 110000000, 0, 0); - pfp.getState()->bidOnAuctionChecker(users[6], 0, 110000000); - gt_marketFee = div((110000000 - 12000000) * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); - gt_shareHolderFee = div((110000000 - 12000000) * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); - - earnedQubic += gt_marketFee; - collectedShareHolderFee += gt_shareHolderFee; - - EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); - EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); - - auto getInfoOfMarketplace_output = pfp.getInfoOfMarketplace(); - pfp.getState()->getInfoOfMarketplaceChecker(getInfoOfMarketplace_output); - - pfp.getState()->profitChecker(earnedQubic, earnedCFB); - - pfp.changeStatusOfMarketPlace(MARKETPLACE_OWNER, 0); - pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 0); - - pfp.endEpoch(); - - // increased the amount of MARKETPLACE_OWNER in line 771, so the balance of marketPlaceOwner should be earnedQubic + 1. - EXPECT_EQ(getBalance(MARKETPLACE_OWNER), earnedQubic + 1); - - uint64 numberOfQXCFB = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, CFB_ISSUER, CFB_ISSUER, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); - EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, 10000, CFB_ISSUER), 10000); - EXPECT_EQ(numberOfQXCFB - 10000, numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, CFB_ISSUER, CFB_ISSUER, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); - increaseEnergy(CFB_ISSUER, 1000000); - EXPECT_EQ(pfp.qbayTransferShareManagementRights(CFB_ISSUER, 10000, QX_CONTRACT_INDEX, 1000000).transferredNumberOfShares, 10000); - EXPECT_EQ(numberOfQXCFB, numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, CFB_ISSUER, CFB_ISSUER, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); -} \ No newline at end of file diff --git a/test/contract_qbond.cpp b/test/contract_qbond.cpp deleted file mode 100644 index 9eb9d0644..000000000 --- a/test/contract_qbond.cpp +++ /dev/null @@ -1,444 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -std::string assetNameFromInt64(uint64 assetName); -std::string getCurrentMbondIndex(uint16_t epoch) -{ - if (epoch < QBOND_CYCLIC_START_EPOCH) - { - return std::to_string(epoch); - } - else - { - uint16_t index = (epoch - QBOND_CYCLIC_START_EPOCH + 1) % 53 == 0 ? 53 : (epoch - QBOND_CYCLIC_START_EPOCH + 1) % 53; - return index < 10 ? std::string("0").append(std::to_string(index)) : std::to_string(index); - } -} -const id adminAddress = ID(_B, _O, _N, _D, _A, _A, _F, _B, _U, _G, _H, _E, _L, _A, _N, _X, _G, _H, _N, _L, _M, _S, _U, _I, _V, _B, _K, _B, _H, _A, _Y, _E, _Q, _S, _Q, _B, _V, _P, _V, _N, _B, _H, _L, _F, _J, _I, _A, _Z, _F, _Q, _C, _W, _W, _B, _V, _E); -const id testAddress1 = ID(_H, _O, _G, _T, _K, _D, _N, _D, _V, _U, _U, _Z, _U, _F, _L, _A, _M, _L, _V, _B, _L, _Z, _D, _S, _G, _D, _D, _A, _E, _B, _E, _K, _K, _L, _N, _Z, _J, _B, _W, _S, _C, _A, _M, _D, _S, _X, _T, _C, _X, _A, _M, _A, _X, _U, _D, _F); -const id testAddress2 = ID(_E, _Q, _M, _B, _B, _V, _Y, _G, _Z, _O, _F, _U, _I, _H, _E, _X, _F, _O, _X, _K, _T, _F, _T, _A, _N, _E, _K, _B, _X, _L, _B, _X, _H, _A, _Y, _D, _F, _F, _M, _R, _E, _E, _M, _R, _Q, _E, _V, _A, _D, _Y, _M, _M, _E, _W, _A, _C); - -class QBondChecker : public QBOND -{ -public: - int64_t getCFAPopulation() - { - return _commissionFreeAddresses.population(); - } -}; - -class ContractTestingQBond : protected ContractTesting -{ -public: - ContractTestingQBond() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QBOND); - callSystemProcedure(QBOND_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QEARN); - callSystemProcedure(QEARN_CONTRACT_INDEX, INITIALIZE); - } - - QBondChecker* getState() - { - return (QBondChecker*)contractStates[QBOND_CONTRACT_INDEX]; - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(QBOND_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QBOND_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - void stake(const id& staker, const int64_t& quMillions, const int64_t& quAmount) - { - QBOND::Stake_input input{ quMillions }; - QBOND::Stake_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 1, input, output, staker, quAmount); - } - - QBOND::TransferMBondOwnershipAndPossession_output transfer(const id& from, const id& to, const uint16_t& epoch, const int64_t& mbondsAmount, const int64_t& quAmount) - { - QBOND::TransferMBondOwnershipAndPossession_input input{ to, epoch, mbondsAmount }; - QBOND::TransferMBondOwnershipAndPossession_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 2, input, output, from, quAmount); - return output; - } - - QBOND::AddAskOrder_output addAskOrder(const id& asker, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) - { - QBOND::AddAskOrder_input input{ epoch, price, mbondsAmount }; - QBOND::AddAskOrder_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 3, input, output, asker, quAmount); - return output; - } - - QBOND::RemoveAskOrder_output removeAskOrder(const id& asker, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) - { - QBOND::RemoveAskOrder_input input{ epoch, price, mbondsAmount }; - QBOND::RemoveAskOrder_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 4, input, output, asker, quAmount); - return output; - } - - QBOND::AddBidOrder_output addBidOrder(const id& bider, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) - { - QBOND::AddBidOrder_input input{ epoch, price, mbondsAmount }; - QBOND::AddBidOrder_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 5, input, output, bider, quAmount); - return output; - } - - QBOND::RemoveBidOrder_output removeBidOrder(const id& bider, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) - { - QBOND::RemoveBidOrder_input input{ epoch, price, mbondsAmount }; - QBOND::RemoveBidOrder_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 6, input, output, bider, quAmount); - return output; - } - - QBOND::BurnQU_output burnQU(const id& invocator, const int64_t& quToBurn, const int64_t& quAmount) - { - QBOND::BurnQU_input input{ quToBurn }; - QBOND::BurnQU_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 7, input, output, invocator, quAmount); - return output; - } - - bool updateCFA(const id& invocator, const id& address, const bool operation) - { - QBOND::UpdateCFA_input input{ address, operation }; - QBOND::UpdateCFA_output output; - invokeUserProcedure(QBOND_CONTRACT_INDEX, 8, input, output, invocator, 0); - return output.result; - } - - QBOND::GetEarnedFees_output getEarnedFees() - { - QBOND::GetEarnedFees_input input; - QBOND::GetEarnedFees_output output; - callFunction(QBOND_CONTRACT_INDEX, 2, input, output); - return output; - } - - QBOND::GetInfoPerEpoch_output getInfoPerEpoch(const uint16_t& epoch) - { - QBOND::GetInfoPerEpoch_input input{ epoch }; - QBOND::GetInfoPerEpoch_output output; - callFunction(QBOND_CONTRACT_INDEX, 3, input, output); - return output; - } - - QBOND::GetOrders_output getOrders(const uint16_t& epoch, const int64_t& asksOffset, const int64_t& bidsOffset) - { - QBOND::GetOrders_input input{ epoch, asksOffset, bidsOffset }; - QBOND::GetOrders_output output; - callFunction(QBOND_CONTRACT_INDEX, 4, input, output); - return output; - } - - QBOND::GetUserOrders_output getUserOrders(const id& user, const int64_t& asksOffset, const int64_t& bidsOffset) - { - QBOND::GetUserOrders_input input{ user, asksOffset, bidsOffset }; - QBOND::GetUserOrders_output output; - callFunction(QBOND_CONTRACT_INDEX, 5, input, output); - return output; - } - - QBOND::GetMBondsTable_output getMBondsTable() - { - QBOND::GetMBondsTable_input input; - QBOND::GetMBondsTable_output output; - callFunction(QBOND_CONTRACT_INDEX, 6, input, output); - return output; - } - - QBOND::GetUserMBonds_output getUserMBonds(const id& user) - { - QBOND::GetUserMBonds_input input{ user }; - QBOND::GetUserMBonds_output output; - callFunction(QBOND_CONTRACT_INDEX, 7, input, output); - return output; - } - - QBOND::GetCFA_output getCFA() - { - QBOND::GetCFA_input input; - QBOND::GetCFA_output output; - callFunction(QBOND_CONTRACT_INDEX, 8, input, output); - return output; - } -}; - -TEST(ContractQBond, Stake) -{ - system.epoch = QBOND_CYCLIC_START_EPOCH; - ContractTestingQBond qbond; - qbond.beginEpoch(); - - increaseEnergy(testAddress1, 100000000LL); - increaseEnergy(testAddress2, 100000000LL); - - // scenario 1: testAddress1 want to stake 50 millions, but send to sc 30 millions - qbond.stake(testAddress1, 50, 30000000LL); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); - - // scenario 2: testAddress1 want to stake 50 millions, but send to sc 50 millions (without commission) - qbond.stake(testAddress1, 50, 50000000LL); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); - - // scenario 3: testAddress1 want to stake 50 millions and send full amount with commission - qbond.stake(testAddress1, 50, 50250000LL); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 50LL); - - // scenario 4.1: testAddress2 want to stake 5 millions, recieve 0 MBonds, because minimum is 10 and 5 were put in queue - qbond.stake(testAddress2, 5, 5025000); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); - - // scenario 4.2: testAddress1 want to stake 7 millions, testAddress1 recieve 7 MBonds and testAddress2 recieve 5 MBonds, because the total qu millions in the queue became more than 10 - qbond.stake(testAddress1, 7, 7035000); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 57); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 5); -} - - -TEST(ContractQBond, TransferMBondOwnershipAndPossession) -{ - ContractTestingQBond qbond; - qbond.beginEpoch(); - increaseEnergy(testAddress1, 1000000000); - qbond.stake(testAddress1, 50, 50250000); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 50); - - // scenario 1: not enough gas, 100 needed - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 10, 50).transferredMBonds, 0); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); - - // scenario 2: enough gas, not enough mbonds - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 70, 100).transferredMBonds, 0); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); - - // scenario 3: success - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 40, 100).transferredMBonds, 40); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 40); -} - -TEST(ContractQBond, AddRemoveAskOrder) -{ - ContractTestingQBond qbond; - qbond.beginEpoch(); - increaseEnergy(testAddress1, 1000000000); - qbond.stake(testAddress1, 50, 50250000); - - // scenario 1: not enough mbonds - EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1500000, 100, 0).addedMBondsAmount, 0); - - // scenario 2: success to add ask, asked mbonds are blocked and cannot be transferred to another address - EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1500000, 30, 0).addedMBondsAmount, 30); - // not enough free mbonds - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 21, 100).transferredMBonds, 0); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); - // successful transfer - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 20, 100).transferredMBonds, 20); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 20); - - // scenario 3: no orders to remove at this price - EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1400000, 30, 0).removedMBondsAmount, 0); - EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1600000, 30, 0).removedMBondsAmount, 0); - - // scenario 4: no free mbonds, then successful removal ask order and transfer to another address - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 1, 100).transferredMBonds, 0); - EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1500000, 5, 0).removedMBondsAmount, 5); - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 5, 100).transferredMBonds, 5); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 25); - - EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1500000, 500, 0).removedMBondsAmount, 25); -} - -TEST(ContractQBond, AddRemoveBidOrder) -{ - ContractTestingQBond qbond; - qbond.beginEpoch(); - increaseEnergy(testAddress1, 1000000000); - increaseEnergy(testAddress2, 1000000000); - qbond.stake(testAddress1, 50, 50250000); - - // scenario 1: not enough qu - EXPECT_EQ(qbond.addBidOrder(testAddress2, system.epoch, 1500000, 10, 100).addedMBondsAmount, 0); - - // scenario 2: success to add bid - EXPECT_EQ(qbond.addBidOrder(testAddress2, system.epoch, 1500000, 10, 15000000).addedMBondsAmount, 10); - - // scenario 3: testAddress1 add ask order which matches the bid order - EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1500000, 3, 0).addedMBondsAmount, 3); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 47); - EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 3); - - // scenario 3: no orders to remove at this price - EXPECT_EQ(qbond.removeBidOrder(testAddress2, system.epoch, 1400000, 30, 0).removedMBondsAmount, 0); - EXPECT_EQ(qbond.removeBidOrder(testAddress2, system.epoch, 1600000, 30, 0).removedMBondsAmount, 0); - - // scenario 4: successful removal bid order, qu are returned (7 mbonds per 1500000 each) - int64_t prevBalance = getBalance(testAddress2); - EXPECT_EQ(qbond.removeBidOrder(testAddress2, system.epoch, 1500000, 100, 0).removedMBondsAmount, 7); - EXPECT_EQ(getBalance(testAddress2) - prevBalance, 10500000); - - // check earned fees - auto fees = qbond.getEarnedFees(); - EXPECT_EQ(fees.stakeFees, 250000); - EXPECT_EQ(fees.tradeFees, 1350); // 1500000 (MBond price) * 3 (MBonds) * 0.0003 (0.03% fees for trade) - - // getOrders checks - EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1600000, 5, 0).addedMBondsAmount, 5); - EXPECT_EQ(qbond.addAskOrder(testAddress2, system.epoch, 1500000, 3, 0).addedMBondsAmount, 3); - EXPECT_EQ(qbond.addBidOrder(testAddress1, system.epoch, 1400000, 10, 14000000).addedMBondsAmount, 10); - EXPECT_EQ(qbond.addBidOrder(testAddress2, system.epoch, 1300000, 5, 6500000).addedMBondsAmount, 5); - - // all orders sorted by price, therefore the element with index 0 contains an order with a price of 1500000 - auto orders = qbond.getOrders(system.epoch, 0, 0); - EXPECT_EQ(orders.askOrders.get(0).epoch, (sint64) QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(orders.askOrders.get(0).numberOfMBonds, 3); - EXPECT_EQ(orders.askOrders.get(0).owner, testAddress2); - EXPECT_EQ(orders.askOrders.get(0).price, 1500000); - - EXPECT_EQ(orders.bidOrders.get(0).epoch, (sint64) QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(orders.bidOrders.get(0).numberOfMBonds, 10); - EXPECT_EQ(orders.bidOrders.get(0).owner, testAddress1); - EXPECT_EQ(orders.bidOrders.get(0).price, 1400000); - - // with offset - orders = qbond.getOrders(system.epoch, 1, 1); - EXPECT_EQ(orders.askOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(orders.askOrders.get(0).numberOfMBonds, 5); - EXPECT_EQ(orders.askOrders.get(0).owner, testAddress1); - EXPECT_EQ(orders.askOrders.get(0).price, 1600000); - - EXPECT_EQ(orders.bidOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(orders.bidOrders.get(0).numberOfMBonds, 5); - EXPECT_EQ(orders.bidOrders.get(0).owner, testAddress2); - EXPECT_EQ(orders.bidOrders.get(0).price, 1300000); - - // user orders - auto userOrders = qbond.getUserOrders(testAddress1, 0, 0); - EXPECT_EQ(userOrders.askOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(userOrders.askOrders.get(0).numberOfMBonds, 5); - EXPECT_EQ(userOrders.askOrders.get(0).owner, testAddress1); - EXPECT_EQ(userOrders.askOrders.get(0).price, 1600000); - - EXPECT_EQ(userOrders.bidOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(userOrders.bidOrders.get(0).numberOfMBonds, 10); - EXPECT_EQ(userOrders.bidOrders.get(0).owner, testAddress1); - EXPECT_EQ(userOrders.bidOrders.get(0).price, 1400000); - - // with offset - userOrders = qbond.getUserOrders(testAddress1, 1, 1); - EXPECT_EQ(userOrders.askOrders.get(0).epoch, 0); - EXPECT_EQ(userOrders.askOrders.get(0).numberOfMBonds, 0); - EXPECT_EQ(userOrders.askOrders.get(0).owner, NULL_ID); - EXPECT_EQ(userOrders.askOrders.get(0).price, 0); - - EXPECT_EQ(userOrders.bidOrders.get(0).epoch, 0); - EXPECT_EQ(userOrders.bidOrders.get(0).numberOfMBonds, 0); - EXPECT_EQ(userOrders.bidOrders.get(0).owner, NULL_ID); - EXPECT_EQ(userOrders.bidOrders.get(0).price, 0); -} - -TEST(ContractQBond, BurnQu) -{ - ContractTestingQBond qbond; - qbond.beginEpoch(); - increaseEnergy(testAddress1, 1000000000); - - // scenario 1: not enough qu - EXPECT_EQ(qbond.burnQU(testAddress1, 1000000, 1000).amount, -1); - - // scenario 2: successful burning - EXPECT_EQ(qbond.burnQU(testAddress1, 1000000, 1000000).amount, 1000000); - - // scenario 3: successful burning, the surplus is returned - int64_t prevBalance = getBalance(testAddress1); - EXPECT_EQ(qbond.burnQU(testAddress1, 1000000, 10000000).amount, 1000000); - EXPECT_EQ(prevBalance - getBalance(testAddress1), 1000000); -} - -TEST(ContractQBond, UpdateCFA) -{ - ContractTestingQBond qbond; - increaseEnergy(testAddress1, 1000); - increaseEnergy(adminAddress, 1000); - - // only adminAddress can update CFA - EXPECT_EQ(qbond.getState()->getCFAPopulation(), 1); - EXPECT_FALSE(qbond.updateCFA(testAddress1, testAddress2, 1)); - EXPECT_EQ(qbond.getState()->getCFAPopulation(), 1); - EXPECT_TRUE(qbond.updateCFA(adminAddress, testAddress2, 1)); - EXPECT_EQ(qbond.getState()->getCFAPopulation(), 2); - - auto cfa = qbond.getCFA(); - EXPECT_EQ(cfa.commissionFreeAddresses.get(0), testAddress2); - EXPECT_EQ(cfa.commissionFreeAddresses.get(1), adminAddress); - EXPECT_EQ(cfa.commissionFreeAddresses.get(2), NULL_ID); - - EXPECT_FALSE(qbond.updateCFA(testAddress1, testAddress2, 0)); - EXPECT_EQ(qbond.getState()->getCFAPopulation(), 2); - EXPECT_TRUE(qbond.updateCFA(adminAddress, testAddress2, 0)); - EXPECT_EQ(qbond.getState()->getCFAPopulation(), 1); -} - -TEST(ContractQBond, GetInfoPerEpoch) -{ - ContractTestingQBond qbond; - qbond.beginEpoch(); - increaseEnergy(testAddress1, 1000000000); - increaseEnergy(testAddress2, 1000000000); - - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 0); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 0); - - qbond.stake(testAddress1, 50, 50250000); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 1); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 50); - - qbond.stake(testAddress2, 100, 100500000); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 2); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 150); - - EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 50, 100).transferredMBonds, 50); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 1); - EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 150); -} - -TEST(ContractQBond, GetMBondsTable) -{ - ContractTestingQBond qbond; - qbond.beginEpoch(); - increaseEnergy(testAddress1, 1000000000); - increaseEnergy(testAddress2, 1000000000); - - qbond.stake(testAddress1, 50, 50250000); - qbond.stake(testAddress2, 100, 100500000); - qbond.endEpoch(); - - system.epoch++; - qbond.beginEpoch(); - qbond.stake(testAddress1, 10, 10050000); - qbond.stake(testAddress2, 20, 20100000); - - auto table = qbond.getMBondsTable(); - EXPECT_EQ(table.info.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(table.info.get(1).epoch, (sint64)QBOND_CYCLIC_START_EPOCH + 1); - EXPECT_EQ(table.info.get(2).epoch, 0); - - auto userMBonds = qbond.getUserMBonds(testAddress1); - EXPECT_EQ(userMBonds.totalMBondsAmount, 60); - EXPECT_EQ(userMBonds.mbonds.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); - EXPECT_EQ(userMBonds.mbonds.get(0).amount, 50); - EXPECT_EQ(userMBonds.mbonds.get(1).epoch, (sint64)QBOND_CYCLIC_START_EPOCH + 1); - EXPECT_EQ(userMBonds.mbonds.get(1).amount, 10); -} diff --git a/test/contract_qduel.cpp b/test/contract_qduel.cpp deleted file mode 100644 index 34145ee15..000000000 --- a/test/contract_qduel.cpp +++ /dev/null @@ -1,1328 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" -#include - -constexpr uint16 PROCEDURE_INDEX_CREATE_ROOM = 1; -constexpr uint16 PROCEDURE_INDEX_CONNECT_ROOM = 2; -constexpr uint16 PROCEDURE_INDEX_SET_PERCENT_FEES = 3; -constexpr uint16 PROCEDURE_INDEX_SET_TTL_HOURS = 4; -constexpr uint16 PROCEDURE_INDEX_DEPOSIT = 5; -constexpr uint16 PROCEDURE_INDEX_WITHDRAW = 6; -constexpr uint16 PROCEDURE_INDEX_CLOSE_ROOM = 7; -constexpr uint16 FUNCTION_INDEX_GET_PERCENT_FEES = 1; -constexpr uint16 FUNCTION_INDEX_GET_ROOMS = 2; -constexpr uint16 FUNCTION_INDEX_GET_TTL_HOURS = 3; -constexpr uint16 FUNCTION_INDEX_GET_USER_PROFILE = 4; - -static const id QDUEL_TEAM_ADDRESS = - ID(_O, _C, _Z, _W, _N, _J, _S, _N, _R, _U, _Q, _J, _U, _A, _H, _Z, _C, _T, _R, _P, _N, _Y, _W, _G, _G, _E, _F, _C, _X, _B, _A, _V, _F, _O, _P, _R, - _S, _N, _U, _L, _U, _E, _B, _S, _P, _U, _T, _R, _Z, _N, _T, _G, _F, _B, _I, _E); - -class QpiContextUserFunctionCallWithInvocator : public QpiContextFunctionCall -{ -public: - QpiContextUserFunctionCallWithInvocator(unsigned int contractIndex, const id& invocator) - : QpiContextFunctionCall(contractIndex, invocator, 0, USER_FUNCTION_CALL) - {} -}; - -class QDuelChecker : public QDUEL -{ -public: - // Expose read-only accessors for internal state so tests can assert without - // modifying contract storage directly. - uint64 roomCount() const { return rooms.population(); } - id team() const { return teamAddress; } - uint8 ttl() const { return ttlHours; } - uint8 devFee() const { return devFeePercentBps; } - uint8 burnFee() const { return burnFeePercentBps; } - uint8 shareholdersFee() const { return shareholdersFeePercentBps; } - sint64 minDuelAmount() const { return minimumDuelAmount; } - void setState(EState newState) { currentState = newState; } - EState getState() const { return currentState; } - // Helper to fetch user record without exposing contract internals. - bool getUserData(const id& user, UserData& data) const { return users.get(user, data); } - // Directly set a user record to simulate edge-case storage edits. - void setUserData(const UserData& data) { users.set(data.userId, data); } - - RoomInfo firstRoom() const - { - // Map storage can be sparse; walk to first element. - const sint64 index = rooms.nextElementIndex(NULL_INDEX); - if (index == NULL_INDEX) - { - return RoomInfo{}; - } - return rooms.value(index); - } - - bool hasRoom(const id& roomId) const { return rooms.contains(roomId); } - - id computeWinner(const id& player1, const id& player2) const - { - // Run the same winner function as the contract to keep tests deterministic. - QpiContextUserFunctionCall qpi(QDUEL_CONTRACT_INDEX); - GetWinnerPlayer_input input{player1, player2}; - GetWinnerPlayer_output output{}; - GetWinnerPlayer_locals locals{}; - GetWinnerPlayer(qpi, *this, input, output, locals); - return output.winner; - } - - void calculateRevenue(uint64 amount, CalculateRevenue_output& output) const - { - QpiContextUserFunctionCall qpi(QDUEL_CONTRACT_INDEX); - - // Contract helpers require zeroed outputs and locals. - output = {}; - CalculateRevenue_input revenueInput{amount}; - CalculateRevenue_locals revenueLocals{}; - CalculateRevenue(qpi, *this, revenueInput, output, revenueLocals); - } - - GetUserProfile_output getUserProfileFor(const id& user) const - { - QpiContextUserFunctionCallWithInvocator qpi(QDUEL_CONTRACT_INDEX, user); - GetUserProfile_input input{user}; - GetUserProfile_output output{}; - GetUserProfile_locals locals{}; - GetUserProfile(qpi, *this, input, output, locals); - return output; - } -}; - -class ContractTestingQDuel : protected ContractTesting -{ -public: - ContractTestingQDuel() - { - // Build an empty chain state and deploy the contract under test. - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QDUEL); - system.epoch = contractDescriptions[QDUEL_CONTRACT_INDEX].constructionEpoch; - callSystemProcedure(QDUEL_CONTRACT_INDEX, INITIALIZE); - } - - // Access helper for the underlying contract state. - QDuelChecker* state() { return reinterpret_cast(contractStates[QDUEL_CONTRACT_INDEX]); } - - QDUEL::CreateRoom_output createRoom(const id& user, const id& allowedPlayer, sint64 stake, sint64 raiseStep, sint64 maxStake, sint64 reward) - { - QDUEL::CreateRoom_input input{allowedPlayer, stake, raiseStep, maxStake}; - QDUEL::CreateRoom_output output; - // Route through contract procedure to keep call path identical to production. - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_CREATE_ROOM, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - QDUEL::ConnectToRoom_output connectToRoom(const id& user, const id& roomId, sint64 reward) - { - QDUEL::ConnectToRoom_input input{roomId}; - QDUEL::ConnectToRoom_output output; - // Call the user procedure so validation and state updates are exercised. - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_CONNECT_ROOM, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - QDUEL::SetPercentFees_output setPercentFees(const id& user, uint8 devFee, uint8 burnFee, uint8 shareholdersFee, uint16 percentScale, - sint64 reward = 0) - { - QDUEL::SetPercentFees_input input{devFee, burnFee, shareholdersFee, percentScale}; - QDUEL::SetPercentFees_output output; - // System procedures are tested via normal user invocation. - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_PERCENT_FEES, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - QDUEL::SetTTLHours_output setTtlHours(const id& user, uint8 ttlHours, sint64 reward = 0) - { - QDUEL::SetTTLHours_input input{ttlHours}; - QDUEL::SetTTLHours_output output; - // Ensure contract state gets updated through procedure validation. - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_TTL_HOURS, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - QDUEL::GetPercentFees_output getPercentFees() - { - QDUEL::GetPercentFees_input input{}; - QDUEL::GetPercentFees_output output; - // Read-only function call for fee snapshot. - callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_PERCENT_FEES, input, output); - return output; - } - - QDUEL::GetRooms_output getRooms() - { - QDUEL::GetRooms_input input{}; - QDUEL::GetRooms_output output; - // Read-only function call for rooms snapshot. - callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_ROOMS, input, output); - return output; - } - - QDUEL::GetTTLHours_output getTtlHours() - { - QDUEL::GetTTLHours_input input{}; - QDUEL::GetTTLHours_output output; - // Read-only function call for TTL configuration. - callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_TTL_HOURS, input, output); - return output; - } - - QDUEL::GetUserProfile_output getUserProfile(const id& userId) - { - QDUEL::GetUserProfile_input input{userId}; - QDUEL::GetUserProfile_output output; - // Read-only function call for profile by user id. - callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_USER_PROFILE, input, output); - return output; - } - - QDUEL::Deposit_output deposit(const id& user, sint64 reward) - { - QDUEL::Deposit_input input{}; - QDUEL::Deposit_output output; - // Deposit is a user procedure that mutates balance and state. - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_DEPOSIT, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - QDUEL::Withdraw_output withdraw(const id& user, sint64 amount, sint64 reward = 0) - { - QDUEL::Withdraw_input input{amount}; - QDUEL::Withdraw_output output; - // Withdraw uses user procedure to enforce validations and limits. - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_WITHDRAW, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - QDUEL::CloseRoom_output closeRoom(const id& user, sint64 reward = 0) - { - QDUEL::CloseRoom_input input{}; - QDUEL::CloseRoom_output output; - if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_CLOSE_ROOM, input, output, user, reward)) - { - output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - // Helpers that dispatch system procedures during lifecycle tests. - void endTick() { callSystemProcedure(QDUEL_CONTRACT_INDEX, END_TICK); } - - void endEpoch() { callSystemProcedure(QDUEL_CONTRACT_INDEX, END_EPOCH); } - - void beginEpoch() { callSystemProcedure(QDUEL_CONTRACT_INDEX, BEGIN_EPOCH); } - - // Control time and tick for deterministic tests. - void setTick(uint32 tick) { system.tick = tick; } - uint32 getTick() const { return system.tick; } - - void forceEndTick() - { - // Align tick to update period so END_TICK work executes. - system.tick = system.tick + (QDUEL_TICK_UPDATE_PERIOD - mod(system.tick, static_cast(QDUEL_TICK_UPDATE_PERIOD))); - - endTick(); - } - - void setDeterministicTime(uint16 year = 2025, uint8 month = 1, uint8 day = 1, uint8 hour = 0) - { - // Set a fixed time and reset etalon tick so tests are stable. - setMemory(utcTime, 0); - utcTime.Year = year; - utcTime.Month = month; - utcTime.Day = day; - utcTime.Hour = hour; - utcTime.Minute = 0; - utcTime.Second = 0; - utcTime.Nanosecond = 0; - updateQpiTime(); - etalonTick.prevSpectrumDigest = m256i::zero(); - } -}; - -namespace -{ - bool findPlayersForWinner(ContractTestingQDuel& qduel, bool wantPlayer1Win, id& player1, id& player2) - { - // Brute-force deterministic ids until winner matches desired side. - for (uint64 i = 1; i < 10000; ++i) - { - const id candidate1(i, 0, 0, 0); - const id candidate2(i + 1, 0, 0, 0); - const id winner = qduel.state()->computeWinner(candidate1, candidate2); - if (winner == (wantPlayer1Win ? candidate1 : candidate2)) - { - player1 = candidate1; - player2 = candidate2; - return true; - } - } - return false; - } - - void runFullGameCycleWithFees(ContractTestingQDuel& qduel, const id& player1, const id& player2, const id& expectedWinner) - { - // Setup shareholders so revenue distribution can be validated. - const id shareholder1 = id::randomValue(); - const id shareholder2 = id::randomValue(); - constexpr unsigned int rlSharesOwner1 = 100; - constexpr unsigned int rlSharesOwner2 = 576; - std::vector> rlShares{ - {shareholder1, rlSharesOwner1}, - {shareholder2, rlSharesOwner2}, - }; - issueContractShares(RL_CONTRACT_INDEX, rlShares); - - // Set fees as the team address (contract owner). - constexpr uint8 devFee = 15; - constexpr uint8 burnFee = 30; - constexpr uint8 shareholdersFee = 55; - increaseEnergy(qduel.state()->team(), 1); - EXPECT_EQ(qduel.setPercentFees(qduel.state()->team(), devFee, burnFee, shareholdersFee, QDUEL_PERCENT_SCALE).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - // Setup: give both players enough balance to cover the duel. - constexpr sint64 duelAmount = 100000LL; - increaseEnergy(player1, duelAmount); - increaseEnergy(player2, duelAmount); - const uint64 player1Before = getBalance(player1); - const uint64 player2Before = getBalance(player2); - - const uint64 teamBefore = getBalance(qduel.state()->team()); - const uint64 shareholder1Before = getBalance(shareholder1); - const uint64 shareholder2Before = getBalance(shareholder2); - - // Create room and keep initial balance snapshots for payout assertions. - EXPECT_EQ(qduel.createRoom(player1, NULL_ID, duelAmount, 1, duelAmount, duelAmount).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const uint64 player1AfterCreateRoom = getBalance(player1); - - const id winner = qduel.state()->computeWinner(player1, player2); - EXPECT_EQ(winner, expectedWinner); - - // Calculate expected revenue distribution for fees and winner. - QDUEL::CalculateRevenue_output revenueOutput{}; - qduel.state()->calculateRevenue(duelAmount * 2, revenueOutput); - - // Player 2 joins and triggers finalize logic. - const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(player2, qduel.state()->firstRoom().roomId, duelAmount); - EXPECT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(connectOutput.winner, winner); - - // Check fee distribution for team and shareholders. - EXPECT_EQ(getBalance(qduel.state()->team()), teamBefore + revenueOutput.devFee); - - // Check shareholder dividends across the full set of computors. - const uint64 dividendPerShare = revenueOutput.shareholdersFee / NUMBER_OF_COMPUTORS; - EXPECT_EQ(getBalance(shareholder1), shareholder1Before + dividendPerShare * rlSharesOwner1); - EXPECT_EQ(getBalance(shareholder2), shareholder2Before + dividendPerShare * rlSharesOwner2); - - // Check winner receives the remainder and loser only pays entry. - if (winner == player1) - { - EXPECT_EQ(getBalance(player1), player1AfterCreateRoom + revenueOutput.winner); - EXPECT_EQ(getBalance(player2), player2Before - duelAmount); - } - else - { - EXPECT_EQ(getBalance(player1), player1Before - duelAmount); - EXPECT_EQ(getBalance(player2), (player2Before - duelAmount) + revenueOutput.winner); - } - } -} // namespace - -TEST(ContractQDuel, EndEpochKeepsDepositWhileRoomsRecreatedEachEpoch) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(40, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - const uint64 epochs = 3; - const uint64 reward = stake + (stake * epochs); - increaseEnergy(owner, reward); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - QDUEL::UserData ownerData{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, ownerData)); - uint64 expectedDeposit = ownerData.depositedAmount; - id currentRoomId = ownerData.roomId; - - for (uint32 epoch = 0; epoch < epochs; ++epoch) - { - qduel.beginEpoch(); - qduel.endEpoch(); - qduel.setTick(qduel.getTick() + 1); - - QDUEL::UserData afterEndEpoch{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, afterEndEpoch)); - EXPECT_EQ(afterEndEpoch.depositedAmount, expectedDeposit); - EXPECT_EQ(afterEndEpoch.roomId, currentRoomId); - - qduel.state()->setState(QDUEL::EState::NONE); - - const id opponent(200 + epoch, 0, 0, 0); - increaseEnergy(opponent, stake); - EXPECT_EQ(qduel.connectToRoom(opponent, currentRoomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - ASSERT_TRUE(qduel.state()->getUserData(owner, ownerData)); - EXPECT_NE(ownerData.roomId, currentRoomId); - EXPECT_EQ(ownerData.locked, stake); - expectedDeposit -= stake; - EXPECT_EQ(ownerData.depositedAmount, expectedDeposit); - currentRoomId = ownerData.roomId; - } -} - -TEST(ContractQDuel, BeginEpochKeepsRoomsAndUsers) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - qduel.setDeterministicTime(2022, 4, 13, 0); - - const id owner(1, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Give the owner enough balance to create a room. - increaseEnergy(owner, stake); - - // Create a room and verify it survives epoch transition. - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - QDUEL::UserData userBefore{}; - EXPECT_TRUE(qduel.state()->getUserData(owner, userBefore)); - - // Begin epoch should not wipe persistent data. - qduel.beginEpoch(); - - // Room and user record should still exist after epoch transition. - EXPECT_TRUE(qduel.state()->hasRoom(roomBefore.roomId)); - QDUEL::UserData userAfter{}; - EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); - EXPECT_EQ(userAfter.roomId, roomBefore.roomId); -} - -TEST(ContractQDuel, FirstTickAfterUnlockResetsTimerStart) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - increaseEnergy(qduel.state()->team(), 1); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 23, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - qduel.setDeterministicTime(2022, 4, 13, 0); - - const id owner(2, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund owner so the room creation succeeds. - increaseEnergy(owner, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - const uint64 initialCloseTimer = roomBefore.closeTimer; - const DateAndTime initialLastUpdate = roomBefore.lastUpdate; - - // Locking occurs at epoch start; timers should not advance while locked. - qduel.beginEpoch(); - - // Still locked: no timer or lastUpdate changes. - qduel.setDeterministicTime(2022, 4, 13, 1); - qduel.forceEndTick(); - - const QDUEL::RoomInfo lockedRoom = qduel.state()->firstRoom(); - EXPECT_EQ(lockedRoom.closeTimer, initialCloseTimer); - EXPECT_EQ(lockedRoom.lastUpdate, initialLastUpdate); - - // First unlocked tick: reset lastUpdate to "now" without reducing timer. - qduel.setDeterministicTime(2022, 4, 14, 2); - qduel.forceEndTick(); - - const QDUEL::RoomInfo unlockedRoom = qduel.state()->firstRoom(); - EXPECT_EQ(unlockedRoom.closeTimer, initialCloseTimer); - const DateAndTime expectedNow(2022, 4, 14, 2, 0, 0); - EXPECT_EQ(unlockedRoom.lastUpdate, expectedNow); -} - -TEST(ContractQDuel, EndTickExpiresRoomCreatesNewWhenDepositAvailable) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - increaseEnergy(qduel.state()->team(), 1); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(3, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund owner with enough to re-create room after finalize. - increaseEnergy(owner, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - EXPECT_EQ(qduel.state()->roomCount(), 1ULL); - - // Advance to TTL to trigger finalize and auto room creation. - qduel.setDeterministicTime(2025, 1, 1, 1); - qduel.forceEndTick(); - - // A new room should replace the expired one. - EXPECT_EQ(qduel.state()->roomCount(), 1ULL); - const QDUEL::RoomInfo roomAfter = qduel.state()->firstRoom(); - EXPECT_NE(roomAfter.roomId, roomBefore.roomId); - - QDUEL::UserData userAfter{}; - EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); - // User should be re-bound to the new room with locked stake. - EXPECT_EQ(userAfter.roomId, roomAfter.roomId); - EXPECT_EQ(userAfter.locked, stake); -} - -TEST(ContractQDuel, EndTickExpiresRoomWithoutAvailableDepositRemovesUser) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - increaseEnergy(qduel.state()->team(), 1); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(4, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund owner just enough to create the initial room. - increaseEnergy(owner, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - QDUEL::UserData userData{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, userData)); - // Remove available balance so finalize cannot recreate the room. - userData.depositedAmount = 0; - userData.locked = 0; - qduel.state()->setUserData(userData); - - // Expire room and expect cleanup. - qduel.setDeterministicTime(2025, 1, 1, 1); - qduel.forceEndTick(); - - // Room and user data should be removed when deposit is insufficient. - EXPECT_EQ(qduel.state()->roomCount(), 0ULL); - QDUEL::UserData userAfter{}; - EXPECT_FALSE(qduel.state()->getUserData(owner, userAfter)); -} - -TEST(ContractQDuel, EndTickSkipsNonPeriodTicks) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - increaseEnergy(qduel.state()->team(), 1); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 2, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(5, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund owner to create a room. - increaseEnergy(owner, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - qduel.setDeterministicTime(2025, 1, 1, 1); - qduel.setTick(1); - // Non-period tick: no updates expected. - qduel.endTick(); - - const QDUEL::RoomInfo roomAfterSkipped = qduel.state()->firstRoom(); - EXPECT_EQ(roomAfterSkipped.closeTimer, roomBefore.closeTimer); - EXPECT_EQ(roomAfterSkipped.lastUpdate, roomBefore.lastUpdate); - - // Period tick: updates should apply. - qduel.setTick(QDUEL_TICK_UPDATE_PERIOD); - qduel.endTick(); - - const QDUEL::RoomInfo roomAfterProcessed = qduel.state()->firstRoom(); - // Close timer should have decreased by one hour and lastUpdate bumped. - EXPECT_EQ(roomAfterProcessed.closeTimer, roomBefore.closeTimer - 3600ULL); - const DateAndTime expectedNow(2025, 1, 1, 1, 0, 0); - EXPECT_EQ(roomAfterProcessed.lastUpdate, expectedNow); -} - -TEST(ContractQDuel, LockedStateBlocksCreateAndConnect) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(6, 0, 0, 0); - const id other(7, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund owner to create the baseline room. - increaseEnergy(owner, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - - // Lock contract and verify user procedures are blocked. - qduel.state()->setState(QDUEL::EState::LOCKED); - // Fund the other user so only the lock gate can fail. - increaseEnergy(other, stake); - - EXPECT_EQ(qduel.createRoom(other, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::STATE_LOCKED)); - EXPECT_EQ(qduel.connectToRoom(other, roomBefore.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::STATE_LOCKED)); - // Existing room should remain unchanged. - EXPECT_TRUE(qduel.state()->hasRoom(roomBefore.roomId)); -} - -TEST(ContractQDuel, EndTickRecreatesRoomWithUpdatedStake) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - increaseEnergy(qduel.state()->team(), 1); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(8, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund owner so next stake can be doubled. - increaseEnergy(owner, stake * 2); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 2, 0, stake * 2).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - - // Expire the room and expect a new one using computed next stake. - qduel.setDeterministicTime(2025, 1, 1, 1); - qduel.forceEndTick(); - - const QDUEL::RoomInfo roomAfter = qduel.state()->firstRoom(); - EXPECT_NE(roomAfter.roomId, roomBefore.roomId); - // Amount should reflect the raiseStep applied to the original stake. - EXPECT_EQ(roomAfter.amount, stake * 2); - - QDUEL::UserData userAfter{}; - EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); - // User should be locked into the new room with the updated stake. - EXPECT_EQ(userAfter.roomId, roomAfter.roomId); - EXPECT_EQ(userAfter.locked, stake * 2); - EXPECT_EQ(userAfter.depositedAmount, 0ULL); -} - -TEST(ContractQDuel, ConnectFinalizeIgnoresLockedAmount) -{ - ContractTestingQDuel qduel; - // Start from a deterministic time and unlocked state. - qduel.state()->setState(QDUEL::EState::NONE); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(9, 0, 0, 0); - const id opponent(10, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - // Fund both players so creation and join can proceed. - increaseEnergy(owner, stake); - increaseEnergy(opponent, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - - // On connect, finalize uses includeLocked=false, so owner data is cleared. - EXPECT_EQ(qduel.connectToRoom(opponent, roomBefore.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - // Room is removed and owner record should be purged after finalize. - EXPECT_FALSE(qduel.state()->hasRoom(roomBefore.roomId)); - QDUEL::UserData ownerAfter{}; - EXPECT_FALSE(qduel.state()->getUserData(owner, ownerAfter)); -} - -TEST(ContractQDuel, InitializeDefaults) -{ - ContractTestingQDuel qduel; - - EXPECT_EQ(qduel.state()->team(), QDUEL_TEAM_ADDRESS); - EXPECT_EQ(qduel.state()->minDuelAmount(), static_cast(QDUEL_MINIMUM_DUEL_AMOUNT)); - EXPECT_EQ(qduel.state()->devFee(), QDUEL_DEV_FEE_PERCENT_BPS); - EXPECT_EQ(qduel.state()->burnFee(), QDUEL_BURN_FEE_PERCENT_BPS); - EXPECT_EQ(qduel.state()->shareholdersFee(), QDUEL_SHAREHOLDERS_FEE_PERCENT_BPS); - EXPECT_EQ(qduel.state()->ttl(), QDUEL_TTL_HOURS); - EXPECT_EQ(qduel.state()->getState(), QDUEL::EState::NONE); - EXPECT_EQ(qduel.state()->roomCount(), 0ULL); -} - -TEST(ContractQDuel, CreateRoomStoresRoomAndUser) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - qduel.setDeterministicTime(2025, 1, 1, 0); - - const id owner(11, 0, 0, 0); - const id allowed(12, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - const uint64 reward = stake + 5000; - increaseEnergy(owner, reward); - - EXPECT_EQ(qduel.createRoom(owner, allowed, stake, 2, stake * 3, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - EXPECT_EQ(qduel.state()->roomCount(), 1ULL); - const QDUEL::RoomInfo room = qduel.state()->firstRoom(); - EXPECT_EQ(room.owner, owner); - EXPECT_EQ(room.allowedPlayer, allowed); - EXPECT_EQ(room.amount, stake); - EXPECT_EQ(room.closeTimer, static_cast(qduel.state()->ttl()) * 3600ULL); - const DateAndTime expectedNow(2025, 1, 1, 0, 0, 0); - EXPECT_EQ(room.lastUpdate, expectedNow); - - QDUEL::UserData user{}; - EXPECT_TRUE(qduel.state()->getUserData(owner, user)); - EXPECT_EQ(user.roomId, room.roomId); - EXPECT_EQ(user.allowedPlayer, allowed); - EXPECT_EQ(user.depositedAmount, reward - stake); - EXPECT_EQ(user.locked, stake); - EXPECT_EQ(user.stake, stake); - EXPECT_EQ(user.raiseStep, 2ULL); - EXPECT_EQ(user.maxStake, stake * 3); -} - -TEST(ContractQDuel, CreateRoomRejectsStakeBelowMinimum) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(13, 0, 0, 0); - const uint64 stake = qduel.state()->minDuelAmount() - 1; - const uint64 reward = qduel.state()->minDuelAmount(); - increaseEnergy(owner, reward); - const uint64 balanceBefore = getBalance(owner); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INVALID_VALUE)); - EXPECT_EQ(getBalance(owner), balanceBefore); - EXPECT_EQ(qduel.state()->roomCount(), 0ULL); -} - -TEST(ContractQDuel, CreateRoomRejectsMaxStakeBelowStake) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(14, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - const uint64 reward = stake; - increaseEnergy(owner, reward); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake - 1, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INVALID_VALUE)); - EXPECT_EQ(qduel.state()->roomCount(), 0ULL); -} - -TEST(ContractQDuel, CreateRoomRejectsRewardBelowMinimum) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(15, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - const uint64 reward = qduel.state()->minDuelAmount() - 1; - increaseEnergy(owner, reward); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); - EXPECT_EQ(qduel.state()->roomCount(), 0ULL); -} - -TEST(ContractQDuel, CreateRoomRejectsRewardBelowStake) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(16, 0, 0, 0); - const uint64 stake = qduel.state()->minDuelAmount() + 1000; - const uint64 reward = stake - 1; - increaseEnergy(owner, reward); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); - EXPECT_EQ(qduel.state()->roomCount(), 0ULL); -} - -TEST(ContractQDuel, CreateRoomRejectsDuplicateUser) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(17, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake * 2); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_ALREADY_EXISTS)); - EXPECT_EQ(qduel.state()->roomCount(), 1ULL); -} - -TEST(ContractQDuel, CreateRoomRejectsWhenRoomsFull) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const sint64 stake = qduel.state()->minDuelAmount(); - for (uint32 i = 0; i < QDUEL_MAX_NUMBER_OF_ROOMS; ++i) - { - const id owner(100 + i, 0, 0, 0); - qduel.setTick(i); - increaseEnergy(owner, stake); - const QDUEL::CreateRoom_output output = qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake); - EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)) << "at[" << i << "]"; - } - - EXPECT_EQ(qduel.state()->roomCount(), static_cast(QDUEL_MAX_NUMBER_OF_ROOMS)); - - const id extraOwner(9999, 0, 0, 0); - increaseEnergy(extraOwner, stake); - EXPECT_EQ(qduel.createRoom(extraOwner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_FULL)); -} - -TEST(ContractQDuel, ConnectToRoomRejectsMissingRoom) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id player(18, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(player, stake); - - EXPECT_EQ(qduel.connectToRoom(player, id(999, 0, 0, 0), stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_NOT_FOUND)); -} - -TEST(ContractQDuel, ConnectToRoomRejectsNotAllowedPlayer) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(19, 0, 0, 0); - const id allowed(20, 0, 0, 0); - const id other(21, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake); - increaseEnergy(other, stake); - - EXPECT_EQ(qduel.createRoom(owner, allowed, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo room = qduel.state()->firstRoom(); - - EXPECT_EQ(qduel.connectToRoom(other, room.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_ACCESS_DENIED)); -} - -TEST(ContractQDuel, ConnectToRoomRejectsInsufficientReward) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(22, 0, 0, 0); - const id opponent(23, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake); - increaseEnergy(opponent, stake - 1); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo room = qduel.state()->firstRoom(); - - EXPECT_EQ(qduel.connectToRoom(opponent, room.roomId, stake - 1).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); -} - -TEST(ContractQDuel, ConnectToRoomRefundsExcessRewardForLoser) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - id owner; - id opponent; - ASSERT_TRUE(findPlayersForWinner(qduel, true, owner, opponent)); - - const sint64 stake = qduel.state()->minDuelAmount(); - const uint64 reward = stake + 5000; - increaseEnergy(owner, stake); - increaseEnergy(opponent, reward); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const uint64 opponentBefore = getBalance(opponent); - - const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(opponent, qduel.state()->firstRoom().roomId, reward); - EXPECT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(connectOutput.winner, owner); - EXPECT_EQ(getBalance(opponent), opponentBefore - stake); -} - -TEST(ContractQDuel, ConnectFinalizeCreatesRoomFromDeposit) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(24, 0, 0, 0); - const id opponent(25, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake * 2); - increaseEnergy(opponent, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, 0, stake * 2).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); - - qduel.setTick(10); - - EXPECT_EQ(qduel.connectToRoom(opponent, roomBefore.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - EXPECT_EQ(qduel.state()->roomCount(), 1ULL); - const QDUEL::RoomInfo roomAfter = qduel.state()->firstRoom(); - EXPECT_NE(roomAfter.roomId, roomBefore.roomId); - EXPECT_EQ(roomAfter.owner, owner); - EXPECT_EQ(roomAfter.amount, stake); - - QDUEL::UserData userAfter{}; - EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); - EXPECT_EQ(userAfter.roomId, roomAfter.roomId); - EXPECT_EQ(userAfter.locked, stake); - EXPECT_EQ(userAfter.depositedAmount, 0ULL); -} - -TEST(ContractQDuel, GetWinnerPlayerIsOrderInvariant) -{ - ContractTestingQDuel qduel; - qduel.setTick(1234); - - const id player1(26, 0, 0, 0); - const id player2(27, 0, 0, 0); - - const id winnerForward = qduel.state()->computeWinner(player1, player2); - const id winnerReverse = qduel.state()->computeWinner(player2, player1); - EXPECT_EQ(winnerForward, winnerReverse); - EXPECT_TRUE(winnerForward == player1 || winnerForward == player2); -} - -TEST(ContractQDuel, CalculateRevenueMatchesExpectedSplits) -{ - ContractTestingQDuel qduel; - - constexpr uint64 amount = 1000000ULL; - QDUEL::CalculateRevenue_output output{}; - qduel.state()->calculateRevenue(amount, output); - - const uint64 expectedDev = (amount * qduel.state()->devFee()) / QDUEL_PERCENT_SCALE; - const uint64 expectedBurn = (amount * qduel.state()->burnFee()) / QDUEL_PERCENT_SCALE; - const uint64 expectedShareholders = ((amount * qduel.state()->shareholdersFee()) / QDUEL_PERCENT_SCALE) / 676ULL * 676ULL; - const uint64 expectedWinner = amount - (expectedDev + expectedBurn + expectedShareholders); - - EXPECT_EQ(output.devFee, expectedDev); - EXPECT_EQ(output.burnFee, expectedBurn); - EXPECT_EQ(output.shareholdersFee, expectedShareholders); - EXPECT_EQ(output.winner, expectedWinner); -} - -TEST(ContractQDuel, SetPercentFeesAccessDeniedAndGetPercentFees) -{ - ContractTestingQDuel qduel; - const QDUEL::GetPercentFees_output before = qduel.getPercentFees(); - - static constexpr sint64 userAmount = 10LL; - static constexpr uint8 devFee = 1; - static constexpr uint8 burnFee = 2; - static constexpr uint8 shareholdersFee = 3; - static constexpr uint16 percentScale = 4; - static constexpr sint64 reward = 10LL; - - const id user(28, 0, 0, 0); - increaseEnergy(user, userAmount); - const uint64 balanceBefore = getBalance(user); - - EXPECT_EQ(qduel.setPercentFees(user, devFee, burnFee, shareholdersFee, percentScale, reward).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::ACCESS_DENIED)); - EXPECT_EQ(getBalance(user), balanceBefore); - - const QDUEL::GetPercentFees_output after = qduel.getPercentFees(); - EXPECT_EQ(memcmp(&before, &after, sizeof(before)), 0); -} - -TEST(ContractQDuel, SetPercentFeesUpdatesState) -{ - ContractTestingQDuel qduel; - - static constexpr sint64 teamAmount = 1LL; - static constexpr uint8 devFee = 1; - static constexpr uint8 burnFee = 2; - static constexpr uint8 shareholdersFee = 3; - static constexpr uint16 percentScale = 4; - static constexpr sint64 reward = 1LL; - - increaseEnergy(qduel.state()->team(), teamAmount); - EXPECT_EQ(qduel.setPercentFees(qduel.state()->team(), devFee, burnFee, shareholdersFee, percentScale, reward).returnCode, - QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::GetPercentFees_output output = qduel.getPercentFees(); - EXPECT_EQ(output.devFeePercentBps, devFee); - EXPECT_EQ(output.burnFeePercentBps, burnFee); - EXPECT_EQ(output.shareholdersFeePercentBps, shareholdersFee); - EXPECT_EQ(static_cast(output.percentScale), static_cast(percentScale)); - EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); -} - -TEST(ContractQDuel, SetTTLHoursAccessDenied) -{ - ContractTestingQDuel qduel; - const uint8 ttlBefore = qduel.state()->ttl(); - - const id user(29, 0, 0, 0); - increaseEnergy(user, 5); - const uint64 balanceBefore = getBalance(user); - - EXPECT_EQ(qduel.setTtlHours(user, 5, 5).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ACCESS_DENIED)); - EXPECT_EQ(getBalance(user), balanceBefore); - EXPECT_EQ(qduel.state()->ttl(), ttlBefore); -} - -TEST(ContractQDuel, SetTTLHoursUpdatesState) -{ - ContractTestingQDuel qduel; - increaseEnergy(qduel.state()->team(), 1); - - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 6, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::GetTTLHours_output output = qduel.getTtlHours(); - EXPECT_EQ(output.ttlHours, 6); - EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); -} - -TEST(ContractQDuel, SetTTLHoursResetsCloseTimerForAllRooms) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - qduel.setDeterministicTime(2025, 1, 1, 0); - - increaseEnergy(qduel.state()->team(), 2); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 23, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const id owner1(300, 0, 0, 0); - const id owner2(301, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner1, stake); - increaseEnergy(owner2, stake); - - EXPECT_EQ(qduel.createRoom(owner1, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(qduel.createRoom(owner2, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - // Spend one hour so rooms have less than initial TTL left. - qduel.setDeterministicTime(2025, 1, 1, 1); - qduel.setTick(QDUEL_TICK_UPDATE_PERIOD); - qduel.endTick(); - - // Apply new TTL and force-reset all existing room timers to it. - qduel.setDeterministicTime(2025, 1, 1, 2); - EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::GetRooms_output roomsOutput = qduel.getRooms(); - const DateAndTime expectedNow(2025, 1, 1, 2, 0, 0); - uint64 seenRooms = 0; - for (uint32 i = 0; i < QDUEL_MAX_NUMBER_OF_ROOMS; ++i) - { - const QDUEL::RoomInfo room = roomsOutput.rooms.get(i); - if (room.roomId != id::zero()) - { - ++seenRooms; - EXPECT_EQ(room.closeTimer, 3600ULL); - EXPECT_EQ(room.lastUpdate, expectedNow); - } - } - EXPECT_EQ(seenRooms, 2ULL); -} - -TEST(ContractQDuel, GetRoomsReturnsActiveRooms) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner1(30, 0, 0, 0); - const id owner2(31, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner1, stake); - increaseEnergy(owner2, stake); - - EXPECT_EQ(qduel.createRoom(owner1, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(qduel.createRoom(owner2, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::GetRooms_output output = qduel.getRooms(); - EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - uint64 count = 0; - bool foundOwner1 = false; - bool foundOwner2 = false; - for (uint32 i = 0; i < QDUEL_MAX_NUMBER_OF_ROOMS; ++i) - { - const QDUEL::RoomInfo room = output.rooms.get(i); - if (room.roomId != id::zero()) - { - ++count; - EXPECT_TRUE(qduel.state()->hasRoom(room.roomId)); - if (room.owner == owner1) - { - foundOwner1 = true; - } - if (room.owner == owner2) - { - foundOwner2 = true; - } - } - } - EXPECT_EQ(count, qduel.state()->roomCount()); - EXPECT_TRUE(foundOwner1); - EXPECT_TRUE(foundOwner2); -} - -TEST(ContractQDuel, GetUserProfileReportsUserData) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id missingUser(3200, 0, 0, 0); - const QDUEL::GetUserProfile_output& missing = qduel.getUserProfile(missingUser); - EXPECT_EQ(missing.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); - - const id owner(32, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake + 200); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 2, stake * 2, stake + 200).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - const QDUEL::GetUserProfile_output profile = qduel.state()->getUserProfileFor(owner); - EXPECT_EQ(profile.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(profile.depositedAmount, 200ULL); - EXPECT_EQ(profile.locked, stake); - EXPECT_EQ(profile.stake, stake); - EXPECT_EQ(profile.raiseStep, 2ULL); - EXPECT_EQ(profile.maxStake, stake * 2); - EXPECT_NE(profile.roomId, id::zero()); -} - -TEST(ContractQDuel, DepositValidationsAndUpdatesBalance) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id missingUser(33, 0, 0, 0); - increaseEnergy(missingUser, 1); - EXPECT_EQ(qduel.deposit(missingUser, 0).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INVALID_VALUE)); - - increaseEnergy(missingUser, 100); - const uint64 missingBefore = getBalance(missingUser); - EXPECT_EQ(qduel.deposit(missingUser, 100).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); - EXPECT_EQ(getBalance(missingUser), missingBefore); - - const id owner(34, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake); - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - QDUEL::UserData before{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, before)); - increaseEnergy(owner, 500); - EXPECT_EQ(qduel.deposit(owner, 500).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - QDUEL::UserData after{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, after)); - EXPECT_EQ(after.depositedAmount, before.depositedAmount + 500); -} - -TEST(ContractQDuel, WithdrawValidationsAndTransfers) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id missingUser(35, 0, 0, 0); - increaseEnergy(missingUser, 1); - EXPECT_EQ(qduel.withdraw(missingUser, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); - - const id owner(36, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake + 1000); - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake + 1000).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - EXPECT_EQ(qduel.withdraw(owner, 0).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INSUFFICIENT_FREE_DEPOSIT)); - EXPECT_EQ(qduel.withdraw(owner, 2000).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INSUFFICIENT_FREE_DEPOSIT)); - - const uint64 balanceBefore = getBalance(owner); - EXPECT_EQ(qduel.withdraw(owner, 500).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(getBalance(owner), balanceBefore + 500); - - QDUEL::UserData userAfter{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, userAfter)); - EXPECT_EQ(userAfter.depositedAmount, 500ULL); -} - -TEST(ContractQDuel, CloseRoomReturnsFundsAndRemovesRoomAndUser) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(360, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - const sint64 deposit = 700; - const sint64 reward = stake + deposit; - increaseEnergy(owner, reward); - const uint64 ownerBalanceBeforeCreate = getBalance(owner); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo room = qduel.state()->firstRoom(); - ASSERT_NE(room.roomId, id::zero()); - ASSERT_TRUE(qduel.state()->hasRoom(room.roomId)); - - const uint64 ownerBalanceAfterCreate = getBalance(owner); - EXPECT_EQ(ownerBalanceAfterCreate, ownerBalanceBeforeCreate - reward); - - EXPECT_EQ(qduel.closeRoom(owner).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_FALSE(qduel.state()->hasRoom(room.roomId)); - - QDUEL::UserData ownerData{}; - EXPECT_FALSE(qduel.state()->getUserData(owner, ownerData)); - EXPECT_EQ(getBalance(owner), ownerBalanceBeforeCreate); -} - -TEST(ContractQDuel, CloseRoomUserNotFound) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id user(361, 0, 0, 0); - increaseEnergy(user, 50); - const uint64 balanceBefore = getBalance(user); - - EXPECT_EQ(qduel.closeRoom(user, 50).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); - EXPECT_EQ(getBalance(user), balanceBefore); -} - -TEST(ContractQDuel, CloseRoomAccessDeniedWhenUserPointsToForeignRoom) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(362, 0, 0, 0); - const id attacker(363, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - increaseEnergy(owner, stake); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - const QDUEL::RoomInfo ownerRoom = qduel.state()->firstRoom(); - ASSERT_NE(ownerRoom.roomId, id::zero()); - - // Inject an inconsistent user record that points to someone else's room. - QDUEL::UserData forged{}; - forged.userId = attacker; - forged.roomId = ownerRoom.roomId; - forged.allowedPlayer = NULL_ID; - forged.depositedAmount = 123; - forged.locked = 456; - forged.stake = stake; - forged.raiseStep = 1; - forged.maxStake = stake; - qduel.state()->setUserData(forged); - - increaseEnergy(attacker, 25); - const uint64 attackerBefore = getBalance(attacker); - EXPECT_EQ(qduel.closeRoom(attacker, 25).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_ACCESS_DENIED)); - EXPECT_EQ(getBalance(attacker), attackerBefore); - - // Nothing should be deleted on access denied. - EXPECT_TRUE(qduel.state()->hasRoom(ownerRoom.roomId)); - QDUEL::UserData attackerAfter{}; - EXPECT_TRUE(qduel.state()->getUserData(attacker, attackerAfter)); -} - -TEST(ContractQDuel, CloseRoomSucceedsWhenRoomMissingAndStillRefundsAndRemovesUser) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id owner(364, 0, 0, 0); - const sint64 stake = qduel.state()->minDuelAmount(); - const sint64 deposit = 900; - const sint64 reward = stake + deposit; - increaseEnergy(owner, reward); - const uint64 ownerBalanceBeforeCreate = getBalance(owner); - - EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - - // Simulate stale user->room pointer (room missing in map). - QDUEL::UserData ownerData{}; - ASSERT_TRUE(qduel.state()->getUserData(owner, ownerData)); - ownerData.roomId = id(999999, 0, 0, 0); - qduel.state()->setUserData(ownerData); - - EXPECT_EQ(qduel.closeRoom(owner).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_FALSE(qduel.state()->getUserData(owner, ownerData)); - EXPECT_EQ(getBalance(owner), ownerBalanceBeforeCreate); -} - -TEST(ContractQDuel, CloseRoomWithZeroFundsRemovesUserWithoutTransfer) -{ - ContractTestingQDuel qduel; - qduel.state()->setState(QDUEL::EState::NONE); - - const id user(365, 0, 0, 0); - increaseEnergy(user, 1); - - QDUEL::UserData data{}; - data.userId = user; - data.roomId = id(888888, 0, 0, 0); - data.allowedPlayer = NULL_ID; - data.depositedAmount = 0; - data.locked = 0; - data.stake = qduel.state()->minDuelAmount(); - data.raiseStep = 1; - data.maxStake = data.stake; - qduel.state()->setUserData(data); - - const uint64 balanceBefore = getBalance(user); - EXPECT_EQ(qduel.closeRoom(user).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); - EXPECT_EQ(getBalance(user), balanceBefore); - - QDUEL::UserData after{}; - EXPECT_FALSE(qduel.state()->getUserData(user, after)); -} - -TEST(ContractQDuel, ConnectToRoomDistributesFeesPlayer1Wins) -{ - ContractTestingQDuel qduel; - - id player1; - id player2; - ASSERT_TRUE(findPlayersForWinner(qduel, true, player1, player2)); - runFullGameCycleWithFees(qduel, player1, player2, player1); -} - -TEST(ContractQDuel, ConnectToRoomDistributesFeesPlayer2Wins) -{ - ContractTestingQDuel qduel; - - id player1; - id player2; - ASSERT_TRUE(findPlayersForWinner(qduel, false, player1, player2)); - runFullGameCycleWithFees(qduel, player1, player2, player2); -} diff --git a/test/contract_qearn.cpp b/test/contract_qearn.cpp deleted file mode 100644 index 330814de3..000000000 --- a/test/contract_qearn.cpp +++ /dev/null @@ -1,983 +0,0 @@ -#define NO_UEFI - -#include -#include - -#include "contract_testing.h" - -#define PRINT_TEST_INFO 0 - -// test config: -// - 0 is fastest -// - 1 to enable more tests with random lock/unlock -// - 2 to enable even more tests with random lock/unlock -// - 3 to also check values more often (expensive functions) -// - 4 to also test out-of-user error -#define LARGE_SCALE_TEST 0 - - -static const id QEARN_CONTRACT_ID(QEARN_CONTRACT_INDEX, 0, 0, 0); - -static std::mt19937_64 rand64; - -static id getUser(unsigned long long i); -static unsigned long long random(unsigned long long maxValue); - -static std::vector fullyUnlockedAmount; -static std::vector fullyUnlockedUser; - - -class QearnChecker : public QEARN -{ -public: - void checkLockerArray(bool beforeEndEpoch, bool printInfo = false) - { - // check that locker array is in consistent state - std::map epochTotalLocked; - uint32 minEpoch = 0xffff; - uint32 maxEpoch = 0; - for (uint64 idx = 0; idx < locker.capacity(); ++idx) - { - const QEARN::LockInfo& lock = locker.get(idx); - if (lock._lockedAmount == 0) - { - EXPECT_TRUE(isZero(lock.ID)); - EXPECT_EQ(lock._lockedEpoch, 0); - } - else - { - EXPECT_GT(lock._lockedAmount, QEARN_MINIMUM_LOCKING_AMOUNT); - EXPECT_LE(lock._lockedAmount, QEARN_MAX_LOCK_AMOUNT); - EXPECT_FALSE(isZero(lock.ID)); - const QEARN::EpochIndexInfo& epochRange = _epochIndex.get(lock._lockedEpoch); - EXPECT_GE(idx, epochRange.startIndex); - EXPECT_LT(idx, epochRange.endIndex); - epochTotalLocked[lock._lockedEpoch] += lock._lockedAmount; - - minEpoch = std::min(minEpoch, lock._lockedEpoch); - maxEpoch = std::max(minEpoch, lock._lockedEpoch); - } - } - - const uint32 beginEpoch = std::max((int)contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch, system.epoch - 52); - EXPECT_LE(beginEpoch, minEpoch); - EXPECT_LE(maxEpoch, uint32(system.epoch)); - - if (PRINT_TEST_INFO) - { - const char * beforeAfterStr = (beforeEndEpoch) ? "Before" : "After"; - std::cout << "--- " << beforeAfterStr << " END_EPOCH in epoch " << system.epoch << std::endl; - } - - for (uint32 epoch = beginEpoch; epoch <= system.epoch; ++epoch) - { - const QEARN::RoundInfo& currentRoundInfo = _currentRoundInfo.get(epoch); - //if (!currentRoundInfo._Epoch_Bonus_Amount && !currentRoundInfo._Total_Locked_Amount) - // continue; - unsigned long long totalLocked = epochTotalLocked[epoch]; - if (printInfo) - { - std::cout << "Total locked amount in epoch " << epoch << " = " << totalLocked << ", total bonus " << currentRoundInfo._epochBonusAmount << std::endl; - } - if (beforeEndEpoch || epoch != system.epoch - 52) - EXPECT_EQ(currentRoundInfo._totalLockedAmount, totalLocked); - } - - // check that old epoch indices have been reset - for (uint32 epoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; epoch < beginEpoch; ++epoch) - { - EXPECT_EQ(this->_epochIndex.get(epoch).startIndex, this->_epochIndex.get(epoch).endIndex); - } - } - - void checkGetUnlockedInfo(uint32 epoch) - { - fullyUnlockedAmount.clear(); - fullyUnlockedUser.clear(); - - const QEARN::EpochIndexInfo& epochIndex = _epochIndex.get(epoch); - for(uint64 idx = epochIndex.startIndex; idx < epochIndex.endIndex; ++idx) - { - if(locker.get(idx)._lockedAmount != 0) - { - fullyUnlockedAmount.push_back(locker.get(idx)._lockedAmount); - fullyUnlockedUser.push_back(locker.get(idx).ID); - } - } - } - - void checkFullyUnlockedAmount() - { - for(uint32 idx = 0; idx < _fullyUnlockedCnt; idx++) - { - const QEARN::HistoryInfo& FullyUnlockedInfo = fullyUnlocker.get(idx); - - EXPECT_EQ(fullyUnlockedAmount[idx], FullyUnlockedInfo._unlockedAmount); - EXPECT_EQ(fullyUnlockedUser[idx], FullyUnlockedInfo._unlockedID); - } - } - - void checkStatsPerEpoch(getBurnedAndBoostedStatsPerEpoch_output result, uint16 epoch) - { - EXPECT_EQ(result.boostedAmount, statsInfo.get(epoch).boostedAmount); - EXPECT_EQ(result.burnedAmount, statsInfo.get(epoch).burnedAmount); - EXPECT_EQ(result.rewardedAmount, statsInfo.get(epoch).rewardedAmount); - EXPECT_EQ(result.boostedPercent, div(result.boostedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount)); - EXPECT_EQ(result.burnedPercent, div(result.burnedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount)); - EXPECT_EQ(result.rewardedPercent, div(result.rewardedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount)); - } - - void checkStatsForAll(getBurnedAndBoostedStats_output result) - { - uint64 totalBurnedAmountInSC = 0; - uint64 totalBoostedAmountInSC = 0; - uint64 totalRewardedAmountInSC = 0; - uint64 sumBurnedPercent = 0; - uint64 sumBoostedPercent = 0; - uint64 sumRewardedPercent = 0; - - for(uint32 epoch = 138 ; epoch < system.epoch; epoch++) - { - totalBurnedAmountInSC += statsInfo.get(epoch).burnedAmount; - totalBoostedAmountInSC += statsInfo.get(epoch).boostedAmount; - totalRewardedAmountInSC += statsInfo.get(epoch).rewardedAmount; - - sumBurnedPercent += div(statsInfo.get(epoch).burnedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount); - sumBoostedPercent += div(statsInfo.get(epoch).boostedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount); - sumRewardedPercent += div(statsInfo.get(epoch).rewardedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount); - } - - EXPECT_EQ(result.boostedAmount, totalBoostedAmountInSC); - EXPECT_EQ(result.burnedAmount, totalBurnedAmountInSC); - EXPECT_EQ(result.rewardedAmount, totalRewardedAmountInSC); - EXPECT_EQ(result.averageBoostedPercent, div(sumBoostedPercent, system.epoch - 138ULL)); - EXPECT_EQ(result.averageBurnedPercent, div(sumBurnedPercent, system.epoch - 138ULL)); - EXPECT_EQ(result.averageRewardedPercent, div(sumRewardedPercent, system.epoch - 138ULL)); - } - - QEARN::EpochIndexInfo getEpochIndex(uint32 epoch) const - { - return _epochIndex.get(epoch); - } -}; - -class ContractTestingQearn : protected ContractTesting -{ - struct UnlockTableEntry - { - unsigned long long rewardPercent; - unsigned long long burnPercent; - }; - std::vector epochChangesToUnlockParams; - -public: - ContractTestingQearn() - { - INIT_CONTRACT(QEARN); - initEmptySpectrum(); - rand64.seed(42); - - for (unsigned int epChanges = 0; epChanges <= 52; ++epChanges) - { - if (epChanges <= 4) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 0, 0 }); - else if (epChanges <= 12) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 5, 45 }); - else if (epChanges <= 16) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 10, 45 }); - else if (epChanges <= 20) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 15, 40 }); - else if (epChanges <= 24) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 20, 40 }); - else if (epChanges <= 28) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 25, 35 }); - else if (epChanges <= 32) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 30, 35 }); - else if (epChanges <= 36) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 35, 35 }); - else if (epChanges <= 40) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 40, 30 }); - else if (epChanges <= 44) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 45, 30 }); - else if (epChanges <= 48) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 50, 30 }); - else if (epChanges <= 52) - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 55, 25 }); - else - epochChangesToUnlockParams.push_back(UnlockTableEntry{ 100, 0 }); - } - } - - QearnChecker* getState() - { - return (QearnChecker*)contractStates[QEARN_CONTRACT_INDEX]; - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(QEARN_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - - // If there is no entry for this epoch in allEpochData, create one with default init (all 0) - allEpochData[system.epoch]; - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QEARN_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - QEARN::getLockInfoPerEpoch_output getLockInfoPerEpoch(uint16 epoch) const - { - QEARN::getLockInfoPerEpoch_input input{ epoch }; - QEARN::getLockInfoPerEpoch_output output; - callFunction(QEARN_CONTRACT_INDEX, 1, input, output); - return output; - } - - uint64 getUserLockedInfo(uint16 epoch, const id& user) const - { - QEARN::getUserLockedInfo_input input; - input.epoch = epoch; - input.user = user; - QEARN::getUserLockedInfo_output output; - callFunction(QEARN_CONTRACT_INDEX, 2, input, output); - return output.lockedAmount; - } - - uint32 getStateOfRound(uint16 epoch) const - { - QEARN::getStateOfRound_input input{ epoch }; - QEARN::getStateOfRound_output output; - callFunction(QEARN_CONTRACT_INDEX, 3, input, output); - return output.state; - } - - uint64 getUserLockStatus(const id& user) const - { - QEARN::getUserLockStatus_input input{ user }; - QEARN::getUserLockStatus_output output; - callFunction(QEARN_CONTRACT_INDEX, 4, input, output); - return output.status; - } - - QEARN::getEndedStatus_output getEndedStatus(const id& user) const - { - QEARN::getEndedStatus_input input{ user }; - QEARN::getEndedStatus_output output; - callFunction(QEARN_CONTRACT_INDEX, 5, input, output); - return output; - } - - QEARN::getStatsPerEpoch_output getStatsPerEpoch(uint16 epoch) const - { - QEARN::getStatsPerEpoch_input input{ epoch }; - QEARN::getStatsPerEpoch_output output; - callFunction(QEARN_CONTRACT_INDEX, 6, input, output); - return output; - } - - QEARN::getBurnedAndBoostedStats_output getBurnedAndBoostedStats() const - { - QEARN::getBurnedAndBoostedStats_input input; - QEARN::getBurnedAndBoostedStats_output output; - callFunction(QEARN_CONTRACT_INDEX, 7, input, output); - return output; - } - - QEARN::getBurnedAndBoostedStatsPerEpoch_output getBurnedAndBoostedStatsPerEpoch(uint16 epoch) const - { - QEARN::getBurnedAndBoostedStatsPerEpoch_input input{ epoch }; - QEARN::getBurnedAndBoostedStatsPerEpoch_output output; - callFunction(QEARN_CONTRACT_INDEX, 8, input, output); - return output; - } - - sint32 lock(const id& user, long long amount, bool expectSuccess = true) - { - QEARN::lock_input input; - QEARN::lock_output output; - EXPECT_EQ(invokeUserProcedure(QEARN_CONTRACT_INDEX, 1, input, output, user, amount), expectSuccess); - return output.returnCode; - } - - sint32 unlock(const id& user, long long amount, uint16 lockedEpoch, bool expectSuccess = true) - { - QEARN::unlock_input input; - input.amount = amount; - input.lockedEpoch = lockedEpoch; - QEARN::unlock_output output; - EXPECT_EQ(invokeUserProcedure(QEARN_CONTRACT_INDEX, 2, input, output, user, 0), expectSuccess); - return output.returnCode; - } - - struct UserData - { - std::map locked; - }; - - std::map allUserData; - - struct EpochData - { - unsigned long long initialBonusAmount; - unsigned long long initialTotalLockedAmount; - unsigned long long bonusAmount; - unsigned long long amountCurrentlyLocked; - }; - - std::map allEpochData; - - std::map amountUnlockPerUser; - - void simulateDonation(const unsigned long long donationAmount) - { - increaseEnergy(QEARN_CONTRACT_ID, donationAmount); - - unsigned long long& totalBonusAmount = allEpochData[system.epoch + 1].bonusAmount; - totalBonusAmount += donationAmount; - if (totalBonusAmount > QEARN_MAX_BONUS_AMOUNT) - totalBonusAmount = QEARN_MAX_BONUS_AMOUNT; - } - - bool lockAndCheck(const id& user, uint64 amountLock, bool expectSuccess = true) - { - // check consistency of epoch info expected vs returned by contract - checkEpochInfo(system.epoch); - - // get amount and balances before action -#if LARGE_SCALE_TEST >= 3 - uint64 amountBefore = getUserLockedInfo(system.epoch, user); - EXPECT_EQ(allUserData[user].locked[system.epoch], amountBefore); -#else - uint64 amountBefore = allUserData[user].locked[system.epoch]; -#endif - sint64 userBalanceBefore = getBalance(user); - sint64 contractBalanceBefore = getBalance(QEARN_CONTRACT_ID); - - // call lock prcoedure - uint32 retCode = lock(user, amountLock, expectSuccess); - - // check new amount and balances - uint64 amountAfter = getUserLockedInfo(system.epoch, user); - sint64 userBalanceAfter = getBalance(user); - sint64 contractBalanceAfter = getBalance(QEARN_CONTRACT_ID); - if (retCode == QEARN_LOCK_SUCCESS && expectSuccess) - { - EXPECT_EQ(amountAfter, amountBefore + amountLock); - EXPECT_EQ(userBalanceAfter, userBalanceBefore - amountLock); - EXPECT_EQ(contractBalanceAfter, contractBalanceBefore + amountLock); - - allUserData[user].locked[system.epoch] += amountLock; - allEpochData[system.epoch].amountCurrentlyLocked += amountLock; - allEpochData[system.epoch].initialTotalLockedAmount += amountLock; - } - else - { - EXPECT_EQ(amountAfter, amountBefore); - EXPECT_EQ(userBalanceAfter, userBalanceBefore); - EXPECT_EQ(contractBalanceAfter, contractBalanceBefore); - } - - if (!expectSuccess) - return false; - - // check return code - if (retCode != QEARN_OVERFLOW_USER) - { - if (amountLock < QEARN_MINIMUM_LOCKING_AMOUNT || system.epoch < QEARN_INITIAL_EPOCH) - { - EXPECT_EQ(retCode, QEARN_INVALID_INPUT_AMOUNT); - } - else if (amountBefore + amountLock > QEARN_MAX_LOCK_AMOUNT) - { - EXPECT_EQ(retCode, QEARN_LIMIT_LOCK); - } - } - - return retCode == QEARN_LOCK_SUCCESS; - } - - unsigned long long getAndCheckRewardFactorTenmillionth(uint16 epoch) const - { - auto edIt = allEpochData.find(epoch); - EXPECT_NE(edIt, allEpochData.end()); - const EpochData& ed = edIt->second; - const unsigned long long rewardFactorTenmillionth = QPI::div(ed.bonusAmount * 10000000ULL, ed.amountCurrentlyLocked); - if (rewardFactorTenmillionth) - { - // detect overflow in computation of rewardFactorTenmillionth - const double rewardFactorTenmillionthDouble = ed.bonusAmount * 10000000.0 / ed.amountCurrentlyLocked; - double arthmeticError = double(rewardFactorTenmillionth) - rewardFactorTenmillionthDouble; - EXPECT_LT(fabs(arthmeticError), 1e5); - } - - return rewardFactorTenmillionth; - } - - void checkEpochInfo(uint16 epoch) - { - const auto scEpochInfo = getLockInfoPerEpoch(epoch); - EXPECT_LE(scEpochInfo.currentBonusAmount, QEARN_MAX_BONUS_AMOUNT); - if (epoch < QEARN_INITIAL_EPOCH) - return; - auto edIt = allEpochData.find(epoch); - EXPECT_NE(edIt, allEpochData.end()); - const EpochData& ed = edIt->second; - EXPECT_EQ(getAndCheckRewardFactorTenmillionth(epoch), scEpochInfo.yield); - EXPECT_EQ(ed.bonusAmount, scEpochInfo.currentBonusAmount); - EXPECT_EQ(ed.amountCurrentlyLocked, scEpochInfo.currentLockedAmount); - - const auto scStatsInfo = getStatsPerEpoch(epoch); - - EXPECT_EQ(scStatsInfo.earlyUnlockedAmount, ed.initialTotalLockedAmount - ed.amountCurrentlyLocked); - EXPECT_EQ(scStatsInfo.earlyUnlockedPercent, QPI::div((ed.initialTotalLockedAmount - ed.amountCurrentlyLocked) * 10000, ed.initialTotalLockedAmount)); - - const auto scBurnedAndBoostedStatsPerEpoch = getBurnedAndBoostedStatsPerEpoch(epoch); - const auto scBurnedAndBoostedStatsForAllEpoch = getBurnedAndBoostedStats(); - - getState()->checkStatsPerEpoch(scBurnedAndBoostedStatsPerEpoch, epoch); - getState()->checkStatsForAll(scBurnedAndBoostedStatsForAllEpoch); - - uint64 averageAPY = 0; - uint32 cnt = 0; - for(uint16 t = system.epoch - 1; t >= system.epoch - 52; t--) - { - auto preEdIt = allEpochData.find(t); - const EpochData& preED = preEdIt->second; - if (t < QEARN_INITIAL_EPOCH) - { - break; - } - if(preED.amountCurrentlyLocked == 0) - { - continue; - } - - cnt++; - EXPECT_EQ(getLockInfoPerEpoch(t).currentLockedAmount, preED.amountCurrentlyLocked); - averageAPY += QPI::div(preED.bonusAmount * 10000000ULL, preED.amountCurrentlyLocked); - } - EXPECT_EQ(scStatsInfo.totalLockedAmount, getBalance(QEARN_CONTRACT_ID)); - EXPECT_EQ(scStatsInfo.averageAPY, QPI::div(averageAPY, cnt * 1ULL)); - } - - bool unlockAndCheck(const id& user, uint16 lockingEpoch, uint64 amountUnlock, bool expectSuccess = true) - { - // make sure that user exists in spectrum - increaseEnergy(user, 1); - - // get old locked amount -#if LARGE_SCALE_TEST >= 3 - uint64 amountBefore = getUserLockedInfo(lockingEpoch, user); - EXPECT_EQ(allUserData[user].locked[lockingEpoch], amountBefore); -#else - uint64 amountBefore = allUserData[user].locked[lockingEpoch]; -#endif - sint64 userBalanceBefore = getBalance(user); - sint64 contractBalanceBefore = getBalance(QEARN_CONTRACT_ID); - - // call unlock prcoedure - uint32 retCode = unlock(user, amountUnlock, lockingEpoch); - - // check new locked amount and balances - uint64 amountAfter = getUserLockedInfo(lockingEpoch, user); - sint64 userBalanceAfter = getBalance(user); - sint64 contractBalanceAfter = getBalance(QEARN_CONTRACT_ID); - if (retCode == QEARN_UNLOCK_SUCCESS && expectSuccess) - { - EXPECT_GE(amountBefore, amountUnlock); - uint64 expectedAmountAfter = amountBefore - amountUnlock; - if (expectedAmountAfter < QEARN_MINIMUM_LOCKING_AMOUNT) - { - expectedAmountAfter = 0; - } - EXPECT_EQ(amountAfter, expectedAmountAfter); - uint64 amountUnlocked = amountBefore - amountAfter; - - uint16 epochTransitions = system.epoch - lockingEpoch; - unsigned long long rewardFactorTenmillionth = getAndCheckRewardFactorTenmillionth(lockingEpoch); - unsigned long long commonFactor = QPI::div(rewardFactorTenmillionth * amountUnlocked, 100ULL); - unsigned long long amountReward = QPI::div(commonFactor * epochChangesToUnlockParams[epochTransitions].rewardPercent, 10000000ULL); - unsigned long long amountBurn = QPI::div(commonFactor * epochChangesToUnlockParams[epochTransitions].burnPercent, 10000000ULL); - { - // Check for overflows - double commonFactorError = fabs((double(rewardFactorTenmillionth) * double(amountUnlocked) / 100.0) - commonFactor); - EXPECT_LT(commonFactorError, 1e3); - double amountRewardError = fabs((double(commonFactor) * double(epochChangesToUnlockParams[epochTransitions].rewardPercent) / 10000000.0) - amountReward); - EXPECT_LE(amountRewardError, 1); - double amountBurnError = fabs((double(commonFactor) * double(epochChangesToUnlockParams[epochTransitions].burnPercent) / 10000000.0) - amountBurn); - EXPECT_LE(amountBurnError, 1); - } - - allUserData[user].locked[lockingEpoch] -= amountUnlocked; - if(system.epoch == lockingEpoch) - { - allEpochData[lockingEpoch].initialTotalLockedAmount -= amountUnlocked; - } - allEpochData[lockingEpoch].amountCurrentlyLocked -= amountUnlocked; - allEpochData[lockingEpoch].bonusAmount -= amountReward + amountBurn; - - // Edge case of unlocking of all locked funds in previous epoch -> bonus added to next round - if (lockingEpoch != system.epoch && !allEpochData[lockingEpoch].amountCurrentlyLocked) - { - amountBurn += allEpochData[lockingEpoch].bonusAmount; - allEpochData[lockingEpoch].bonusAmount = 0; - } - - EXPECT_EQ(userBalanceAfter, userBalanceBefore + amountUnlocked + amountReward); - EXPECT_EQ(contractBalanceAfter, contractBalanceBefore - amountUnlocked - amountReward - amountBurn); - - // Check consistency of epoch info expected vs returned by contract - checkEpochInfo(lockingEpoch); - - // getEndedStatus() only included Early_Unlocked_Amount if unlocked after locking epoch - if (lockingEpoch != system.epoch) - { - amountUnlockPerUser[user] += amountUnlocked; - } - } - else - { - EXPECT_EQ(amountAfter, amountBefore); - EXPECT_EQ(userBalanceAfter, userBalanceBefore); - EXPECT_EQ(contractBalanceAfter, contractBalanceBefore); - } - - return retCode == QEARN_UNLOCK_SUCCESS; - } - - void endEpochAndCheck() - { - // check getStateOfRound - uint16 payoutEpoch = system.epoch - 52; - EXPECT_EQ(getStateOfRound(QEARN_INITIAL_EPOCH - 1), 2); - EXPECT_EQ(getStateOfRound(payoutEpoch - 1), 2); - EXPECT_EQ(getStateOfRound(payoutEpoch), (payoutEpoch >= QEARN_INITIAL_EPOCH) ? 1 : 2); - EXPECT_EQ(getStateOfRound(system.epoch - 1), (system.epoch - 1 >= QEARN_INITIAL_EPOCH) ? 1 : 2); - EXPECT_EQ(getStateOfRound(system.epoch), (system.epoch >= QEARN_INITIAL_EPOCH) ? 1 : 2); - EXPECT_EQ(getStateOfRound(system.epoch + 1), (system.epoch + 1 >= QEARN_INITIAL_EPOCH) ? 0 : 2); - - // test getUserLockStatus() - { - id user = getUser(random(10)); - uint64 lockStatus = getUserLockStatus(user); - const auto userDataIt = allUserData.find(user); - if (userDataIt == allUserData.end()) - { - EXPECT_EQ(lockStatus, 0); - } - else - { - auto& userData = userDataIt->second; - for (int i = 0; i <= 52; ++i) - { - if (lockStatus & 1) - { - EXPECT_GT(userData.locked[system.epoch - i], 0ll); - } - else - { - EXPECT_EQ(userData.locked[system.epoch - i], 0ll); - } - lockStatus >>= 1; - } - } - } - - // check unlocked amounts returned by getEndedStatus() - for (const auto& userAmountPairs : amountUnlockPerUser) - { - QEARN::getEndedStatus_output endedStatus = getEndedStatus(userAmountPairs.first); - EXPECT_EQ(userAmountPairs.second, endedStatus.earlyUnlockedAmount); - } - - checkEpochInfo(system.epoch); - - bool beforeEndEpoch = true; - getState()->checkLockerArray(beforeEndEpoch, PRINT_TEST_INFO); - getState()->checkGetUnlockedInfo(payoutEpoch); - - // get entity balances to check payout in END_EPOCH - std::map oldUserBalance; - long long oldContractBalance = getBalance(QEARN_CONTRACT_ID); - for (const auto& userIdDataPair : allUserData) - { - const id& user = userIdDataPair.first; - oldUserBalance[user] = getBalance(user); - } - checkEpochInfo(payoutEpoch); - - amountUnlockPerUser.clear(); - endEpoch(); - - // check payout after END_EPOCH - bool expectPayout = (allEpochData.find(payoutEpoch) != allEpochData.end()); - checkEpochInfo(payoutEpoch); - if (expectPayout) - { - // compute and check expected payouts - unsigned long long rewardFactorTenmillionth = getAndCheckRewardFactorTenmillionth(payoutEpoch); - unsigned long long totalRewardsPaid = 0; - const EpochData& ed = allEpochData[payoutEpoch]; - - for (const auto& userIdBalancePair : oldUserBalance) - { - const id& user = userIdBalancePair.first; - const long long oldUserBalance = userIdBalancePair.second; - const UserData& userData = allUserData[user]; - - auto userLockedAmountIter = userData.locked.find(payoutEpoch); - if (userLockedAmountIter == userData.locked.end() || userLockedAmountIter->second == 0) - continue; - const long long userLockedAmount = userLockedAmountIter->second; - const unsigned long long userReward = userLockedAmount * rewardFactorTenmillionth / 10000000ULL; - if (rewardFactorTenmillionth) - EXPECT_EQ((userLockedAmount * rewardFactorTenmillionth) / rewardFactorTenmillionth, userLockedAmount); - totalRewardsPaid += userReward; - - EXPECT_EQ(oldUserBalance + userLockedAmount + userReward, getBalance(user)); - } - EXPECT_EQ(oldContractBalance - ed.bonusAmount - ed.amountCurrentlyLocked, getBalance(QEARN_CONTRACT_ID)); - - - // all the bonus that has not been paid is burned (remainder due to inaccurate arithmetic and full bonus if nothing is locked until the end) - EXPECT_LE(totalRewardsPaid, ed.bonusAmount); - if (ed.amountCurrentlyLocked && ed.bonusAmount) - EXPECT_GE(QPI::div(totalRewardsPaid * 1000, ed.bonusAmount), 998); // only small part of bonus should be burned - else - EXPECT_EQ(totalRewardsPaid, 0ull); - } - else - { - // no payout expected - for (const auto& userIdBalancePair : oldUserBalance) - { - const id& user = userIdBalancePair.first; - const long long oldUserBalance = userIdBalancePair.second; - const long long currentUserBalance = getBalance(user); - EXPECT_EQ(oldUserBalance, currentUserBalance); - } - EXPECT_EQ(oldContractBalance, getBalance(QEARN_CONTRACT_ID)); - } - - beforeEndEpoch = false; - getState()->checkLockerArray(beforeEndEpoch, PRINT_TEST_INFO); - getState()->checkFullyUnlockedAmount(); - } -}; - - -static id getUser(unsigned long long i) -{ - return id(i, i / 2 + 4, i + 10, i * 3 + 8); -} - -static unsigned long long random(unsigned long long maxValue) -{ - return rand64() % (maxValue + 1); -} - -static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) -{ - unsigned long long userCount = random(maxNum); - std::vector users; - users.reserve(userCount); - for (unsigned int i = 0; i < userCount; ++i) - { - unsigned long long userIdx = random(totalUsers - 1); - users.push_back(getUser(userIdx)); - } - return users; -} - - -TEST(TestContractQearn, ErrorChecking) -{ - ContractTestingQearn qearn; - id user(1, 2, 3, 4); - - system.epoch = QEARN_INITIAL_EPOCH - 1; - - qearn.beginEpoch(); - - // special test case: trying to lock/unlock before QEARN_INITIAL_EPOCH must fail - { - id user2(98765, 43, 2, 1); - increaseEnergy(user2, QEARN_MAX_LOCK_AMOUNT); - EXPECT_FALSE(qearn.lockAndCheck(user2, QEARN_MAX_LOCK_AMOUNT)); - EXPECT_EQ(qearn.unlock(user2, QEARN_MAX_LOCK_AMOUNT, system.epoch), QEARN_INVALID_INPUT_LOCKED_EPOCH); - } - - qearn.endEpoch(); - - system.epoch = QEARN_INITIAL_EPOCH; - - qearn.beginEpoch(); - - // test cases, for which procedures is not executed: - { - // 1. non-existing entities = invalid ID) - EXPECT_FALSE(qearn.lockAndCheck(id::zero(), QEARN_MAX_LOCK_AMOUNT, false)); - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MAX_LOCK_AMOUNT, false)); - - // 2. valid ID but negative amount / insufficient balance - increaseEnergy(user, 1); - EXPECT_FALSE(qearn.lockAndCheck(user, -10, false)); - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT, false)); - } - - // test cases, for which procedure is executed (valid ID, enough balance) - increaseEnergy(user, QEARN_MAX_LOCK_AMOUNT * 1000); - { - EXPECT_FALSE(qearn.lockAndCheck(user, 0)); - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT / 2)); - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT - 1)); - - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MAX_LOCK_AMOUNT + 1)); - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MAX_LOCK_AMOUNT * 2)); - } - - // in order trigger out-of-lock-slots error, lock with many users -#if LARGE_SCALE_TEST >= 4 - // notes: - disabled by default because it takes long - // - seems like the last locker slot is never used in QEARN (FIXME) - for (uint64 i = 0; i < QEARN_MAX_LOCKS - 1; ++i) - { - id otherUser(i, 42, 1234, 642); - long long amount = QEARN_MINIMUM_LOCKING_AMOUNT + (7 * i) % (QEARN_MAX_LOCK_AMOUNT - QEARN_MINIMUM_LOCKING_AMOUNT); - increaseEnergy(otherUser, amount); - EXPECT_TRUE(qearn.lockAndCheck(otherUser, amount)); - } - EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT)); -#endif - - // note: lock implements no checking of system.epoch - - // for unlock, successfully lock some funds - id otherUser(1, 42, 1234, 642); - long long amount = QEARN_MINIMUM_LOCKING_AMOUNT; - increaseEnergy(otherUser, amount); - EXPECT_TRUE(qearn.lockAndCheck(otherUser, amount)); - - // unlock with too high amount - EXPECT_EQ(qearn.unlock(otherUser, QEARN_MAX_LOCK_AMOUNT + 1, system.epoch), QEARN_INVALID_INPUT_UNLOCK_AMOUNT); - - // unlock with too low amount - EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT - 1, system.epoch), QEARN_INVALID_INPUT_AMOUNT); - - // unlock with wrong user - EXPECT_EQ(qearn.unlock(user, QEARN_MINIMUM_LOCKING_AMOUNT, system.epoch), QEARN_EMPTY_LOCKED); - - // unlock with wrong epoch - EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT, 1), QEARN_INVALID_INPUT_LOCKED_EPOCH); - EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT, QEARN_MAX_EPOCHS + 1), QEARN_INVALID_INPUT_LOCKED_EPOCH); - - // finally, test success case - EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT, system.epoch), QEARN_UNLOCK_SUCCESS); -} - -// Test case for gap removal logic in overflow check (lines 635-656 in Qearn.h) -// This test verifies that when the locker array is near capacity and contains gaps, -// attempting to lock triggers gap removal, allowing the lock to succeed. -// Note: This test is disabled by default because it requires filling many slots (QEARN_MAX_LOCKS - 1) -// Enable with LARGE_SCALE_TEST >= 4 to run this comprehensive test - -#if LARGE_SCALE_TEST >= 4 -TEST(TestContractQearn, GapRemovalOnOverflow) -{ - std::cout << "gap removal test. If you want to test this case as soon, please set the QEARN_MAX_LOCKS to a smaller value on the contract." << std::endl; - ContractTestingQearn qearn; - - system.epoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; - qearn.beginEpoch(); - qearn.endEpoch(); - - system.epoch = QEARN_INITIAL_EPOCH; - - qearn.beginEpoch(); - - // Create a scenario where we fill up the locker array and create gaps - // Strategy: Fill up to near capacity, unlock some to create gaps, - // then try to lock again which triggers gap removal - - const uint64 numGapsToCreate = 100; // Create some gaps by unlocking - // Fill up to QEARN_MAX_LOCKS - 1 so that after unlocking (which doesn't change endIndex), - // the next lock attempt will trigger the overflow check (endIndex >= QEARN_MAX_LOCKS - 1) - const uint64 targetEndIndex = QEARN_MAX_LOCKS - 1; - - std::vector usersToUnlock; - usersToUnlock.reserve(numGapsToCreate); - - // Step 1: Fill up the array to near capacity - // We'll fill up to targetEndIndex, then unlock some to create gaps - // The endIndex will stay high, so when we try to lock again, it will trigger overflow check - for (uint64 i = 0; i < targetEndIndex; ++i) - { - id testUser(i, 100, 200, 300); - uint64 amount = QEARN_MINIMUM_LOCKING_AMOUNT + 1; - increaseEnergy(testUser, amount); - EXPECT_TRUE(qearn.lockAndCheck(testUser, amount)); - - // Store some users to unlock later (to create gaps) - if (i < numGapsToCreate) - { - usersToUnlock.push_back(testUser); - } - } - - // Step 2: Verify we're near capacity - QearnChecker* state = qearn.getState(); - uint32 endIndexBeforeUnlock = state->getEpochIndex(system.epoch).endIndex; - EXPECT_GE(endIndexBeforeUnlock, targetEndIndex); - - // Step 3: Unlock some users to create gaps in the locker array - // Note: endIndex doesn't decrease when unlocking, so gaps are created but endIndex stays high - for (const auto& userToUnlock : usersToUnlock) - { - uint64 unlockAmount = QEARN_MINIMUM_LOCKING_AMOUNT + 1; - EXPECT_EQ(qearn.unlock(userToUnlock, unlockAmount, system.epoch), QEARN_UNLOCK_SUCCESS); - } - - // Step 4: Verify endIndex is still high (gaps created but not removed yet) - uint32 endIndexAfterUnlock = state->getEpochIndex(system.epoch).endIndex; - EXPECT_EQ(endIndexAfterUnlock, endIndexBeforeUnlock); // endIndex doesn't change on unlock - - // Step 5: Try to lock one more user - this should trigger overflow check and gap removal - // After gap removal, the lock should succeed because we created gaps earlier - id finalUser(targetEndIndex + 1, 100, 200, 300); - uint64 finalAmount = QEARN_MINIMUM_LOCKING_AMOUNT + 1; - increaseEnergy(finalUser, finalAmount); - - // The lock should succeed after gap removal - sint32 retCode = qearn.lock(finalUser, finalAmount); - - // Verify that gap removal happened and lock succeeded - // After gap removal, endIndex should be less than QEARN_MAX_LOCKS - 1 - uint32 endIndexAfterGapRemoval = state->getEpochIndex(system.epoch).endIndex; - - // The lock should succeed because gaps were removed - EXPECT_EQ(retCode, QEARN_LOCK_SUCCESS); - EXPECT_EQ(endIndexAfterGapRemoval, QEARN_MAX_LOCKS - numGapsToCreate); - EXPECT_LT(endIndexAfterGapRemoval, QEARN_MAX_LOCKS - 1); - EXPECT_LT(endIndexAfterGapRemoval, endIndexAfterUnlock); // endIndex should decrease after gap removal - - // Verify the locker array is consistent after gap removal - qearn.getState()->checkLockerArray(true, false); - - // Verify the final user's lock was successful - EXPECT_EQ(qearn.getUserLockedInfo(system.epoch, finalUser), finalAmount); - - qearn.endEpoch(); -} -#endif - -void testRandomLockWithoutUnlock(const uint16 numEpochs, const unsigned int totalUsers, const unsigned int maxUserLocking) -{ - std::cout << "random test without early unlock for " << numEpochs << " epochs with " << totalUsers << " total users and up to " << maxUserLocking << " lock calls per epoch" << std::endl; - ContractTestingQearn qearn; - - const uint16 firstEpoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; - const uint16 lastEpoch = firstEpoch + numEpochs; - - // first epoch is without donation/bonus - for (system.epoch = firstEpoch; system.epoch <= lastEpoch; ++system.epoch) - { - // invoke BEGIN_EPOCH - qearn.beginEpoch(); - - // simulate a random additional donation during the epoch - qearn.simulateDonation(random(ISSUANCE_RATE / 2)); - - // locking - auto lockUsers = getRandomUsers(totalUsers, maxUserLocking); - for (const auto& user : lockUsers) - { - // get random amount for locking and make sure that user has enough qus (may be invalid amount for locking) - uint64 amountLock = random(QEARN_MAX_LOCK_AMOUNT * 4 / 3); - increaseEnergy(user, amountLock); - - qearn.lockAndCheck(user, amountLock); - } - - // invoke END_EPOCH and check correct payouts - qearn.endEpochAndCheck(); - - // send revenue donation to qearn contract (happens after END_EPOCH but before system.epoch is incremented and before BEGIN_EPOCH - qearn.simulateDonation(random(ISSUANCE_RATE)); - } -} - -TEST(TestContractQearn, RandomLockWithoutUnlock) -{ - // params: epochs, total number of users, max users locking in epoch - testRandomLockWithoutUnlock(100, 40, 10); - testRandomLockWithoutUnlock(100, 20, 20); -#if LARGE_SCALE_TEST >= 1 - testRandomLockWithoutUnlock(300, 1000, 1000); -#endif -#if LARGE_SCALE_TEST >= 2 - testRandomLockWithoutUnlock(100, 20000, 10000); -#endif -} - -void testRandomLockWithUnlock(const uint16 numEpochs, const unsigned int totalUsers, const unsigned int maxUserLocking, const unsigned int maxUserUnlocking) -{ - std::cout << "random test with early unlock for " << numEpochs << " epochs with " << totalUsers << " total users, up to " << maxUserLocking << " lock calls (per epoch), and up to " << maxUserUnlocking << " unlock calls (per running round)" << std::endl; - ContractTestingQearn qearn; - - const uint16 firstEpoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; - const uint16 lastEpoch = firstEpoch + numEpochs; - - for (system.epoch = firstEpoch; system.epoch <= lastEpoch; ++system.epoch) - { - // invoke BEGIN_EPOCH - qearn.beginEpoch(); - - // simulate a random additional donation during the epoch - qearn.simulateDonation(random(ISSUANCE_RATE / 2)); - - // locking - auto lockUsers = getRandomUsers(totalUsers, maxUserLocking); - for (const auto& user : lockUsers) - { - // get random amount for locking and make sure that user has enough qus (may be invalid amount for locking) - uint64 amountLock = random(QEARN_MAX_LOCK_AMOUNT * 4 / 3); - increaseEnergy(user, amountLock); - - qearn.lockAndCheck(user, amountLock); - } - - // unlocking - auto unlockUsers = getRandomUsers(totalUsers, maxUserUnlocking); - for (const auto& user : unlockUsers) - { - for (sint32 lockedEpoch = system.epoch; lockedEpoch >= system.epoch - 52; lockedEpoch--) - { - uint64 amountUnlock = random(qearn.allUserData[user].locked[lockedEpoch] * 11 / 10); - qearn.unlockAndCheck(user, lockedEpoch, amountUnlock); - } - } - - // invoke END_EPOCH and check correct payouts - qearn.endEpochAndCheck(); - - // send revenue donation to qearn contract (happens after END_EPOCH but before system.epoch is incremented and before BEGIN_EPOCH - qearn.simulateDonation(random(ISSUANCE_RATE)); - } -} - -TEST(TestContractQearn, RandomLockAndUnlock) -{ - // params: epochs, total number of users, max users locking in epoch, maxUserUnlocking - testRandomLockWithUnlock(100, 40, 10, 10); - testRandomLockWithUnlock(100, 40, 10, 8); // less early unlocking - testRandomLockWithUnlock(100, 40, 20, 19); // more user activity -#if LARGE_SCALE_TEST >= 1 - testRandomLockWithUnlock(300, 1000, 1000, 1000); - testRandomLockWithUnlock(300, 1000, 1000, 800); -#endif -#if LARGE_SCALE_TEST >= 2 - testRandomLockWithUnlock(400, 2000, 1500, 1200); - testRandomLockWithUnlock(100, 20000, 10000, 8000); -#endif -} diff --git a/test/contract_qip.cpp b/test/contract_qip.cpp deleted file mode 100644 index 034fd9b1e..000000000 --- a/test/contract_qip.cpp +++ /dev/null @@ -1,1444 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -static constexpr uint64 QIP_ISSUE_ASSET_FEE = 1000000000ull; -static constexpr uint64 QIP_TRANSFER_ASSET_FEE = 100ull; -static constexpr uint64 QIP_TRANSFER_RIGHTS_FEE = 100ull; - -static const id QIP_CONTRACT_ID(QIP_CONTRACT_INDEX, 0, 0, 0); - -const id QIP_testIssuer = ID(_T, _E, _S, _T, _I, _S, _S, _U, _E, _R, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T); -const id QIP_testAddress1 = ID(_A, _D, _D, _R, _A, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); -const id QIP_testAddress2 = ID(_A, _D, _D, _R, _B, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); -const id QIP_testAddress3 = ID(_A, _D, _D, _R, _C, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); -const id QIP_testBuyer = ID(_B, _U, _Y, _E, _R, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); - -class QIPChecker : public QIP -{ -public: - uint32 getNumberOfICO() const { return numberOfICO; } - - void checkICOInfo(const QIP::getICOInfo_output& output, const QIP::createICO_input& input, const id& creator) - { - EXPECT_EQ(output.creatorOfICO, creator); - EXPECT_EQ(output.issuer, input.issuer); - EXPECT_EQ(output.address1, input.address1); - EXPECT_EQ(output.address2, input.address2); - EXPECT_EQ(output.address3, input.address3); - EXPECT_EQ(output.address4, input.address4); - EXPECT_EQ(output.address5, input.address5); - EXPECT_EQ(output.address6, input.address6); - EXPECT_EQ(output.address7, input.address7); - EXPECT_EQ(output.address8, input.address8); - EXPECT_EQ(output.address9, input.address9); - EXPECT_EQ(output.address10, input.address10); - EXPECT_EQ(output.assetName, input.assetName); - EXPECT_EQ(output.price1, input.price1); - EXPECT_EQ(output.price2, input.price2); - EXPECT_EQ(output.price3, input.price3); - EXPECT_EQ(output.saleAmountForPhase1, input.saleAmountForPhase1); - EXPECT_EQ(output.saleAmountForPhase2, input.saleAmountForPhase2); - EXPECT_EQ(output.saleAmountForPhase3, input.saleAmountForPhase3); - EXPECT_EQ(output.remainingAmountForPhase1, input.saleAmountForPhase1); - EXPECT_EQ(output.remainingAmountForPhase2, input.saleAmountForPhase2); - EXPECT_EQ(output.remainingAmountForPhase3, input.saleAmountForPhase3); - EXPECT_EQ(output.percent1, input.percent1); - EXPECT_EQ(output.percent2, input.percent2); - EXPECT_EQ(output.percent3, input.percent3); - EXPECT_EQ(output.percent4, input.percent4); - EXPECT_EQ(output.percent5, input.percent5); - EXPECT_EQ(output.percent6, input.percent6); - EXPECT_EQ(output.percent7, input.percent7); - EXPECT_EQ(output.percent8, input.percent8); - EXPECT_EQ(output.percent9, input.percent9); - EXPECT_EQ(output.percent10, input.percent10); - EXPECT_EQ(output.startEpoch, input.startEpoch); - } -}; - -class ContractTestingQIP : protected ContractTesting -{ -public: - ContractTestingQIP() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QIP); - callSystemProcedure(QIP_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - QIPChecker* getState() - { - return (QIPChecker*)contractStates[QIP_CONTRACT_INDEX]; - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QIP_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares) - { - QX::IssueAsset_input input; - input.assetName = assetName; - input.numberOfShares = numberOfShares; - input.unitOfMeasurement = 0; - input.numberOfDecimalPlaces = 0; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QIP_ISSUE_ASSET_FEE); - return output.issuedNumberOfShares; - } - - sint64 transferAsset(const id& from, const id& to, uint64 assetName, const id& issuer, sint64 numberOfShares) - { - QX::TransferShareOwnershipAndPossession_input input; - input.assetName = assetName; - input.issuer = issuer; - input.newOwnerAndPossessor = to; - input.numberOfShares = numberOfShares; - QX::TransferShareOwnershipAndPossession_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, from, QIP_TRANSFER_ASSET_FEE); - return output.transferredNumberOfShares; - } - - QIP::createICO_output createICO(const id& creator, const QIP::createICO_input& input) - { - QIP::createICO_output output; - invokeUserProcedure(QIP_CONTRACT_INDEX, 1, input, output, creator, 0); - return output; - } - - QIP::buyToken_output buyToken(const id& buyer, uint32 indexOfICO, uint64 amount, sint64 invocationReward) - { - QIP::buyToken_input input; - input.indexOfICO = indexOfICO; - input.amount = amount; - QIP::buyToken_output output; - invokeUserProcedure(QIP_CONTRACT_INDEX, 2, input, output, buyer, invocationReward); - return output; - } - - QIP::getICOInfo_output getICOInfo(uint32 indexOfICO) - { - QIP::getICOInfo_input input; - input.indexOfICO = indexOfICO; - QIP::getICOInfo_output output; - callFunction(QIP_CONTRACT_INDEX, 1, input, output); - return output; - } - - sint64 transferShareManagementRightsQX(const id& invocator, const Asset& asset, sint64 numberOfShares, uint32 newManagingContractIndex, sint64 fee) - { - QX::TransferShareManagementRights_input input; - input.asset = asset; - input.numberOfShares = numberOfShares; - input.newManagingContractIndex = newManagingContractIndex; - QX::TransferShareManagementRights_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, invocator, fee); - return output.transferredNumberOfShares; - } - - sint64 transferShareManagementRights(const id& invocator, const Asset& asset, sint64 numberOfShares, uint32 newManagingContractIndex, sint64 invocationReward) - { - QIP::TransferShareManagementRights_input input; - input.asset = asset; - input.numberOfShares = numberOfShares; - input.newManagingContractIndex = newManagingContractIndex; - QIP::TransferShareManagementRights_output output; - invokeUserProcedure(QIP_CONTRACT_INDEX, 3, input, output, invocator, invocationReward); - return output.transferredNumberOfShares; - } -}; - -TEST(ContractQIP, createICO_Success) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - // Issue asset and transfer to creator - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - // Prepare ICO input - QIP::createICO_input input; - input.issuer = issuer; - input.address1 = QIP_testAddress1; - input.address2 = QIP_testAddress2; - input.address3 = QIP_testAddress3; - input.address4 = QIP_testAddress1; - input.address5 = QIP_testAddress2; - input.address6 = QIP_testAddress3; - input.address7 = QIP_testAddress1; - input.address8 = QIP_testAddress2; - input.address9 = QIP_testAddress3; - input.address10 = QIP_testAddress1; - input.assetName = assetName; - input.price1 = 100; - input.price2 = 200; - input.price3 = 300; - input.saleAmountForPhase1 = 300000; - input.saleAmountForPhase2 = 300000; - input.saleAmountForPhase3 = 400000; - input.percent1 = 10; - input.percent2 = 10; - input.percent3 = 10; - input.percent4 = 10; - input.percent5 = 10; - input.percent6 = 10; - input.percent7 = 10; - input.percent8 = 10; - input.percent9 = 10; - input.percent10 = 5; - input.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output output = QIP.createICO(creator, input); - EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_success); - - // Check ICO info - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - QIP.getState()->checkICOInfo(icoInfo, input, creator); - - // Verify shares were transferred to contract - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, creator, creator, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 0); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares); -} - -TEST(ContractQIP, createICO_InvalidStartEpoch) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input input; - input.issuer = issuer; - input.address1 = QIP_testAddress1; - input.address2 = QIP_testAddress2; - input.address3 = QIP_testAddress3; - input.address4 = QIP_testAddress1; - input.address5 = QIP_testAddress2; - input.address6 = QIP_testAddress3; - input.address7 = QIP_testAddress1; - input.address8 = QIP_testAddress2; - input.address9 = QIP_testAddress3; - input.address10 = QIP_testAddress1; - input.assetName = assetName; - input.price1 = 100; - input.price2 = 200; - input.price3 = 300; - input.saleAmountForPhase1 = 300000; - input.saleAmountForPhase2 = 300000; - input.saleAmountForPhase3 = 400000; - input.percent1 = 10; - input.percent2 = 10; - input.percent3 = 10; - input.percent4 = 10; - input.percent5 = 10; - input.percent6 = 10; - input.percent7 = 10; - input.percent8 = 10; - input.percent9 = 10; - input.percent10 = 5; - - // Test with startEpoch <= current epoch + 1 - input.startEpoch = system.epoch; - increaseEnergy(creator, 1); - QIP::createICO_output output1 = QIP.createICO(creator, input); - EXPECT_EQ(output1.returnCode, QIPLogInfo::QIP_invalidStartEpoch); - - input.startEpoch = system.epoch + 1; - QIP::createICO_output output2 = QIP.createICO(creator, input); - EXPECT_EQ(output2.returnCode, QIPLogInfo::QIP_invalidStartEpoch); -} - -TEST(ContractQIP, createICO_InvalidSaleAmount) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input input; - input.issuer = issuer; - input.address1 = QIP_testAddress1; - input.address2 = QIP_testAddress2; - input.address3 = QIP_testAddress3; - input.address4 = QIP_testAddress1; - input.address5 = QIP_testAddress2; - input.address6 = QIP_testAddress3; - input.address7 = QIP_testAddress1; - input.address8 = QIP_testAddress2; - input.address9 = QIP_testAddress3; - input.address10 = QIP_testAddress1; - input.assetName = assetName; - input.price1 = 100; - input.price2 = 200; - input.price3 = 300; - input.saleAmountForPhase1 = 300000; - input.saleAmountForPhase2 = 300000; - input.saleAmountForPhase3 = 400001; // Total doesn't match - input.percent1 = 10; - input.percent2 = 10; - input.percent3 = 10; - input.percent4 = 10; - input.percent5 = 10; - input.percent6 = 10; - input.percent7 = 10; - input.percent8 = 10; - input.percent9 = 10; - input.percent10 = 5; - input.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output output = QIP.createICO(creator, input); - EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_invalidSaleAmount); -} - -TEST(ContractQIP, createICO_InvalidPrice) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input input; - input.issuer = issuer; - input.address1 = QIP_testAddress1; - input.address2 = QIP_testAddress2; - input.address3 = QIP_testAddress3; - input.address4 = QIP_testAddress1; - input.address5 = QIP_testAddress2; - input.address6 = QIP_testAddress3; - input.address7 = QIP_testAddress1; - input.address8 = QIP_testAddress2; - input.address9 = QIP_testAddress3; - input.address10 = QIP_testAddress1; - input.assetName = assetName; - input.price1 = 0; // Invalid price - input.price2 = 200; - input.price3 = 300; - input.saleAmountForPhase1 = 300000; - input.saleAmountForPhase2 = 300000; - input.saleAmountForPhase3 = 400000; - input.percent1 = 10; - input.percent2 = 10; - input.percent3 = 10; - input.percent4 = 10; - input.percent5 = 10; - input.percent6 = 10; - input.percent7 = 10; - input.percent8 = 10; - input.percent9 = 10; - input.percent10 = 5; - input.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output output = QIP.createICO(creator, input); - EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_invalidPrice); -} - -TEST(ContractQIP, createICO_InvalidPercent) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input input; - input.issuer = issuer; - input.address1 = QIP_testAddress1; - input.address2 = QIP_testAddress2; - input.address3 = QIP_testAddress3; - input.address4 = QIP_testAddress1; - input.address5 = QIP_testAddress2; - input.address6 = QIP_testAddress3; - input.address7 = QIP_testAddress1; - input.address8 = QIP_testAddress2; - input.address9 = QIP_testAddress3; - input.address10 = QIP_testAddress1; - input.assetName = assetName; - input.price1 = 100; - input.price2 = 200; - input.price3 = 300; - input.saleAmountForPhase1 = 300000; - input.saleAmountForPhase2 = 300000; - input.saleAmountForPhase3 = 400000; - input.percent1 = 10; - input.percent2 = 10; - input.percent3 = 10; - input.percent4 = 10; - input.percent5 = 10; - input.percent6 = 10; - input.percent7 = 10; - input.percent8 = 10; - input.percent9 = 5; - input.percent10 = 1; // Total is 96, should be 95 - input.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output output = QIP.createICO(creator, input); - EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_invalidPercent); -} - -TEST(ContractQIP, buyToken_Phase1) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Advance to start epoch - ++system.epoch; - ++system.epoch; - - id buyer = QIP_testBuyer; - uint64 buyAmount = 10000; - uint64 price = createInput.price1; - sint64 requiredReward = buyAmount * price; - - increaseEnergy(buyer, requiredReward); - increaseEnergy(QIP_testAddress1, 1); - increaseEnergy(QIP_testAddress2, 1); - increaseEnergy(QIP_testAddress3, 1); - - // Record balances before purchase for all addresses - sint64 balanceBefore1 = getBalance(QIP_testAddress1); - sint64 balanceBefore2 = getBalance(QIP_testAddress2); - sint64 balanceBefore3 = getBalance(QIP_testAddress3); - sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); - - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); - - // Verify buyer received the shares - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); - - // Check remaining amounts - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - EXPECT_EQ(icoInfo.remainingAmountForPhase1, createInput.saleAmountForPhase1 - buyAmount); - EXPECT_EQ(icoInfo.remainingAmountForPhase2, createInput.saleAmountForPhase2); - EXPECT_EQ(icoInfo.remainingAmountForPhase3, createInput.saleAmountForPhase3); - - // Calculate expected distributions for all 10 addresses - sint64 totalPayment = buyAmount * price; - uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); - uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); - uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); - uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); - uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); - uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); - uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); - uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); - uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); - uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); - - // Calculate total distributed to addresses (should be 95% of total payment) - sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + - expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; - - // Calculate expected dividend amount (remaining 5% divided by 676) - sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; - uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; - - // Verify all addresses received correct amounts - // Note: addresses 1, 4, 7, 10 map to QIP_testAddress1 - // addresses 2, 5, 8 map to QIP_testAddress2 - // addresses 3, 6, 9 map to QIP_testAddress3 - sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; - sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; - sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; - - EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); - EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); - EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); - - // Verify contract balance decreased by total payment (minus any refund to buyer) - sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); - sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; - // Contract should have received the payment and distributed it, so balance should increase by fee minus distributions - // But since we're transferring from contract to addresses, the contract balance should decrease - // Actually, the contract receives the invocation reward, then transfers to addresses - // So the contract balance should be: initial + requiredReward - totalDistributedToAddresses - expectedDividendAmount - sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; - EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); -} - -TEST(ContractQIP, buyToken_Phase2) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Advance to Phase 2 (startEpoch + 1) - ++system.epoch; - ++system.epoch; - ++system.epoch; // Now at startEpoch + 1 - - id buyer = QIP_testBuyer; - uint64 buyAmount = 10000; - uint64 price = createInput.price2; - sint64 requiredReward = buyAmount * price; - - increaseEnergy(buyer, requiredReward); - increaseEnergy(QIP_testAddress1, 1); - increaseEnergy(QIP_testAddress2, 1); - increaseEnergy(QIP_testAddress3, 1); - - // Record balances before purchase - sint64 balanceBefore1 = getBalance(QIP_testAddress1); - sint64 balanceBefore2 = getBalance(QIP_testAddress2); - sint64 balanceBefore3 = getBalance(QIP_testAddress3); - sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); - - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); - - // Verify buyer received the shares - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); - - // Check remaining amounts - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - EXPECT_EQ(icoInfo.remainingAmountForPhase1, createInput.saleAmountForPhase1); - EXPECT_EQ(icoInfo.remainingAmountForPhase2, createInput.saleAmountForPhase2 - buyAmount); - EXPECT_EQ(icoInfo.remainingAmountForPhase3, createInput.saleAmountForPhase3); - - // Verify fee distribution for all addresses - sint64 totalPayment = buyAmount * price; - uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); - uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); - uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); - uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); - uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); - uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); - uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); - uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); - uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); - uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); - - sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + - expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; - sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; - uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; - - sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; - sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; - sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; - - EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); - EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); - EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); - - sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); - sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; - sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; - EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); -} - -TEST(ContractQIP, buyToken_Phase3) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Advance to Phase 3 (startEpoch + 2) - ++system.epoch; - ++system.epoch; - ++system.epoch; // Now at startEpoch + 1 - ++system.epoch; // Now at startEpoch + 2 - - id buyer = QIP_testBuyer; - uint64 buyAmount = 10000; - uint64 price = createInput.price3; - sint64 requiredReward = buyAmount * price; - - increaseEnergy(buyer, requiredReward); - increaseEnergy(QIP_testAddress1, 1); - increaseEnergy(QIP_testAddress2, 1); - increaseEnergy(QIP_testAddress3, 1); - - // Record balances before purchase - sint64 balanceBefore1 = getBalance(QIP_testAddress1); - sint64 balanceBefore2 = getBalance(QIP_testAddress2); - sint64 balanceBefore3 = getBalance(QIP_testAddress3); - sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); - - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); - - // Verify buyer received the shares - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); - - // Check remaining amounts - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - EXPECT_EQ(icoInfo.remainingAmountForPhase1, createInput.saleAmountForPhase1); - EXPECT_EQ(icoInfo.remainingAmountForPhase2, createInput.saleAmountForPhase2); - EXPECT_EQ(icoInfo.remainingAmountForPhase3, createInput.saleAmountForPhase3 - buyAmount); - - // Verify fee distribution for all addresses - sint64 totalPayment = buyAmount * price; - uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); - uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); - uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); - uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); - uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); - uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); - uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); - uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); - uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); - uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); - - sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + - expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; - sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; - uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; - - sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; - sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; - sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; - - EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); - EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); - EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); - - sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); - sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; - sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; - EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); -} - -TEST(ContractQIP, buyToken_InvalidEpoch) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Try to buy before start epoch - id buyer = QIP_testBuyer; - uint64 buyAmount = 10000; - uint64 price = createInput.price1; - sint64 requiredReward = buyAmount * price; - - increaseEnergy(buyer, requiredReward); - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_invalidEpoch); -} - -TEST(ContractQIP, buyToken_InvalidAmount) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Advance to start epoch - ++system.epoch; - ++system.epoch; - - id buyer = QIP_testBuyer; - uint64 buyAmount = 300001; // More than remaining - uint64 price = createInput.price1; - sint64 requiredReward = buyAmount * price; - - increaseEnergy(buyer, requiredReward); - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_invalidAmount); -} - -TEST(ContractQIP, buyToken_ICONotFound) -{ - ContractTestingQIP QIP; - - id buyer = QIP_testBuyer; - uint64 buyAmount = 10000; - sint64 requiredReward = buyAmount * 100; - - increaseEnergy(buyer, requiredReward); - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 999, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_ICONotFound); -} - -TEST(ContractQIP, buyToken_InsufficientInvocationReward) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Advance to start epoch - ++system.epoch; - ++system.epoch; - - id buyer = QIP_testBuyer; - uint64 buyAmount = 10000; - uint64 price = createInput.price1; - sint64 requiredReward = buyAmount * price; - sint64 insufficientReward = requiredReward - 1; - - increaseEnergy(buyer, insufficientReward); - QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, insufficientReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_insufficientInvocationReward); -} - -TEST(ContractQIP, END_EPOCH_Phase1Rollover) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Check initial state - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - uint64 initialPhase1 = icoInfo.remainingAmountForPhase1; - uint64 initialPhase2 = icoInfo.remainingAmountForPhase2; - - // Advance to startEpoch (Phase 1 ends) - ++system.epoch; // epoch = startEpoch - 1 - ++system.epoch; // epoch = startEpoch - - // End epoch should rollover Phase 1 remaining to Phase 2 - QIP.endEpoch(); - - // Check that Phase 1 remaining was set to 0 - icoInfo = QIP.getICOInfo(0); - EXPECT_EQ(icoInfo.remainingAmountForPhase1, 0); - EXPECT_EQ(icoInfo.remainingAmountForPhase2, initialPhase2 + initialPhase1); -} - -TEST(ContractQIP, END_EPOCH_Phase2Rollover) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Check initial state - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - uint64 initialPhase2 = icoInfo.remainingAmountForPhase2; - uint64 initialPhase3 = icoInfo.remainingAmountForPhase3; - - // Advance to startEpoch + 1 (Phase 2 ends) - ++system.epoch; // epoch = startEpoch - 1 - ++system.epoch; // epoch = startEpoch - ++system.epoch; // epoch = startEpoch + 1 - - // End epoch should rollover Phase 2 remaining to Phase 3 - QIP.endEpoch(); - - // Check that Phase 2 remaining was set to 0 - icoInfo = QIP.getICOInfo(0); - EXPECT_EQ(icoInfo.remainingAmountForPhase2, 0); - EXPECT_EQ(icoInfo.remainingAmountForPhase3, initialPhase3 + initialPhase2); -} - -TEST(ContractQIP, END_EPOCH_Phase3ReturnToCreator) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Check initial state - verify shares are in contract - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares); - QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); - uint64 remainingPhase3 = icoInfo.remainingAmountForPhase3; - sint64 creatorSharesBefore = numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX); - - // Advance to startEpoch + 2 (Phase 3 ends) - ++system.epoch; // epoch = startEpoch - 1 - ++system.epoch; // epoch = startEpoch - ++system.epoch; // epoch = startEpoch + 1 - ++system.epoch; // epoch = startEpoch + 2 - - // End epoch should return Phase 3 remaining to creator and remove ICO - QIP.endEpoch(); - - // Verify shares were returned to creator - sint64 creatorSharesAfter = numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX); - EXPECT_EQ(creatorSharesAfter, creatorSharesBefore + remainingPhase3); - - // Verify ICO was removed - QIPChecker* state = QIP.getState(); - EXPECT_EQ(state->getNumberOfICO(), 0); - - // Verify contract no longer has the returned shares - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares - remainingPhase3); -} - -TEST(ContractQIP, TransferShareManagementRights) -{ - ContractTestingQIP QIP; - - id issuer = QIP_testIssuer; - uint64 assetName = assetNameFromString("ICOASS"); - sint64 totalShares = 1000000; - - increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); - EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); - - id creator = QIP_testBuyer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); - - // Transfer management rights to QIP contract - Asset asset; - asset.assetName = assetName; - asset.issuer = issuer; - increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); - EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); - - // Transfer shares to QIP contract - QIP::createICO_input createInput; - createInput.issuer = issuer; - createInput.address1 = QIP_testAddress1; - createInput.address2 = QIP_testAddress2; - createInput.address3 = QIP_testAddress3; - createInput.address4 = QIP_testAddress1; - createInput.address5 = QIP_testAddress2; - createInput.address6 = QIP_testAddress3; - createInput.address7 = QIP_testAddress1; - createInput.address8 = QIP_testAddress2; - createInput.address9 = QIP_testAddress3; - createInput.address10 = QIP_testAddress1; - createInput.assetName = assetName; - createInput.price1 = 100; - createInput.price2 = 200; - createInput.price3 = 300; - createInput.saleAmountForPhase1 = 300000; - createInput.saleAmountForPhase2 = 300000; - createInput.saleAmountForPhase3 = 400000; - createInput.percent1 = 10; - createInput.percent2 = 10; - createInput.percent3 = 10; - createInput.percent4 = 10; - createInput.percent5 = 10; - createInput.percent6 = 10; - createInput.percent7 = 10; - createInput.percent8 = 10; - createInput.percent9 = 10; - createInput.percent10 = 5; - createInput.startEpoch = system.epoch + 2; - - increaseEnergy(creator, 1); - QIP::createICO_output createOutput = QIP.createICO(creator, createInput); - EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); - - // Verify shares are in QIP contract - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares); - - system.epoch += 2; - // buy token - uint64 buyAmount = 100000; - uint64 price = createInput.price1; - sint64 requiredReward = buyAmount * price; - - increaseEnergy(creator, requiredReward); - increaseEnergy(QIP_testAddress1, 1); - increaseEnergy(QIP_testAddress2, 1); - increaseEnergy(QIP_testAddress3, 1); - - // Record balances before purchase - sint64 balanceBefore1 = getBalance(QIP_testAddress1); - sint64 balanceBefore2 = getBalance(QIP_testAddress2); - sint64 balanceBefore3 = getBalance(QIP_testAddress3); - sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); - - QIP::buyToken_output buyOutput = QIP.buyToken(creator, 0, buyAmount, requiredReward); - EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); - - // Verify fee distribution for all addresses - sint64 totalPayment = buyAmount * price; - uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); - uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); - uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); - uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); - uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); - uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); - uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); - uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); - uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); - uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); - - sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + - expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; - sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; - uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; - - sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; - sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; - sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; - - EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); - EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); - EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); - - sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); - sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; - sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; - EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); - - // Transfer management rights - sint64 transferAmount = 100000; - - increaseEnergy(creator, QIP_TRANSFER_RIGHTS_FEE * 2); - QIP.endEpoch(); - system.epoch += 1; - QIP.endEpoch(); - system.epoch += 1; - sint64 transferred = QIP.transferShareManagementRights(creator, asset, transferAmount, QX_CONTRACT_INDEX, QIP_TRANSFER_RIGHTS_FEE); - EXPECT_EQ(transferred, 0); - QIP.endEpoch(); - transferred = QIP.transferShareManagementRights(creator, asset, transferAmount, QX_CONTRACT_INDEX, QIP_TRANSFER_RIGHTS_FEE); - EXPECT_EQ(transferred, transferAmount); - - // Verify shares were transferred - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), 0); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares - transferAmount); -} - diff --git a/test/contract_qraffle.cpp b/test/contract_qraffle.cpp deleted file mode 100644 index afbf3e503..000000000 --- a/test/contract_qraffle.cpp +++ /dev/null @@ -1,1576 +0,0 @@ -#define NO_UEFI - -#include -#include - -#include "contract_testing.h" - -static std::mt19937_64 rand64; - -static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) -{ - if(minValue > maxValue) - { - return 0; - } - return minValue + rand64() % (maxValue - minValue); -} - -static id getUser(unsigned long long i) -{ - return id(i, i / 2 + 4, i + 10, i * 3 + 8); -} - -static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) -{ - std::map userMap; - unsigned long long userCount = random(0, maxNum); - std::vector users; - users.reserve(userCount); - for (unsigned int i = 0; i < userCount; ++i) - { - unsigned long long userIdx = random(0, totalUsers - 1); - id user = getUser(userIdx); - if (userMap.contains(user)) - { - continue; - } - userMap[user] = true; - users.push_back(user); - } - return users; -} - -class QRaffleChecker : public QRAFFLE -{ -public: - void registerChecker(const id& user, uint32 expectedRegisters, bool isRegistered) - { - if (isRegistered) - { - EXPECT_EQ(registers.contains(user), 1); - } - else - { - EXPECT_EQ(registers.contains(user), 0); - } - EXPECT_EQ(numberOfRegisters, expectedRegisters); - } - - void unregisterChecker(const id& user, uint32 expectedRegisters) - { - EXPECT_EQ(registers.contains(user), 0); - EXPECT_EQ(numberOfRegisters, expectedRegisters); - } - - void entryAmountChecker(const id& user, uint64 expectedAmount, uint32 expectedSubmitted) - { - uint64 amount = 0; - if (quRaffleEntryAmount.contains(user)) - { - quRaffleEntryAmount.get(user, amount); - EXPECT_EQ(amount, expectedAmount); - } - EXPECT_EQ(numberOfEntryAmountSubmitted, expectedSubmitted); - } - - void proposalChecker(uint32 index, const Asset& expectedToken, uint64 expectedEntryAmount) - { - EXPECT_EQ(proposals.get(index).token.assetName, expectedToken.assetName); - EXPECT_EQ(proposals.get(index).token.issuer, expectedToken.issuer); - EXPECT_EQ(proposals.get(index).entryAmount, expectedEntryAmount); - EXPECT_EQ(numberOfProposals, index + 1); - } - - void voteChecker(uint32 proposalIndex, uint32 expectedYes, uint32 expectedNo) - { - EXPECT_EQ(proposals.get(proposalIndex).nYes, expectedYes); - EXPECT_EQ(proposals.get(proposalIndex).nNo, expectedNo); - } - - void quRaffleMemberChecker(const id& user, uint32 expectedMembers) - { - bool found = false; - for (uint32 i = 0; i < numberOfQuRaffleMembers; i++) - { - if (quRaffleMembers.get(i) == user) - { - found = true; - break; - } - } - EXPECT_EQ(found, 1); - EXPECT_EQ(numberOfQuRaffleMembers, expectedMembers); - } - - void tokenRaffleMemberChecker(uint32 raffleIndex, const id& user, uint32 expectedMembers) - { - tokenRaffleMembers.get(raffleIndex, tmpTokenRaffleMembers); - bool found = false; - for (uint32 i = 0; i < numberOfTokenRaffleMembers.get(raffleIndex); i++) - { - if (tmpTokenRaffleMembers.get(i) == user) - { - found = true; - break; - } - } - EXPECT_EQ(found, 1); - EXPECT_EQ(numberOfTokenRaffleMembers.get(raffleIndex), expectedMembers); - } - - void analyticsChecker(uint64 expectedBurn, uint64 expectedCharity, uint64 expectedShareholder, - uint64 expectedRegister, uint64 expectedFee, uint64 expectedWinner, - uint64 expectedLargestWinner, uint32 expectedRegisters, uint32 expectedProposals, - uint32 expectedQuMembers, uint32 expectedActiveTokenRaffle, - uint32 expectedEndedTokenRaffle, uint32 expectedEntrySubmitted) - { - EXPECT_EQ(totalBurnAmount, expectedBurn); - EXPECT_EQ(totalCharityAmount, expectedCharity); - EXPECT_EQ(totalShareholderAmount, expectedShareholder); - EXPECT_EQ(totalRegisterAmount, expectedRegister); - EXPECT_EQ(totalFeeAmount, expectedFee); - EXPECT_EQ(totalWinnerAmount, expectedWinner); - EXPECT_EQ(largestWinnerAmount, expectedLargestWinner); - EXPECT_EQ(numberOfRegisters, expectedRegisters); - EXPECT_EQ(numberOfProposals, expectedProposals); - EXPECT_EQ(numberOfQuRaffleMembers, expectedQuMembers); - EXPECT_EQ(numberOfActiveTokenRaffle, expectedActiveTokenRaffle); - EXPECT_EQ(numberOfEndedTokenRaffle, expectedEndedTokenRaffle); - EXPECT_EQ(numberOfEntryAmountSubmitted, expectedEntrySubmitted); - } - - void activeTokenRaffleChecker(uint32 index, const Asset& expectedToken, uint64 expectedEntryAmount) - { - EXPECT_EQ(activeTokenRaffle.get(index).token.assetName, expectedToken.assetName); - EXPECT_EQ(activeTokenRaffle.get(index).token.issuer, expectedToken.issuer); - EXPECT_EQ(activeTokenRaffle.get(index).entryAmount, expectedEntryAmount); - } - - void endedTokenRaffleChecker(uint32 index, const id& expectedWinner, const Asset& expectedToken, - uint64 expectedEntryAmount, uint32 expectedMembers, uint32 expectedWinnerIndex, uint32 expectedEpoch) - { - EXPECT_EQ(tokenRaffle.get(index).epochWinner, expectedWinner); - EXPECT_EQ(tokenRaffle.get(index).token.assetName, expectedToken.assetName); - EXPECT_EQ(tokenRaffle.get(index).token.issuer, expectedToken.issuer); - EXPECT_EQ(tokenRaffle.get(index).entryAmount, expectedEntryAmount); - EXPECT_EQ(tokenRaffle.get(index).numberOfMembers, expectedMembers); - EXPECT_EQ(tokenRaffle.get(index).winnerIndex, expectedWinnerIndex); - EXPECT_EQ(tokenRaffle.get(index).epoch, expectedEpoch); - } - - void quRaffleWinnerChecker(uint16 epoch, const id& expectedWinner, uint64 expectedReceived, - uint64 expectedEntryAmount, uint32 expectedMembers, uint32 expectedWinnerIndex) - { - EXPECT_EQ(QuRaffles.get(epoch).epochWinner, expectedWinner); - EXPECT_EQ(QuRaffles.get(epoch).receivedAmount, expectedReceived); - EXPECT_EQ(QuRaffles.get(epoch).entryAmount, expectedEntryAmount); - EXPECT_EQ(QuRaffles.get(epoch).numberOfMembers, expectedMembers); - EXPECT_EQ(QuRaffles.get(epoch).winnerIndex, expectedWinnerIndex); - } - - uint64 getQuRaffleEntryAmount() - { - return qREAmount; - } - - uint32 getNumberOfActiveTokenRaffle() - { - return numberOfActiveTokenRaffle; - } - - uint32 getNumberOfEndedTokenRaffle() - { - return numberOfEndedTokenRaffle; - } - - uint64 getEpochQXMRRevenue() - { - return epochQXMRRevenue; - } - - uint32 getNumberOfRegisters() - { - return numberOfRegisters; - } - - id getQXMRIssuer() - { - return QXMRIssuer; - } -}; - -class ContractTestingQraffle : protected ContractTesting -{ -public: - ContractTestingQraffle() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QRAFFLE); - callSystemProcedure(QRAFFLE_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - QRaffleChecker* getState() - { - return (QRaffleChecker*)contractStates[QRAFFLE_CONTRACT_INDEX]; - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QRAFFLE_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - QRAFFLE::registerInSystem_output registerInSystem(const id& user, uint64 amount, bit useQXMR) - { - QRAFFLE::registerInSystem_input input; - QRAFFLE::registerInSystem_output output; - - input.useQXMR = useQXMR; - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 1, input, output, user, amount); - return output; - } - - QRAFFLE::logoutInSystem_output logoutInSystem(const id& user) - { - QRAFFLE::logoutInSystem_input input; - QRAFFLE::logoutInSystem_output output; - - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 2, input, output, user, 0); - return output; - } - - QRAFFLE::submitEntryAmount_output submitEntryAmount(const id& user, uint64 amount) - { - QRAFFLE::submitEntryAmount_input input; - QRAFFLE::submitEntryAmount_output output; - - input.amount = amount; - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 3, input, output, user, 0); - return output; - } - - QRAFFLE::submitProposal_output submitProposal(const id& user, const Asset& token, uint64 entryAmount) - { - QRAFFLE::submitProposal_input input; - QRAFFLE::submitProposal_output output; - - input.tokenIssuer = token.issuer; - input.tokenName = token.assetName; - input.entryAmount = entryAmount; - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 4, input, output, user, 0); - return output; - } - - QRAFFLE::voteInProposal_output voteInProposal(const id& user, uint32 proposalIndex, bit yes) - { - QRAFFLE::voteInProposal_input input; - QRAFFLE::voteInProposal_output output; - - input.indexOfProposal = proposalIndex; - input.yes = yes; - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 5, input, output, user, 0); - return output; - } - - QRAFFLE::depositInQuRaffle_output depositInQuRaffle(const id& user, uint64 amount) - { - QRAFFLE::depositInQuRaffle_input input; - QRAFFLE::depositInQuRaffle_output output; - - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 6, input, output, user, amount); - return output; - } - - QRAFFLE::depositInTokenRaffle_output depositInTokenRaffle(const id& user, uint32 raffleIndex, uint64 amount) - { - QRAFFLE::depositInTokenRaffle_input input; - QRAFFLE::depositInTokenRaffle_output output; - - input.indexOfTokenRaffle = raffleIndex; - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 7, input, output, user, amount); - return output; - } - - QRAFFLE::getRegisters_output getRegisters(uint32 offset, uint32 limit) - { - QRAFFLE::getRegisters_input input; - QRAFFLE::getRegisters_output output; - - input.offset = offset; - input.limit = limit; - callFunction(QRAFFLE_CONTRACT_INDEX, 1, input, output); - return output; - } - - QRAFFLE::getAnalytics_output getAnalytics() - { - QRAFFLE::getAnalytics_input input; - QRAFFLE::getAnalytics_output output; - - callFunction(QRAFFLE_CONTRACT_INDEX, 2, input, output); - return output; - } - - QRAFFLE::getActiveProposal_output getActiveProposal(uint32 proposalIndex) - { - QRAFFLE::getActiveProposal_input input; - QRAFFLE::getActiveProposal_output output; - - input.indexOfProposal = proposalIndex; - callFunction(QRAFFLE_CONTRACT_INDEX, 3, input, output); - return output; - } - - QRAFFLE::getEndedTokenRaffle_output getEndedTokenRaffle(uint32 raffleIndex) - { - QRAFFLE::getEndedTokenRaffle_input input; - QRAFFLE::getEndedTokenRaffle_output output; - - input.indexOfRaffle = raffleIndex; - callFunction(QRAFFLE_CONTRACT_INDEX, 4, input, output); - return output; - } - - QRAFFLE::getEndedQuRaffle_output getEndedQuRaffle(uint16 epoch) - { - QRAFFLE::getEndedQuRaffle_input input; - QRAFFLE::getEndedQuRaffle_output output; - - input.epoch = epoch; - callFunction(QRAFFLE_CONTRACT_INDEX, 5, input, output); - return output; - } - - QRAFFLE::getActiveTokenRaffle_output getActiveTokenRaffle(uint32 raffleIndex) - { - QRAFFLE::getActiveTokenRaffle_input input; - QRAFFLE::getActiveTokenRaffle_output output; - - input.indexOfTokenRaffle = raffleIndex; - callFunction(QRAFFLE_CONTRACT_INDEX, 6, input, output); - return output; - } - - QRAFFLE::getEpochRaffleIndexes_output getEpochRaffleIndexes(uint16 epoch) - { - QRAFFLE::getEpochRaffleIndexes_input input; - QRAFFLE::getEpochRaffleIndexes_output output; - - input.epoch = epoch; - callFunction(QRAFFLE_CONTRACT_INDEX, 7, input, output); - return output; - } - - QRAFFLE::getQuRaffleEntryAmountPerUser_output getQuRaffleEntryAmountPerUser(const id& user) - { - QRAFFLE::getQuRaffleEntryAmountPerUser_input input; - QRAFFLE::getQuRaffleEntryAmountPerUser_output output; - - input.user = user; - callFunction(QRAFFLE_CONTRACT_INDEX, 8, input, output); - return output; - } - - QRAFFLE::getQuRaffleEntryAverageAmount_output getQuRaffleEntryAverageAmount() - { - QRAFFLE::getQuRaffleEntryAverageAmount_input input; - QRAFFLE::getQuRaffleEntryAverageAmount_output output; - - callFunction(QRAFFLE_CONTRACT_INDEX, 9, input, output); - return output; - } - - sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) - { - QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, 1000000000ULL); - return output.issuedNumberOfShares; - } - - sint64 transferShareOwnershipAndPossession(const id& issuer, uint64 assetName, const id& currentOwnerAndPossesor, sint64 numberOfShares, const id& newOwnerAndPossesor) - { - QX::TransferShareOwnershipAndPossession_input input; - QX::TransferShareOwnershipAndPossession_output output; - - input.assetName = assetName; - input.issuer = issuer; - input.newOwnerAndPossessor = newOwnerAndPossesor; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, currentOwnerAndPossesor, 100); - return output.transferredNumberOfShares; - } - - sint64 TransferShareManagementRights(const id& issuer, uint64 assetName, uint32 newManagingContractIndex, sint64 numberOfShares, const id& currentOwner) - { - QX::TransferShareManagementRights_input input; - QX::TransferShareManagementRights_output output; - - input.asset.assetName = assetName; - input.asset.issuer = issuer; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, currentOwner, 0); - - return output.transferredNumberOfShares; - } - - sint64 TransferShareManagementRightsQraffle(const id& issuer, uint64 assetName, uint32 newManagingContractIndex, sint64 numberOfShares, const id& currentOwner) - { - QRAFFLE::TransferShareManagementRights_input input; - QRAFFLE::TransferShareManagementRights_output output; - - input.tokenName = assetName; - input.tokenIssuer = issuer; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 8, input, output, currentOwner, QRAFFLE_TRANSFER_SHARE_FEE); - return output.transferredNumberOfShares; - } -}; - -TEST(ContractQraffle, RegisterInSystem) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Test successful registration - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->registerChecker(user, ++registerCount, true); - } - - // // Test insufficient funds - id poorUser = getUser(9999); - increaseEnergy(poorUser, QRAFFLE_REGISTER_AMOUNT - 1); - auto result = qraffle.registerInSystem(poorUser, QRAFFLE_REGISTER_AMOUNT - 1, 0); - EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_FUND); - qraffle.getState()->registerChecker(poorUser, registerCount, false); - - // Test already registered - increaseEnergy(users[0], QRAFFLE_REGISTER_AMOUNT); - result = qraffle.registerInSystem(users[0], QRAFFLE_REGISTER_AMOUNT, 0); - EXPECT_EQ(result.returnCode, QRAFFLE_ALREADY_REGISTERED); - qraffle.getState()->registerChecker(users[0], registerCount, true); -} - -TEST(ContractQraffle, LogoutInSystem) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Test successful logout - for (const auto& user : users) - { - auto result = qraffle.logoutInSystem(user); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(getBalance(user), QRAFFLE_REGISTER_AMOUNT - QRAFFLE_LOGOUT_FEE); - qraffle.getState()->unregisterChecker(user, --registerCount); - } - - // Test unregistered user logout - qraffle.getState()->unregisterChecker(users[0], registerCount); - auto result = qraffle.logoutInSystem(users[0]); - EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); -} - -TEST(ContractQraffle, SubmitEntryAmount) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - uint32 entrySubmittedCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Test successful entry amount submission - for (const auto& user : users) - { - uint64 amount = random(1000000, 1000000000); - auto result = qraffle.submitEntryAmount(user, amount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->entryAmountChecker(user, amount, ++entrySubmittedCount); - } - - // Test unregistered user - id unregisteredUser = getUser(9999); - increaseEnergy(unregisteredUser, QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.submitEntryAmount(unregisteredUser, 1000000); - EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); - - // Test update entry amount - uint64 newAmount = random(1000000, 1000000000); - result = qraffle.submitEntryAmount(users[0], newAmount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->entryAmountChecker(users[0], newAmount, entrySubmittedCount); -} - -TEST(ContractQraffle, SubmitProposal) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - uint32 proposalCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Issue some test assets - id issuer = getUser(2000); - increaseEnergy(issuer, 1000000000ULL); - uint64 assetName1 = assetNameFromString("TEST1"); - uint64 assetName2 = assetNameFromString("TEST2"); - qraffle.issueAsset(issuer, assetName1, 1000000, 0, 0); - qraffle.issueAsset(issuer, assetName2, 2000000, 0, 0); - - Asset token1, token2; - token1.assetName = assetName1; - token1.issuer = issuer; - token2.assetName = assetName2; - token2.issuer = issuer; - - // Test successful proposal submission - for (const auto& user : users) - { - uint64 entryAmount = random(1000000, 1000000000); - Asset token = (random(0, 2) == 0) ? token1 : token2; - - if (proposalCount == QRAFFLE_MAX_PROPOSAL_EPOCH - 1) - { - break; - } - increaseEnergy(user, 1000); - auto result = qraffle.submitProposal(user, token, entryAmount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->proposalChecker(proposalCount, token, entryAmount); - proposalCount++; - } - - // Test unregistered user - id unregisteredUser = getUser(1999); - increaseEnergy(unregisteredUser, QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.submitProposal(unregisteredUser, token1, 1000000); - EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); -} - -TEST(ContractQraffle, VoteInProposal) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - uint32 proposalCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Create a proposal - id issuer = getUser(2000); - increaseEnergy(issuer, 1000000000ULL); - uint64 assetName = assetNameFromString("VOTETS"); - qraffle.issueAsset(issuer, assetName, 1000000, 0, 0); - - Asset token; - token.assetName = assetName; - token.issuer = issuer; - - qraffle.submitProposal(users[0], token, 1000000); - proposalCount++; - - uint32 yesVotes = 0, noVotes = 0; - - // Test voting - for (const auto& user : users) - { - bit vote = (bit)random(0, 2); - auto result = qraffle.voteInProposal(user, 0, vote); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - if (vote) - yesVotes++; - else - noVotes++; - - qraffle.getState()->voteChecker(0, yesVotes, noVotes); - } - - // Test duplicate vote (should change vote) - bit newVote = (bit)random(0, 2); - auto result = qraffle.voteInProposal(users[0], 0, newVote); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - if (newVote) - { - yesVotes++; - noVotes--; - } - else - { - noVotes++; - yesVotes--; - } - - qraffle.getState()->voteChecker(0, yesVotes, noVotes); - - // Test unregistered user - id unregisteredUser = getUser(9999); - increaseEnergy(unregisteredUser, 1000000000ULL); - result = qraffle.voteInProposal(unregisteredUser, 0, 1); - EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); - - // Test invalid proposal index - result = qraffle.voteInProposal(users[0], 9999, 1); - EXPECT_EQ(result.returnCode, QRAFFLE_INVALID_PROPOSAL); -} - -TEST(ContractQraffle, depositInQuRaffle) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - uint32 memberCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Test successful deposit - for (const auto& user : users) - { - increaseEnergy(user, qraffle.getState()->getQuRaffleEntryAmount()); - auto result = qraffle.depositInQuRaffle(user, qraffle.getState()->getQuRaffleEntryAmount()); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->quRaffleMemberChecker(user, ++memberCount); - } - - // Test insufficient funds - id poorUser = getUser(9999); - increaseEnergy(poorUser, qraffle.getState()->getQuRaffleEntryAmount() - 1); - auto result = qraffle.depositInQuRaffle(poorUser, qraffle.getState()->getQuRaffleEntryAmount() - 1); - EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_FUND); - - // Test already registered - increaseEnergy(users[0], qraffle.getState()->getQuRaffleEntryAmount()); - result = qraffle.depositInQuRaffle(users[0], qraffle.getState()->getQuRaffleEntryAmount()); - EXPECT_EQ(result.returnCode, QRAFFLE_ALREADY_REGISTERED); -} - -TEST(ContractQraffle, DepositInTokenRaffle) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Create a proposal and vote for it - id issuer = getUser(2000); - increaseEnergy(issuer, 2000000000ULL); - uint64 assetName = assetNameFromString("TOKENRF"); - qraffle.issueAsset(issuer, assetName, 1000000000000, 0, 0); - - Asset token; - token.assetName = assetName; - token.issuer = issuer; - - qraffle.submitProposal(users[0], token, 1000000); - - // Vote yes for the proposal - for (const auto& user : users) - { - qraffle.voteInProposal(user, 0, 1); - } - - // End epoch to activate token raffle - qraffle.endEpoch(); - - // Test active token raffle - auto activeRaffle = qraffle.getActiveTokenRaffle(0); - EXPECT_EQ(activeRaffle.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(activeRaffle.tokenName, assetName); - EXPECT_EQ(activeRaffle.tokenIssuer, issuer); - EXPECT_EQ(activeRaffle.entryAmount, 1000000); - - // Test successful token raffle deposit - uint32 memberCount = 0; - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_TRANSFER_SHARE_FEE); - EXPECT_EQ(qraffle.transferShareOwnershipAndPossession(issuer, assetName, issuer, 1000000, user), 1000000); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user, user, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 1000000); - - EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName, QRAFFLE_CONTRACT_INDEX, 1000000, user), 1000000); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user, user, QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), 1000000); - - auto result = qraffle.depositInTokenRaffle(user, 0, QRAFFLE_TRANSFER_SHARE_FEE); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - memberCount++; - qraffle.getState()->tokenRaffleMemberChecker(0, user, memberCount); - } - - // Test insufficient funds - id poorUser = getUser(9999); - increaseEnergy(poorUser, QRAFFLE_TRANSFER_SHARE_FEE - 1); - auto result = qraffle.depositInTokenRaffle(poorUser, 0, QRAFFLE_TRANSFER_SHARE_FEE - 1); - EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_FUND); - - // Test insufficient Token - id poorUser2 = getUser(8888); - increaseEnergy(poorUser2, QRAFFLE_TRANSFER_SHARE_FEE); - qraffle.transferShareOwnershipAndPossession(issuer, assetName, issuer, 999999, poorUser2); - result = qraffle.depositInTokenRaffle(poorUser2, 0, QRAFFLE_TRANSFER_SHARE_FEE); - EXPECT_EQ(result.returnCode, QRAFFLE_FAILED_TO_DEPOSIT); - - // Test invalid token raffle index - increaseEnergy(users[0], QRAFFLE_TRANSFER_SHARE_FEE); - result = qraffle.depositInTokenRaffle(users[0], 999, QRAFFLE_TRANSFER_SHARE_FEE); - EXPECT_EQ(result.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); -} - -TEST(ContractQraffle, TransferShareManagementRights) -{ - ContractTestingQraffle qraffle; - - id issuer = getUser(1000); - increaseEnergy(issuer, 2000000000ULL); - uint64 assetName = assetNameFromString("TOKENRF"); - qraffle.issueAsset(issuer, assetName, 1000000000000, 0, 0); - - id user1 = getUser(1001); - increaseEnergy(user1, 1000000000ULL); - qraffle.transferShareOwnershipAndPossession(issuer, assetName, issuer, 1000000, user1); - EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName, QRAFFLE_CONTRACT_INDEX, 1000000, user1), 1000000); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user1, user1, QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), 1000000); - - increaseEnergy(user1, 1000000000ULL); - qraffle.TransferShareManagementRightsQraffle(issuer, assetName, QX_CONTRACT_INDEX, 1000000, user1); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user1, user1, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 1000000); -} - -TEST(ContractQraffle, GetFunctions) -{ - ContractTestingQraffle qraffle; - system.epoch = 0; - - // Setup: Create test users and register them - auto users = getRandomUsers(1000, 1000); // Use smaller set for more predictable testing - uint32 registerCount = 5; - uint32 proposalCount = 0; - uint32 entrySubmittedCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - registerCount++; - } - - // Submit entry amounts for some users - for (size_t i = 0; i < users.size() / 2; ++i) - { - uint64 amount = random(1000000, 1000000000); - auto result = qraffle.submitEntryAmount(users[i], amount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - entrySubmittedCount++; - } - - // Create some proposals - id issuer = getUser(2000); - increaseEnergy(issuer, 1000000000ULL); - uint64 assetName1 = assetNameFromString("TEST1"); - uint64 assetName2 = assetNameFromString("TEST2"); - qraffle.issueAsset(issuer, assetName1, 1000000000, 0, 0); - qraffle.issueAsset(issuer, assetName2, 2000000000, 0, 0); - - Asset token1, token2; - token1.assetName = assetName1; - token1.issuer = issuer; - token2.assetName = assetName2; - token2.issuer = issuer; - - // Submit proposals - for (size_t i = 0; i < std::min(users.size(), (size_t)5); ++i) - { - uint64 entryAmount = random(1000000, 1000000000); - Asset token = (i % 2 == 0) ? token1 : token2; - - increaseEnergy(users[i], 1000); - auto result = qraffle.submitProposal(users[i], token, entryAmount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - proposalCount++; - } - - // Vote on proposals - for (const auto& user : users) - { - for (uint32 i = 0; i < proposalCount; ++i) - { - bit vote = (bit)(i % 2); - auto result = qraffle.voteInProposal(user, i, vote); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - } - } - - // Deposit in QuRaffle - uint32 memberCount = 0; - for (size_t i = 0; i < users.size() / 3; ++i) - { - increaseEnergy(users[i], qraffle.getState()->getQuRaffleEntryAmount()); - auto result = qraffle.depositInQuRaffle(users[i], qraffle.getState()->getQuRaffleEntryAmount()); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - memberCount++; - } - - // Test 1: getActiveProposal function - { - // Test with valid proposal indices - for (uint32 i = 0; i < proposalCount; ++i) - { - auto proposal = qraffle.getActiveProposal(i); - EXPECT_EQ(proposal.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(proposal.tokenName, (i % 2 == 0) ? assetName1 : assetName2); - EXPECT_EQ(proposal.tokenIssuer, issuer); - EXPECT_GT(proposal.entryAmount, 0); - EXPECT_GE(proposal.nYes, 0u); - EXPECT_GE(proposal.nNo, 0u); - } - - // Test with invalid proposal index (beyond available proposals) - auto invalidProposal = qraffle.getActiveProposal(proposalCount + 10); - EXPECT_EQ(invalidProposal.returnCode, QRAFFLE_INVALID_PROPOSAL); - - // Test with very large proposal index - auto largeIndexProposal = qraffle.getActiveProposal(UINT32_MAX); - EXPECT_EQ(largeIndexProposal.returnCode, QRAFFLE_INVALID_PROPOSAL); - } - - - // End epoch to create some ended raffles - qraffle.endEpoch(); - - // ===== DETAILED TEST CASES FOR EACH GETTER FUNCTION ===== - - // Test 2: getRegisters function - { - // Test with valid offset and limit - auto registers = qraffle.getRegisters(0, 10); - EXPECT_EQ(registers.returnCode, QRAFFLE_SUCCESS); - - // Test with offset beyond available registers - auto registers2 = qraffle.getRegisters(registerCount + 10, 5); - EXPECT_EQ(registers2.returnCode, QRAFFLE_INVALID_OFFSET_OR_LIMIT); - - // Test with limit exceeding maximum (1024) - auto registers3 = qraffle.getRegisters(0, 1025); - EXPECT_EQ(registers3.returnCode, QRAFFLE_INVALID_OFFSET_OR_LIMIT); - - // Test with offset + limit exceeding total registers - auto registers4 = qraffle.getRegisters(registerCount - 5, 10); - EXPECT_EQ(registers4.returnCode, QRAFFLE_INVALID_OFFSET_OR_LIMIT); - - // Test with zero limit - auto registers5 = qraffle.getRegisters(0, 0); - EXPECT_EQ(registers5.returnCode, QRAFFLE_SUCCESS); - } - - // Test 3: getAnalytics function - { - auto analytics = qraffle.getAnalytics(); - EXPECT_EQ(analytics.returnCode, QRAFFLE_SUCCESS); - - // Validate all analytics fields - EXPECT_GE(analytics.totalBurnAmount, 0); - EXPECT_GE(analytics.totalCharityAmount, 0); - EXPECT_GE(analytics.totalShareholderAmount, 0); - EXPECT_GE(analytics.totalRegisterAmount, 0); - EXPECT_GE(analytics.totalFeeAmount, 0); - EXPECT_GE(analytics.totalWinnerAmount, 0); - EXPECT_GE(analytics.largestWinnerAmount, 0); - EXPECT_EQ(analytics.numberOfRegisters, registerCount); - EXPECT_EQ(analytics.numberOfProposals, 0); - EXPECT_EQ(analytics.numberOfQuRaffleMembers, 0); - EXPECT_GE(analytics.numberOfActiveTokenRaffle, 0u); - EXPECT_GE(analytics.numberOfEndedTokenRaffle, 0u); - EXPECT_EQ(analytics.numberOfEntryAmountSubmitted, 0u); - - // Cross-validate with internal state - qraffle.getState()->analyticsChecker(analytics.totalBurnAmount, analytics.totalCharityAmount, - analytics.totalShareholderAmount, analytics.totalRegisterAmount, - analytics.totalFeeAmount, analytics.totalWinnerAmount, - analytics.largestWinnerAmount, analytics.numberOfRegisters, - analytics.numberOfProposals, analytics.numberOfQuRaffleMembers, - analytics.numberOfActiveTokenRaffle, analytics.numberOfEndedTokenRaffle, - analytics.numberOfEntryAmountSubmitted); - - // Direct-validate with calculated values - // Calculate expected values based on the test setup - uint64 expectedTotalBurnAmount = 0; - uint64 expectedTotalCharityAmount = 0; - uint64 expectedTotalShareholderAmount = 0; - uint64 expectedTotalRegisterAmount = 0; - uint64 expectedTotalFeeAmount = 0; - uint64 expectedTotalWinnerAmount = 0; - uint64 expectedLargestWinnerAmount = 0; - - // Calculate expected values from QuRaffle (if any members participated) - if (memberCount > 0) { - uint64 qREAmount = 10000000; // initial entry amount - uint64 totalQuRaffleAmount = qREAmount * memberCount; - - expectedTotalBurnAmount += (totalQuRaffleAmount * QRAFFLE_BURN_FEE) / 100; - expectedTotalCharityAmount += (totalQuRaffleAmount * QRAFFLE_CHARITY_FEE) / 100; - expectedTotalShareholderAmount += ((totalQuRaffleAmount * QRAFFLE_SHRAEHOLDER_FEE) / 100) / 676 * 676; - expectedTotalRegisterAmount += ((totalQuRaffleAmount * QRAFFLE_REGISTER_FEE) / 100) / registerCount * registerCount; - expectedTotalFeeAmount += (totalQuRaffleAmount * QRAFFLE_FEE) / 100; - - // Winner amount calculation (after all fees) - uint64 winnerAmount = totalQuRaffleAmount - expectedTotalBurnAmount - expectedTotalCharityAmount - - expectedTotalShareholderAmount - expectedTotalRegisterAmount - expectedTotalFeeAmount; - expectedTotalWinnerAmount += winnerAmount; - expectedLargestWinnerAmount = winnerAmount; // First winner sets the largest - } - - // Validate calculated values - EXPECT_EQ(analytics.totalBurnAmount, expectedTotalBurnAmount); - EXPECT_EQ(analytics.totalCharityAmount, expectedTotalCharityAmount); - EXPECT_EQ(analytics.totalShareholderAmount, expectedTotalShareholderAmount); - EXPECT_EQ(analytics.totalRegisterAmount, expectedTotalRegisterAmount); - EXPECT_EQ(analytics.totalFeeAmount, expectedTotalFeeAmount); - EXPECT_EQ(analytics.totalWinnerAmount, expectedTotalWinnerAmount); - EXPECT_EQ(analytics.largestWinnerAmount, expectedLargestWinnerAmount); - - // Validate counters - EXPECT_EQ(analytics.numberOfRegisters, registerCount); - EXPECT_EQ(analytics.numberOfProposals, 0); // Proposals are cleared after epoch end - EXPECT_EQ(analytics.numberOfQuRaffleMembers, 0); // Members are cleared after epoch end - EXPECT_EQ(analytics.numberOfActiveTokenRaffle, qraffle.getState()->getNumberOfActiveTokenRaffle()); - EXPECT_EQ(analytics.numberOfEndedTokenRaffle, qraffle.getState()->getNumberOfEndedTokenRaffle()); - EXPECT_EQ(analytics.numberOfEntryAmountSubmitted, 0); // Entry amounts are cleared after epoch end - - } - - // Test 4: getEndedTokenRaffle function - { - // Test with valid raffle indices (if any ended raffles exist) - for (uint32 i = 0; i < qraffle.getState()->getNumberOfEndedTokenRaffle(); ++i) - { - auto endedRaffle = qraffle.getEndedTokenRaffle(i); - EXPECT_EQ(endedRaffle.returnCode, QRAFFLE_SUCCESS); - EXPECT_NE(endedRaffle.epochWinner, id(0, 0, 0, 0)); // Winner should be set - EXPECT_GT(endedRaffle.entryAmount, 0); - EXPECT_GT(endedRaffle.numberOfMembers, 0u); - EXPECT_GE(endedRaffle.epoch, 0u); - } - - // Test with invalid raffle index (beyond available ended raffles) - auto invalidEndedRaffle = qraffle.getEndedTokenRaffle(qraffle.getState()->getNumberOfEndedTokenRaffle() + 10); - EXPECT_EQ(invalidEndedRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); - - // Test with very large raffle index - auto largeIndexEndedRaffle = qraffle.getEndedTokenRaffle(UINT32_MAX); - EXPECT_EQ(largeIndexEndedRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); - } - - // Test 5: getEpochRaffleIndexes function - { - // Test with current epoch (0) - auto raffleIndexes = qraffle.getEpochRaffleIndexes(0); - EXPECT_EQ(raffleIndexes.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(raffleIndexes.StartIndex, 0); - EXPECT_EQ(raffleIndexes.EndIndex, qraffle.getState()->getNumberOfActiveTokenRaffle()); - - // Test with future epoch - auto futureRaffleIndexes = qraffle.getEpochRaffleIndexes(1); - EXPECT_EQ(futureRaffleIndexes.returnCode, QRAFFLE_INVALID_EPOCH); - - // Test with past epoch (if any exist) - if (qraffle.getState()->getNumberOfEndedTokenRaffle() > 0) - { - auto pastRaffleIndexes = qraffle.getEpochRaffleIndexes(0); // Should work for epoch 0 - EXPECT_EQ(pastRaffleIndexes.returnCode, QRAFFLE_SUCCESS); - } - } - - // Test 6: getEndedQuRaffle function - { - // Test with current epoch (0) - auto endedQuRaffle = qraffle.getEndedQuRaffle(0); - EXPECT_EQ(endedQuRaffle.returnCode, QRAFFLE_SUCCESS); - EXPECT_NE(endedQuRaffle.epochWinner, id(0, 0, 0, 0)); // Winner should be set - EXPECT_GT(endedQuRaffle.receivedAmount, 0); - EXPECT_EQ(endedQuRaffle.entryAmount, 10000000); - EXPECT_EQ(endedQuRaffle.numberOfMembers, memberCount); - - // Test with future epoch - auto futureQuRaffle = qraffle.getEndedQuRaffle(1); - EXPECT_EQ(futureQuRaffle.returnCode, QRAFFLE_SUCCESS); - - // Test with very large epoch number - auto largeEpochQuRaffle = qraffle.getEndedQuRaffle(UINT16_MAX); - EXPECT_EQ(largeEpochQuRaffle.returnCode, QRAFFLE_SUCCESS); - } - - // Test 7: getActiveTokenRaffle function - { - // Test with valid raffle indices (if any active raffles exist) - for (uint32 i = 0; i < qraffle.getState()->getNumberOfActiveTokenRaffle(); ++i) - { - auto activeRaffle = qraffle.getActiveTokenRaffle(i); - EXPECT_EQ(activeRaffle.returnCode, QRAFFLE_SUCCESS); - EXPECT_GT(activeRaffle.tokenName, 0); - EXPECT_NE(activeRaffle.tokenIssuer, id(0, 0, 0, 0)); - EXPECT_GT(activeRaffle.entryAmount, 0); - } - - // Test with invalid raffle index (beyond available active raffles) - auto invalidActiveRaffle = qraffle.getActiveTokenRaffle(qraffle.getState()->getNumberOfActiveTokenRaffle() + 10); - EXPECT_EQ(invalidActiveRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); - - // Test with very large raffle index - auto largeIndexActiveRaffle = qraffle.getActiveTokenRaffle(UINT32_MAX); - EXPECT_EQ(largeIndexActiveRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); - } -} - -TEST(ContractQraffle, EndEpoch) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - } - - // Submit entry amounts - for (const auto& user : users) - { - uint64 amount = random(1000000, 1000000000); - qraffle.submitEntryAmount(user, amount); - } - - // Create proposals and vote for them - id issuer = getUser(2000); - increaseEnergy(issuer, 3000000000ULL); - uint64 assetName1 = assetNameFromString("TOKEN1"); - uint64 assetName2 = assetNameFromString("TOKEN2"); - qraffle.issueAsset(issuer, assetName1, 1000000000, 0, 0); - qraffle.issueAsset(issuer, assetName2, 2000000000, 0, 0); - - Asset token1, token2; - token1.assetName = assetName1; - token1.issuer = issuer; - token2.assetName = assetName2; - token2.issuer = issuer; - - qraffle.submitProposal(users[0], token1, 1000000); - qraffle.submitProposal(users[1], token2, 2000000); - - // Vote yes for both proposals - for (const auto& user : users) - { - qraffle.voteInProposal(user, 0, 1); - qraffle.voteInProposal(user, 1, 1); - } - - // Deposit in QuRaffle - for (const auto& user : users) - { - increaseEnergy(user, qraffle.getState()->getQuRaffleEntryAmount()); - qraffle.depositInQuRaffle(user, qraffle.getState()->getQuRaffleEntryAmount()); - } - - // Deposit in token raffles - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_TRANSFER_SHARE_FEE + 1000000); - EXPECT_EQ(qraffle.transferShareOwnershipAndPossession(issuer, assetName1, issuer, 1000000, user), 1000000); - EXPECT_EQ(qraffle.transferShareOwnershipAndPossession(issuer, assetName2, issuer, 2000000, user), 2000000); - } - - // End epoch - qraffle.endEpoch(); - - qraffle.getState()->activeTokenRaffleChecker(0, token1, 1000000); - qraffle.getState()->activeTokenRaffleChecker(1, token2, 2000000); - - // Deposit in token raffles - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_TRANSFER_SHARE_FEE); - EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName1, QRAFFLE_CONTRACT_INDEX, 1000000, user), 1000000); - EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName2, QRAFFLE_CONTRACT_INDEX, 2000000, user), 2000000); - - qraffle.depositInTokenRaffle(user, 0, QRAFFLE_TRANSFER_SHARE_FEE); - qraffle.depositInTokenRaffle(user, 1, QRAFFLE_TRANSFER_SHARE_FEE); - } - - // Check that QuRaffle was processed - auto quRaffle = qraffle.getEndedQuRaffle(0); - EXPECT_EQ(quRaffle.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->quRaffleWinnerChecker(0, quRaffle.epochWinner, quRaffle.receivedAmount, - quRaffle.entryAmount, quRaffle.numberOfMembers, quRaffle.winnerIndex); - - qraffle.endEpoch(); - // Check that token raffles were processed - auto tokenRaffle1 = qraffle.getEndedTokenRaffle(0); - EXPECT_EQ(tokenRaffle1.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->endedTokenRaffleChecker(0, tokenRaffle1.epochWinner, token1, - tokenRaffle1.entryAmount, tokenRaffle1.numberOfMembers, - tokenRaffle1.winnerIndex, tokenRaffle1.epoch); - - auto tokenRaffle2 = qraffle.getEndedTokenRaffle(1); - EXPECT_EQ(tokenRaffle2.returnCode, QRAFFLE_SUCCESS); - qraffle.getState()->endedTokenRaffleChecker(1, tokenRaffle2.epochWinner, token2, - tokenRaffle2.entryAmount, tokenRaffle2.numberOfMembers, - tokenRaffle2.winnerIndex, tokenRaffle2.epoch); - - // Check analytics after epoch - auto analytics = qraffle.getAnalytics(); - EXPECT_EQ(analytics.returnCode, QRAFFLE_SUCCESS); - EXPECT_GT(analytics.totalBurnAmount, 0); - EXPECT_GT(analytics.totalCharityAmount, 0); - EXPECT_GT(analytics.totalShareholderAmount, 0); - EXPECT_GT(analytics.totalWinnerAmount, 0); -} - -TEST(ContractQraffle, RegisterInSystemWithQXMR) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Issue QXMR tokens to users - id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); - increaseEnergy(qxmrIssuer, 2000000000ULL); - qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); - - // Test successful registration with QXMR tokens - for (const auto& user : users) - { - increaseEnergy(user, 1000); - // Transfer QXMR tokens to user - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, user); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, user); - - // Register using QXMR tokens - auto result = qraffle.registerInSystem(user, 0, 1); // useQXMR = 1 - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - registerCount++; - qraffle.getState()->registerChecker(user, registerCount, true); - } - - // Test insufficient QXMR tokens - id poorUser = getUser(9999); - increaseEnergy(poorUser, 1000); - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT - 1, poorUser); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT - 1, poorUser); - auto result = qraffle.registerInSystem(poorUser, 0, 1); - EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_QXMR); - qraffle.getState()->registerChecker(poorUser, registerCount, false); - - // Test already registered with QXMR - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, users[0]); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, users[0]); - result = qraffle.registerInSystem(users[0], 0, 1); - EXPECT_EQ(result.returnCode, QRAFFLE_ALREADY_REGISTERED); - qraffle.getState()->registerChecker(users[0], registerCount, true); -} - -TEST(ContractQraffle, LogoutInSystemWithQXMR) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Issue QXMR tokens - id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); - increaseEnergy(qxmrIssuer, 2000000000ULL); - qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); - - // Register users with QXMR tokens first - for (const auto& user : users) - { - increaseEnergy(user, 1000); - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, user); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, user); - qraffle.registerInSystem(user, 0, 1); - registerCount++; - } - - // Test successful logout with QXMR tokens - for (const auto& user : users) - { - increaseEnergy(user, 1000); - auto result = qraffle.logoutInSystem(user); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - // Check that user received QXMR refund - uint64 expectedRefund = QRAFFLE_QXMR_REGISTER_AMOUNT - QRAFFLE_QXMR_LOGOUT_FEE; - EXPECT_EQ(numberOfPossessedShares(QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, user, user, QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), expectedRefund); - - registerCount--; - qraffle.getState()->unregisterChecker(user, registerCount); - } - - // Test unregistered user logout with QXMR - increaseEnergy(users[0], 1000); - auto result = qraffle.logoutInSystem(users[0]); - EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); -} - -TEST(ContractQraffle, MixedRegistrationAndLogout) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Issue QXMR tokens - id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); - increaseEnergy(qxmrIssuer, 2000000000ULL); - qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); - - // Register some users with qubic, some with QXMR - for (size_t i = 0; i < users.size(); ++i) - { - if (i % 2 == 0) - { - // Register with qubic - increaseEnergy(users[i], QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.registerInSystem(users[i], QRAFFLE_REGISTER_AMOUNT, 0); // useQXMR = 0 - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - } - else - { - // Register with QXMR - increaseEnergy(users[i], 1000); - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, users[i]); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, users[i]); - auto result = qraffle.registerInSystem(users[i], 0, 1); // useQXMR = 1 - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - } - registerCount++; - } - - // Logout some users with qubic, some with QXMR - for (size_t i = 0; i < users.size(); ++i) - { - if (i % 2 == 0) - { - // Logout with qubic - auto result = qraffle.logoutInSystem(users[i]); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(getBalance(users[i]), QRAFFLE_REGISTER_AMOUNT - QRAFFLE_LOGOUT_FEE); - } - else - { - // Logout with QXMR - auto result = qraffle.logoutInSystem(users[i]); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - uint64 expectedRefund = QRAFFLE_QXMR_REGISTER_AMOUNT - QRAFFLE_QXMR_LOGOUT_FEE; - EXPECT_EQ(numberOfPossessedShares(QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, users[i], users[i], QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), expectedRefund); - } - registerCount--; - } - - // Verify final state - EXPECT_EQ(qraffle.getState()->getNumberOfRegisters(), registerCount); -} - -TEST(ContractQraffle, QXMRInvalidTokenType) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Issue QXMR tokens - id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); - increaseEnergy(qxmrIssuer, 2000000000ULL); - qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); - - // Register user with qubic (token type 1) - increaseEnergy(users[0], QRAFFLE_REGISTER_AMOUNT); - qraffle.registerInSystem(users[0], QRAFFLE_REGISTER_AMOUNT, 0); - registerCount++; - - // Try to logout with QXMR when registered with qubic - auto result = qraffle.logoutInSystem(users[0]); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - // Register user with QXMR (token type 2) - increaseEnergy(users[1], 1000); - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, users[1]); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, users[1]); - qraffle.registerInSystem(users[1], 0, 1); - registerCount++; - - // Try to logout - result = qraffle.logoutInSystem(users[1]); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - registerCount--; -} - -TEST(ContractQraffle, QXMRRevenueDistribution) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - - // Issue QXMR tokens - id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); - increaseEnergy(qxmrIssuer, 2000000000ULL); - qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); - - // Register some users with QXMR to generate QXMR revenue - for (const auto& user : users) - { - increaseEnergy(user, 1000); - qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, user); - qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, user); - qraffle.registerInSystem(user, 0, 1); - registerCount++; - } - - uint64 expectedQXMRRevenue = 0; - // Logout some users to generate QXMR revenue - for (size_t i = 0; i < users.size(); ++i) - { - auto result = qraffle.logoutInSystem(users[i]); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - expectedQXMRRevenue += QRAFFLE_QXMR_LOGOUT_FEE; - registerCount--; - } - - // Check that QXMR revenue was recorded - EXPECT_EQ(qraffle.getState()->getEpochQXMRRevenue(), expectedQXMRRevenue); - - // Test QXMR revenue distribution during epoch end - increaseEnergy(users[0], QRAFFLE_DEFAULT_QRAFFLE_AMOUNT); - qraffle.depositInQuRaffle(users[0], QRAFFLE_DEFAULT_QRAFFLE_AMOUNT); - - qraffle.endEpoch(); - EXPECT_EQ(qraffle.getState()->getEpochQXMRRevenue(), expectedQXMRRevenue - div(expectedQXMRRevenue, 676ull) * 676); -} - -TEST(ContractQraffle, GetQuRaffleEntryAmountPerUser) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - uint32 entrySubmittedCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - registerCount++; - } - - // Test 1: Query entry amount for users who haven't submitted any - for (const auto& user : users) - { - auto result = qraffle.getQuRaffleEntryAmountPerUser(user); - EXPECT_EQ(result.returnCode, QRAFFLE_USER_NOT_FOUND); - EXPECT_EQ(result.entryAmount, 0); - } - - // Submit entry amounts for some users - std::vector submittedAmounts; - for (size_t i = 0; i < users.size() / 2; ++i) - { - uint64 amount = random(1000000, 1000000000); - auto result = qraffle.submitEntryAmount(users[i], amount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - submittedAmounts.push_back(amount); - entrySubmittedCount++; - } - - // Test 2: Query entry amount for users who have submitted amounts - for (size_t i = 0; i < submittedAmounts.size(); ++i) - { - auto result = qraffle.getQuRaffleEntryAmountPerUser(users[i]); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(result.entryAmount, submittedAmounts[i]); - } - - // Test 3: Query entry amount for users who haven't submitted amounts - for (size_t i = submittedAmounts.size(); i < users.size(); ++i) - { - auto result = qraffle.getQuRaffleEntryAmountPerUser(users[i]); - EXPECT_EQ(result.returnCode, QRAFFLE_USER_NOT_FOUND); - EXPECT_EQ(result.entryAmount, 0); - } - - // Test 4: Update entry amount and verify - uint64 newAmount = random(1000000, 1000000000); - auto result = qraffle.submitEntryAmount(users[0], newAmount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - - auto updatedResult = qraffle.getQuRaffleEntryAmountPerUser(users[0]); - EXPECT_EQ(updatedResult.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(updatedResult.entryAmount, newAmount); - - // Test 5: Query for non-existent user - id nonExistentUser = getUser(99999); - auto nonExistentResult = qraffle.getQuRaffleEntryAmountPerUser(nonExistentUser); - EXPECT_EQ(nonExistentResult.returnCode, QRAFFLE_USER_NOT_FOUND); - EXPECT_EQ(nonExistentResult.entryAmount, 0); - - // Test 6: Query for unregistered user - id unregisteredUser = getUser(88888); - increaseEnergy(unregisteredUser, QRAFFLE_REGISTER_AMOUNT); - auto unregisteredResult = qraffle.getQuRaffleEntryAmountPerUser(unregisteredUser); - EXPECT_EQ(unregisteredResult.returnCode, QRAFFLE_USER_NOT_FOUND); - EXPECT_EQ(unregisteredResult.entryAmount, 0); -} - -TEST(ContractQraffle, GetQuRaffleEntryAverageAmount) -{ - ContractTestingQraffle qraffle; - - auto users = getRandomUsers(1000, 1000); - uint32 registerCount = 5; - uint32 entrySubmittedCount = 0; - - // Register users first - for (const auto& user : users) - { - increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); - auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - registerCount++; - } - - // Test 1: Query average when no users have submitted entry amounts - auto result = qraffle.getQuRaffleEntryAverageAmount(); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(result.entryAverageAmount, 0); - - // Submit entry amounts for some users - std::vector submittedAmounts; - uint64 totalAmount = 0; - for (size_t i = 0; i < users.size() / 2; ++i) - { - uint64 amount = random(1000000, 1000000000); - increaseEnergy(users[i], amount); - auto result = qraffle.submitEntryAmount(users[i], amount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - submittedAmounts.push_back(amount); - totalAmount += amount; - entrySubmittedCount++; - } - - // Test 2: Query average with submitted amounts - auto averageResult = qraffle.getQuRaffleEntryAverageAmount(); - EXPECT_EQ(averageResult.returnCode, QRAFFLE_SUCCESS); - - // Calculate expected average - uint64 expectedAverage = 0; - if (submittedAmounts.size() > 0) - { - expectedAverage = totalAmount / submittedAmounts.size(); - } - EXPECT_EQ(averageResult.entryAverageAmount, expectedAverage); - - // Test 3: Add more users and verify average updates - std::vector additionalAmounts; - uint64 additionalTotal = 0; - for (size_t i = users.size() / 2; i < users.size(); ++i) - { - uint64 amount = random(1000000, 1000000000); - auto result = qraffle.submitEntryAmount(users[i], amount); - EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); - additionalAmounts.push_back(amount); - additionalTotal += amount; - entrySubmittedCount++; - } - - // Calculate new expected average - uint64 newTotalAmount = totalAmount + additionalTotal; - uint64 newExpectedAverage = newTotalAmount / (submittedAmounts.size() + additionalAmounts.size()); - - auto updatedAverageResult = qraffle.getQuRaffleEntryAverageAmount(); - EXPECT_EQ(updatedAverageResult.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(updatedAverageResult.entryAverageAmount, newExpectedAverage); - - // Test 4: Update existing user's entry amount and verify average - uint64 updatedAmount = random(1000000, 1000000000); - auto updateResult = qraffle.submitEntryAmount(users[0], updatedAmount); - EXPECT_EQ(updateResult.returnCode, QRAFFLE_SUCCESS); - - // Recalculate expected average with updated amount - uint64 recalculatedTotal = newTotalAmount - submittedAmounts[0] + updatedAmount; - uint64 recalculatedAverage = recalculatedTotal / (submittedAmounts.size() + additionalAmounts.size()); - - auto recalculatedAverageResult = qraffle.getQuRaffleEntryAverageAmount(); - EXPECT_EQ(recalculatedAverageResult.returnCode, QRAFFLE_SUCCESS); - EXPECT_EQ(recalculatedAverageResult.entryAverageAmount, recalculatedAverage); -} \ No newline at end of file diff --git a/test/contract_qrp.cpp b/test/contract_qrp.cpp deleted file mode 100644 index 29795dec9..000000000 --- a/test/contract_qrp.cpp +++ /dev/null @@ -1,263 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -// Procedure/function indices (must match REGISTER_USER_FUNCTIONS_AND_PROCEDURES in `src/contracts/QReservePool.h`). -constexpr uint16 QRP_PROC_GET_RESERVE = 1; -constexpr uint16 QRP_PROC_ADD_ALLOWED_SC = 2; -constexpr uint16 QRP_PROC_REMOVE_ALLOWED_SC = 3; -constexpr uint16 QRP_PROC_SEND_RESERVE = 4; - -constexpr uint16 QRP_FUNC_GET_AVAILABLE_RESERVE = 1; -constexpr uint16 QRP_FUNC_GET_ALLOWED_SC = 2; - -static const id QRP_CONTRACT_ID(QRP_CONTRACT_INDEX, 0, 0, 0); -static const id QRP_DEFAULT_SC_ID(QRP_QTF_INDEX, 0, 0, 0); - -class QRPChecker : public QRP -{ -public: - const id& team() const { return teamAddress; } - const id& owner() const { return ownerAddress; } - bool hasAllowedSC(const id& sc) const { return allowedSmartContracts.contains(sc); } - uint64 allowedCount() const { return allowedSmartContracts.population(); } -}; - -class ContractTestingQRP : protected ContractTesting -{ -public: - ContractTestingQRP() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QRP); - callSystemProcedure(QRP_CONTRACT_INDEX, INITIALIZE); - } - - QRPChecker* state() { return reinterpret_cast(contractStates[QRP_CONTRACT_INDEX]); } - - uint64 balanceOf(const id& account) const { return static_cast(getBalance(account)); } - uint64 balanceQrp() const { return balanceOf(QRP_CONTRACT_ID); } - void fund(const id& account, uint64 amount) { increaseEnergy(account, amount); } - void fundQrp(uint64 amount) { fund(QRP_CONTRACT_ID, amount); } - - QRP::WithdrawReserve_output withdrawReserveReserve(const id& invocator, uint64 revenue, sint64 attachedAmount = 0) - { - QRP::WithdrawReserve_input input{revenue}; - QRP::WithdrawReserve_output output{}; - invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_GET_RESERVE, input, output, invocator, attachedAmount); - return output; - } - - QRP::AddAllowedSC_output addAllowedSC(const id& invocator, uint64 scIndex) - { - QRP::AddAllowedSC_input input{scIndex}; - QRP::AddAllowedSC_output output{}; - invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_ADD_ALLOWED_SC, input, output, invocator, 0); - return output; - } - - QRP::RemoveAllowedSC_output removeAllowedSC(const id& invocator, uint64 scIndex) - { - QRP::RemoveAllowedSC_input input{scIndex}; - QRP::RemoveAllowedSC_output output{}; - invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_REMOVE_ALLOWED_SC, input, output, invocator, 0); - return output; - } - - QRP::SendReserve_output sendReserve(const id& invocator, uint64 scIndex, uint64 amount) - { - QRP::SendReserve_input input{scIndex, amount}; - QRP::SendReserve_output output{}; - invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_SEND_RESERVE, input, output, invocator, 0); - return output; - } - - QRP::GetAvailableReserve_output getAvailableReserve() const - { - QRP::GetAvailableReserve_input input{}; - QRP::GetAvailableReserve_output output{}; - callFunction(QRP_CONTRACT_INDEX, QRP_FUNC_GET_AVAILABLE_RESERVE, input, output); - return output; - } - - QRP::GetAllowedSC_output getAllowedSC() const - { - QRP::GetAllowedSC_input input{}; - QRP::GetAllowedSC_output output{}; - callFunction(QRP_CONTRACT_INDEX, QRP_FUNC_GET_ALLOWED_SC, input, output); - return output; - } -}; - -static bool containsAllowedSC(const QRP::GetAllowedSC_output& allowed, const id& sc) -{ - for (uint64 i = 0; i < QRP_ALLOWED_SC_NUM; ++i) - { - if (allowed.allowedSC.get(i) == sc) - { - return true; - } - } - return false; -} - -TEST(ContractQReservePool, WithdrawReserveEnforcesAuthorizationAndBalance) -{ - ContractTestingQRP qrp; - const id unauthorized = id::randomValue(); - qrp.fund(unauthorized, 0); - qrp.fund(QRP_DEFAULT_SC_ID, 0); - - QRP::WithdrawReserve_output denied = qrp.withdrawReserveReserve(unauthorized, 100); - EXPECT_EQ(denied.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); - EXPECT_EQ(denied.allocatedRevenue, 0ull); - - qrp.fundQrp(1000); - EXPECT_EQ(qrp.balanceQrp(), 1000); - - QRP::WithdrawReserve_output granted = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 600); - EXPECT_EQ(granted.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_EQ(granted.allocatedRevenue, 600ull); - EXPECT_EQ(qrp.balanceQrp(), 400); - EXPECT_EQ(qrp.balanceOf(QRP_DEFAULT_SC_ID), 600); - - QRP::WithdrawReserve_output insufficient = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 500); - EXPECT_EQ(insufficient.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE)); - EXPECT_EQ(insufficient.allocatedRevenue, 0ull); - EXPECT_EQ(qrp.balanceQrp(), 400); - EXPECT_EQ(qrp.balanceOf(QRP_DEFAULT_SC_ID), 600); -} - -TEST(ContractQReservePool, WithdrawReserve_ZeroAndExactRemaining) -{ - ContractTestingQRP qrp; - qrp.fund(QRP_DEFAULT_SC_ID, 0); - - qrp.fundQrp(1000); - EXPECT_EQ(qrp.balanceQrp(), 1000); - - // Zero request should not move funds. - const QRP::WithdrawReserve_output zero = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 0); - EXPECT_EQ(zero.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_EQ(zero.allocatedRevenue, 0ull); - EXPECT_EQ(qrp.balanceQrp(), 1000); - - // Exact remaining should succeed and drain the reserve. - const QRP::WithdrawReserve_output exact = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 1000); - EXPECT_EQ(exact.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_EQ(exact.allocatedRevenue, 1000ull); - EXPECT_EQ(qrp.balanceQrp(), 0); - EXPECT_EQ(qrp.balanceOf(QRP_DEFAULT_SC_ID), 1000); -} - -TEST(ContractQReservePool, OwnerAddsAndRemovesSmartContracts) -{ - ContractTestingQRP qrp; - QRPChecker* state = qrp.state(); - constexpr uint64 newScIndex = 77; - const id newScId(newScIndex, 0, 0, 0); - const id outsider(200, 0, 0, 0); - qrp.fund(newScId, 0); - qrp.fund(outsider, 0); - qrp.fund(state->owner(), 0); - - QRP::AddAllowedSC_output deniedAdd = qrp.addAllowedSC(outsider, newScIndex); - EXPECT_EQ(deniedAdd.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); - EXPECT_FALSE(state->hasAllowedSC(newScId)); - - QRP::AddAllowedSC_output approvedAdd = qrp.addAllowedSC(state->owner(), newScIndex); - EXPECT_EQ(approvedAdd.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_TRUE(state->hasAllowedSC(newScId)); - - QRP::GetAllowedSC_output allowed = qrp.getAllowedSC(); - EXPECT_TRUE(containsAllowedSC(allowed, newScId)); - - QRP::RemoveAllowedSC_output deniedRemove = qrp.removeAllowedSC(outsider, newScIndex); - EXPECT_EQ(deniedRemove.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); - EXPECT_TRUE(state->hasAllowedSC(newScId)); - - QRP::RemoveAllowedSC_output approvedRemove = qrp.removeAllowedSC(state->owner(), newScIndex); - EXPECT_EQ(approvedRemove.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_FALSE(state->hasAllowedSC(newScId)); -} - -TEST(ContractQReservePool, OwnerAddRemove_IdempotencyAndBounds) -{ - ContractTestingQRP qrp; - QRPChecker* state = qrp.state(); - qrp.fund(state->owner(), 0); - - constexpr uint64 newScIndex = 88; - const id newScId(newScIndex, 0, 0, 0); - qrp.fund(newScId, 0); - - EXPECT_FALSE(state->hasAllowedSC(newScId)); - - // This test focuses on idempotency (repeat add/remove) while keeping authorization valid. - // Add twice: first should succeed, second should not change membership (return code may be SUCCESS or specific). - const auto add1 = qrp.addAllowedSC(state->owner(), newScIndex); - EXPECT_EQ(add1.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_TRUE(state->hasAllowedSC(newScId)); - - const auto add2 = qrp.addAllowedSC(state->owner(), newScIndex); - EXPECT_TRUE(state->hasAllowedSC(newScId)); - - // Remove twice: first should succeed, second should keep it removed (return code may be SUCCESS or specific). - const auto rem1 = qrp.removeAllowedSC(state->owner(), newScIndex); - EXPECT_EQ(rem1.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_FALSE(state->hasAllowedSC(newScId)); - - const auto rem2 = qrp.removeAllowedSC(state->owner(), newScIndex); - EXPECT_FALSE(state->hasAllowedSC(newScId)); -} - -TEST(ContractQReservePool, SendReserve_AllValidAndInvalidScenarios) -{ - ContractTestingQRP qrp; - QRPChecker* state = qrp.state(); - const id outsider(900, 0, 0, 0); - static constexpr uint64 recipientScIndex = 77; - const id recipientSc(recipientScIndex, 0, 0, 0); - static constexpr uint64 unknownScIndex = 999; - const id unknownSc(unknownScIndex, 0, 0, 0); - - qrp.fund(state->owner(), 0); - qrp.fund(outsider, 0); - qrp.fund(recipientSc, 0); - qrp.fund(unknownSc, 0); - - // ACCESS_DENIED: only owner can invoke SendReserve. - const QRP::SendReserve_output& denied = qrp.sendReserve(outsider, QRP_QTF_INDEX, 1); - EXPECT_EQ(denied.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); - - // SC_NOT_FOUND: owner invokes, but target SC is not whitelisted. - const QRP::SendReserve_output& notFound = qrp.sendReserve(state->owner(), unknownScIndex, 1); - EXPECT_EQ(notFound.returnCode, QRP::toReturnCode(QRP::EReturnCode::SC_NOT_FOUND)); - - // Add a new allowed SC to validate insufficient/success flows with a non-default recipient. - const QRP::AddAllowedSC_output& addSc = qrp.addAllowedSC(state->owner(), recipientScIndex); - EXPECT_EQ(addSc.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_TRUE(state->hasAllowedSC(recipientSc)); - - // INSUFFICIENT_RESERVE: reserve is 0. - const QRP::SendReserve_output& insufficientZeroReserve = qrp.sendReserve(state->owner(), recipientScIndex, 1); - EXPECT_EQ(insufficientZeroReserve.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE)); - EXPECT_EQ(qrp.balanceQrp(), 0); - EXPECT_EQ(qrp.balanceOf(recipientSc), 0); - - qrp.fundQrp(1000); - EXPECT_EQ(qrp.balanceQrp(), 1000); - - // INSUFFICIENT_RESERVE: requested amount is above available reserve. - const QRP::SendReserve_output& insufficientAboveReserve = qrp.sendReserve(state->owner(), recipientScIndex, 1001); - EXPECT_EQ(insufficientAboveReserve.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE)); - EXPECT_EQ(qrp.balanceQrp(), 1000); - EXPECT_EQ(qrp.balanceOf(recipientSc), 0); - - // SUCCESS: exact available reserve should be transferable. - const QRP::SendReserve_output& successExact = qrp.sendReserve(state->owner(), recipientScIndex, 1000); - EXPECT_EQ(successExact.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); - EXPECT_EQ(qrp.balanceQrp(), 0); - EXPECT_EQ(qrp.balanceOf(recipientSc), 1000); -} diff --git a/test/contract_qrwa.cpp b/test/contract_qrwa.cpp deleted file mode 100644 index 80ff77851..000000000 --- a/test/contract_qrwa.cpp +++ /dev/null @@ -1,1852 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" -#include "test_util.h" - -#define ENABLE_BALANCE_DEBUG 0 - -// Pseudo IDs (for testing only) - -// QMINE_ISSUER is is also the ADMIN_ADDRESS -static const id QMINE_ISSUER = ID( - _Q, _M, _I, _N, _E, _Q, _Q, _X, _Y, _B, _E, _G, _B, _H, _N, _S, - _U, _P, _O, _U, _Y, _D, _I, _Q, _K, _Z, _P, _C, _B, _P, _Q, _I, - _I, _H, _U, _U, _Z, _M, _C, _P, _L, _B, _P, _C, _C, _A, _I, _A, - _R, _V, _Z, _B, _T, _Y, _K, _G -); -static const id ADMIN_ADDRESS = QMINE_ISSUER; - -// temporary holder for the initial 150M QMINE supply -static const id TREASURY_HOLDER = id::randomValue(); - -// Addresses for governance-set fees -static const id FEE_ADDR_E = id::randomValue(); // Electricity fees address -static const id FEE_ADDR_M = id::randomValue(); // Maintenance fees address -static const id FEE_ADDR_R = id::randomValue(); // Reinvestment fees address - -// pseudo test address for QMINE developer -static const id QMINE_DEV_ADDR_TEST = ID( - _Z, _O, _X, _X, _I, _D, _C, _Z, _I, _M, _G, _C, _E, _C, _C, _F, - _A, _X, _D, _D, _C, _M, _B, _B, _X, _C, _D, _A, _Q, _J, _I, _H, - _G, _O, _O, _A, _T, _A, _F, _P, _S, _B, _F, _I, _O, _F, _O, _Y, - _E, _C, _F, _K, _U, _F, _P, _B -); - -// Test accounts for holders and users -static const id HOLDER_A = id::randomValue(); -static const id HOLDER_B = id::randomValue(); -static const id HOLDER_C = id::randomValue(); -static const id USER_D = id::randomValue(); // no-share user -static const id DESTINATION_ADDR = id::randomValue(); // dest for asset releases - -// Test QMINE Asset (using the random issuer for testing only) -static const Asset QMINE_ASSET = { QMINE_ISSUER, 297666170193ULL }; - -// Fees for dependent contracts -static constexpr uint64 QX_ISSUE_ASSET_FEE = 1000000000ull; -static constexpr uint64 QX_TRANSFER_FEE = 100ull; // Fee for transfering assets back to QX -static constexpr uint64 QX_MGT_TRANSFER_FEE = 0ull; // Fee for QX::TransferShareManagementRights -static constexpr sint64 QUTIL_STM1_FEE = 10LL; // QUTIL SendToManyV1 fee (QUTIL_STM1_INVOCATION_FEE) - - -enum qRWAFunctionIds -{ - QRWA_FUNC_GET_GOV_PARAMS = 1, - QRWA_FUNC_GET_GOV_POLL = 2, - QRWA_FUNC_GET_ASSET_RELEASE_POLL = 3, - QRWA_FUNC_GET_TREASURY_BALANCE = 4, - QRWA_FUNC_GET_DIVIDEND_BALANCES = 5, - QRWA_FUNC_GET_TOTAL_DISTRIBUTED = 6 -}; - -enum qRWAProcedureIds -{ - QRWA_PROC_DONATE_TO_TREASURY = 3, - QRWA_PROC_VOTE_GOV_PARAMS = 4, - QRWA_PROC_CREATE_ASSET_RELEASE_POLL = 5, - QRWA_PROC_VOTE_ASSET_RELEASE = 6, - QRWA_PROC_DEPOSIT_GENERAL_ASSET = 7, - QRWA_PROC_REVOKE_ASSET = 8, -}; - -enum QxProcedureIds -{ - QX_PROC_ISSUE_ASSET = 1, - QX_PROC_TRANSFER_SHARES = 2, - QX_PROC_TRANSFER_MANAGEMENT = 9 -}; - -enum QutilProcedureIds -{ - QUTIL_PROC_SEND_TO_MANY_V1 = 1 -}; - - -class ContractTestingQRWA : protected ContractTesting -{ - // Grant access to protected/private members for setup - friend struct QRWA; - -public: - ContractTestingQRWA() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QRWA); - callSystemProcedure(QRWA_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QUTIL); - callSystemProcedure(QUTIL_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QSWAP); - callSystemProcedure(QSWAP_CONTRACT_INDEX, INITIALIZE); - - // Custom Initialization for qRWA State - // (Overrides defaults from INITIALIZE() for testing purposes) - QRWA* state = getState(); - - // Fee addresses - // Note: We want to check these Fee Addresses separately, - // we use different addresses instead of same address as the Admin Address - state->mCurrentGovParams.electricityAddress = FEE_ADDR_E; - state->mCurrentGovParams.maintenanceAddress = FEE_ADDR_M; - state->mCurrentGovParams.reinvestmentAddress = FEE_ADDR_R; - } - - QRWA* getState() - { - return (QRWA*)contractStates[QRWA_CONTRACT_INDEX]; - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(QRWA_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QRWA_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - void endTick(bool expectSuccess = true) - { - callSystemProcedure(QRWA_CONTRACT_INDEX, END_TICK, expectSuccess); - } - - // manually reset the last payout time for testing. - void resetPayoutTime() - { - getState()->mLastPayoutTime = { 0, 0, 0, 0, 0, 0, 0 }; - } - - // QX/QUTIL Contract Wrappers - - void issueAsset(const id& issuer, uint64 assetName, sint64 shares) - { - QX::IssueAsset_input input{ assetName, shares, 0, 0 }; - QX::IssueAsset_output output; - increaseEnergy(issuer, QX_ISSUE_ASSET_FEE); - invokeUserProcedure(QX_CONTRACT_INDEX, QX_PROC_ISSUE_ASSET, input, output, issuer, QX_ISSUE_ASSET_FEE); - } - - // Transfers asset ownership and possession on QX. - void transferAsset(const id& from, const id& to, const Asset& asset, sint64 shares) - { - QX::TransferShareOwnershipAndPossession_input input{ asset.issuer, to, asset.assetName, shares }; - QX::TransferShareOwnershipAndPossession_output output; - increaseEnergy(from, QX_TRANSFER_FEE); - invokeUserProcedure(QX_CONTRACT_INDEX, QX_PROC_TRANSFER_SHARES, input, output, from, QX_TRANSFER_FEE); - } - - // Transfers management rights of an asset to another contract - void transferManagementRights(const id& from, const Asset& asset, sint64 shares, uint32 toContract) - { - QX::TransferShareManagementRights_input input{ asset, shares, toContract }; - QX::TransferShareManagementRights_output output; - increaseEnergy(from, QX_MGT_TRANSFER_FEE); - invokeUserProcedure(QX_CONTRACT_INDEX, QX_PROC_TRANSFER_MANAGEMENT, input, output, from, QX_MGT_TRANSFER_FEE); - } - - // Simulates a dividend payout from QLI pool using QUTIL::SendToManyV1. - void sendToMany(const id& from, const id& to, sint64 amount) - { - QUTIL::SendToManyV1_input input = {}; - input.dst0 = to; - input.amt0 = amount; - QUTIL::SendToManyV1_output output; - increaseEnergy(from, amount + QUTIL_STM1_FEE); - invokeUserProcedure(QUTIL_CONTRACT_INDEX, QUTIL_PROC_SEND_TO_MANY_V1, input, output, from, amount + QUTIL_STM1_FEE); - } - - // QRWA Procedure Wrappers - - uint64 donateToTreasury(const id& from, uint64 amount) - { - QRWA::DonateToTreasury_input input{ amount }; - QRWA::DonateToTreasury_output output; - invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_DONATE_TO_TREASURY, input, output, from, 0); - return output.status; - } - - uint64 voteGovParams(const id& from, const QRWA::QRWAGovParams& params) - { - QRWA::VoteGovParams_input input{ params }; - QRWA::VoteGovParams_output output; - invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_VOTE_GOV_PARAMS, input, output, from, 0); - return output.status; - } - - QRWA::CreateAssetReleasePoll_output createAssetReleasePoll(const id& from, const QRWA::CreateAssetReleasePoll_input& input) - { - QRWA::CreateAssetReleasePoll_output output; - memset(&output, 0, sizeof(output)); - invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_CREATE_ASSET_RELEASE_POLL, input, output, from, 0); - return output; - } - - uint64 voteAssetRelease(const id& from, uint64 pollId, uint64 option) - { - QRWA::VoteAssetRelease_input input{ pollId, option }; - QRWA::VoteAssetRelease_output output; - invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_VOTE_ASSET_RELEASE, input, output, from, 0); - return output.status; - } - - uint64 depositGeneralAsset(const id& from, const Asset& asset, uint64 amount) - { - QRWA::DepositGeneralAsset_input input{ asset, amount }; - QRWA::DepositGeneralAsset_output output; - invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_DEPOSIT_GENERAL_ASSET, input, output, from, 0); - return output.status; - } - - QRWA::RevokeAssetManagementRights_output revokeAssetManagementRights(const id& from, const Asset& asset, sint64 numberOfShares) - { - QRWA::RevokeAssetManagementRights_input input; - input.asset = asset; - input.numberOfShares = numberOfShares; - - QRWA::RevokeAssetManagementRights_output output; - memset(&output, 0, sizeof(output)); - - invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_REVOKE_ASSET, input, output, from, QRWA_RELEASE_MANAGEMENT_FEE); - return output; - } - - // QRWA Wrappers - - QRWA::QRWAGovParams getGovParams() - { - QRWA::GetGovParams_input input; - QRWA::GetGovParams_output output; - callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_GOV_PARAMS, input, output); - return output.params; - } - - QRWA::GetGovPoll_output getGovPoll(uint64 pollId) - { - QRWA::GetGovPoll_input input{ pollId }; - QRWA::GetGovPoll_output output; - callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_GOV_POLL, input, output); - return output; - } - - QRWA::GetAssetReleasePoll_output getAssetReleasePoll(uint64 pollId) - { - QRWA::GetAssetReleasePoll_input input{ pollId }; - QRWA::GetAssetReleasePoll_output output; - callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_ASSET_RELEASE_POLL, input, output); - return output; - } - - uint64 getTreasuryBalance() - { - QRWA::GetTreasuryBalance_input input; - QRWA::GetTreasuryBalance_output output; - callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_TREASURY_BALANCE, input, output); - return output.balance; - } - - QRWA::GetDividendBalances_output getDividendBalances() - { - QRWA::GetDividendBalances_input input; - QRWA::GetDividendBalances_output output; - callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_DIVIDEND_BALANCES, input, output); - return output; - } - - QRWA::GetTotalDistributed_output getTotalDistributed() - { - QRWA::GetTotalDistributed_input input; - QRWA::GetTotalDistributed_output output; - callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_TOTAL_DISTRIBUTED, input, output); - return output; - } - - void issueContractSharesHelper(unsigned int contractIndex, std::vector>& shares) - { - issueContractShares(contractIndex, shares); - } - - void createQswapPool(const id& source, uint64 assetName, sint64 fee) - { - QSWAP::CreatePool_input input{ assetName }; - QSWAP::CreatePool_output output; - invokeUserProcedure(QSWAP_CONTRACT_INDEX, 3, input, output, source, fee); - } - - void getQswapFees(QSWAP::Fees_output& output) - { - QSWAP::Fees_input input; - callFunction(QSWAP_CONTRACT_INDEX, 1, input, output); - } - - void runQswapEndTick() - { - callSystemProcedure(QSWAP_CONTRACT_INDEX, END_TICK); - } - -}; - -TEST(ContractQRWA, QswapDividend_PoolB) -{ - ContractTestingQRWA qrwa; - - // Create QRWA Shareholders - const id QRWA_SH1 = id::randomValue(); - increaseEnergy(QRWA_SH1, 100000000); - - std::vector> qrwaShares{ - {QRWA_SH1, NUMBER_OF_COMPUTORS} - }; - - qrwa.issueContractSharesHelper(QRWA_CONTRACT_INDEX, qrwaShares); - - //create QMINE Shareholders - const id QMINE_HOLDER = id::randomValue(); - increaseEnergy(QMINE_HOLDER, 100000000); - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); - qrwa.transferAsset(QMINE_ISSUER, QMINE_HOLDER, QMINE_ASSET, 1000000); - - - // Create QSWAP Shares and deposit them to QRWA - // QRWA owns 100 shares. Random holder owns the rest (576) - const id QSWAP_OTHER_HOLDER = id::randomValue(); - std::vector> qswapShares{ - {id(QRWA_CONTRACT_INDEX, 0, 0, 0), 100}, - {QSWAP_OTHER_HOLDER, 576} - }; - qrwa.issueContractSharesHelper(QSWAP_CONTRACT_INDEX, qswapShares); - - // now generate Revenue in QSWAP - - const id TRADER = id::randomValue(); - const id ASSET_ISSUER = id::randomValue(); - const uint64 ASSET_NAME = assetNameFromString("TSTCOIN"); - - increaseEnergy(TRADER, 10000000000); - increaseEnergy(ASSET_ISSUER, 10000000000); - - QSWAP::Fees_output qswapFees; - qrwa.getQswapFees(qswapFees); - - // issue asset on QX - qrwa.issueAsset(ASSET_ISSUER, ASSET_NAME, 1000000000); - - // Create Pool on QSWAP - // This generates 'poolCreationFee' for QSWAP shareholders, generating substantial revenue - qrwa.createQswapPool(ASSET_ISSUER, ASSET_NAME, qswapFees.poolCreationFee); - - // We skip AddLiquidity/Swap expectations as the pool creation fee - // alone is sufficient to test dividend routing - - uint64 totalShareholderRevenue = qswapFees.poolCreationFee; - - // Dividend Distribution - - // Get QRWA dividend balances BEFORE - auto qrwaDivsBefore = qrwa.getDividendBalances(); - EXPECT_EQ(qrwaDivsBefore.revenuePoolA, 0); - EXPECT_EQ(qrwaDivsBefore.revenuePoolB, 0); - - // Run END_TICK for QSWAP to distribute dividends - qrwa.runQswapEndTick(); - - // Calculate expected dividend for QRWA (100 shares) - // (TotalRevenue / 676) * 100 - uint64 expectedDividend = totalShareholderRevenue / NUMBER_OF_COMPUTORS * 100; - - // Get QRWA Dividend Balances AFTER - auto qrwaDivsAfter = qrwa.getDividendBalances(); - - // Verify Dividend Routing - // Pool A should be 0 (Only QUTIL transfers go here) - EXPECT_EQ(qrwaDivsAfter.revenuePoolA, 0); - - // Pool B should contain the dividend from QSWAP - EXPECT_EQ(qrwaDivsAfter.revenuePoolB, expectedDividend); -} - - -TEST(ContractQRWA, Initialization) -{ - ContractTestingQRWA qrwa; - - // Check gov params (set in test constructor) - auto params = qrwa.getGovParams(); - EXPECT_EQ(params.mAdminAddress, ADMIN_ADDRESS); - EXPECT_EQ(params.qmineDevAddress, QMINE_DEV_ADDR_TEST); - EXPECT_EQ(params.electricityAddress, FEE_ADDR_E); - EXPECT_EQ(params.maintenanceAddress, FEE_ADDR_M); - EXPECT_EQ(params.reinvestmentAddress, FEE_ADDR_R); - EXPECT_EQ(params.electricityPercent, 350); - EXPECT_EQ(params.maintenancePercent, 50); - EXPECT_EQ(params.reinvestmentPercent, 100); - - // Check pools and balances via public functions - EXPECT_EQ(qrwa.getTreasuryBalance(), 0); - auto divBalances = qrwa.getDividendBalances(); - EXPECT_EQ(divBalances.revenuePoolA, 0); - EXPECT_EQ(divBalances.revenuePoolB, 0); - EXPECT_EQ(divBalances.qmineDividendPool, 0); - EXPECT_EQ(divBalances.qrwaDividendPool, 0); - - auto distTotals = qrwa.getTotalDistributed(); - EXPECT_EQ(distTotals.totalQmineDistributed, 0); - EXPECT_EQ(distTotals.totalQRWADistributed, 0); -} - - -TEST(ContractQRWA, RevenueAccounting_POST_INCOMING_TRANSFER) -{ - ContractTestingQRWA qrwa; - - // Pool A from SC QUTIL - // We simulate this by calling QUTIL's SendToMany - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); - - auto divBalances = qrwa.getDividendBalances(); - EXPECT_EQ(divBalances.revenuePoolA, 1000000); - // We cannot test pool B as the test environment does not support standard transfer - // as noted in contract_testex.cpp - EXPECT_EQ(divBalances.revenuePoolB, 0); -} - -TEST(ContractQRWA, Governance_VoteGovParams_And_EndEpochCount) -{ - ContractTestingQRWA qrwa; - - // Issue QMINE, distribute, and run BEGIN_EPOCH - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 400000); // 40% - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, - { HOLDER_A, QX_CONTRACT_INDEX }), 400000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 600000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // 30% - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, - { HOLDER_B, QX_CONTRACT_INDEX }), 300000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 300000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 200000); // 20% - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, - { HOLDER_C, QX_CONTRACT_INDEX }), 200000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 100000); - - increaseEnergy(HOLDER_A, 1000000); - increaseEnergy(HOLDER_B, 1000000); - increaseEnergy(HOLDER_C, 1000000); - increaseEnergy(USER_D, 1000000); - - qrwa.beginEpoch(); - // Quorum should be 2/3 of 900,000 = 600,000 - - // Not a holder - EXPECT_EQ(qrwa.voteGovParams(USER_D, {}), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); - - // Invalid params (Admin NULL_ID) - QRWA::QRWAGovParams invalidParams = qrwa.getGovParams(); - invalidParams.mAdminAddress = NULL_ID; - EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, invalidParams), QRWA_STATUS_FAILURE_INVALID_INPUT); - - // Create new poll and vote for it - QRWA::QRWAGovParams paramsA = qrwa.getGovParams(); - paramsA.electricityPercent = 100; // Change one param - - EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, paramsA), QRWA_STATUS_SUCCESS); // Poll 0 - EXPECT_EQ(qrwa.voteGovParams(HOLDER_B, paramsA), QRWA_STATUS_SUCCESS); // Vote for Poll 0 - - // Change vote - QRWA::QRWAGovParams paramsB = qrwa.getGovParams(); - paramsB.maintenancePercent = 100; // Change another param - - EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, paramsB), QRWA_STATUS_SUCCESS); // Poll 1 - - // Mid-epoch sale - qrwa.transferAsset(HOLDER_B, USER_D, QMINE_ASSET, 150000); // B's balance is now 150k - EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, - { USER_D, QX_CONTRACT_INDEX }), 150000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, - { HOLDER_B, QX_CONTRACT_INDEX }), 150000); - - - // Accountant at END_EPOCH - qrwa.endEpoch(); - - // Check results: - // Poll 0 (ParamsA): HOLDER_B voted. Begin=300k, End=150k. VotingPower = 150k. - // Poll 1 (ParamsB): HOLDER_A voted. Begin=400k, End=400k. VotingPower = 400k. - // Total power = 900k. Quorum = 600k. - - // Poll 0 (ParamsA) failed. - auto poll0 = qrwa.getGovPoll(0); - EXPECT_EQ(poll0.status, QRWA_STATUS_SUCCESS); - EXPECT_EQ(poll0.proposal.score, 150000); - EXPECT_EQ(poll0.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); - - // Poll 1 (ParamsB) failed. - auto poll1 = qrwa.getGovPoll(1); - EXPECT_EQ(poll1.status, QRWA_STATUS_SUCCESS); - EXPECT_EQ(poll1.proposal.score, 400000); - EXPECT_EQ(poll1.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); - - // Params should be unchanged (still 50 from init) - EXPECT_EQ(qrwa.getGovParams().maintenancePercent, 50); - - // New Epoch: Test successful vote - qrwa.beginEpoch(); // New snapshot total: A(400k) + B(150k) + C(200k) = 750k. Quorum = 500k. - - // All holders vote for ParamsB - EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, paramsB), QRWA_STATUS_SUCCESS); // Creates Poll 2 - EXPECT_EQ(qrwa.voteGovParams(HOLDER_B, paramsB), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteGovParams(HOLDER_C, paramsB), QRWA_STATUS_SUCCESS); - - qrwa.endEpoch(); - - // Check results: - // Poll 2 (ParamsB): A(400k) + B(150k) + C(200k) = 750k vote power. - // Vote passes. - auto poll2 = qrwa.getGovPoll(2); - EXPECT_EQ(poll2.status, QRWA_STATUS_SUCCESS); - EXPECT_EQ(poll2.proposal.score, 750000); - EXPECT_EQ(poll2.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); - - // Verify params were updated - EXPECT_EQ(qrwa.getGovParams().maintenancePercent, 100); -} - -TEST(ContractQRWA, Governance_AssetReleasePolls) -{ - ContractTestingQRWA qrwa; - - increaseEnergy(HOLDER_A, 1000000); - increaseEnergy(HOLDER_B, 1000000); - increaseEnergy(USER_D, 1000000); - increaseEnergy(DESTINATION_ADDR, 1000000); - - // Issue QMINE, distribute, and run BEGIN_EPOCH - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000 + 1000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1001000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 700000); // 70% - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, - { HOLDER_A, QX_CONTRACT_INDEX }), 700000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 301000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // 30% - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, - { HOLDER_B, QX_CONTRACT_INDEX }), 300000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000); - // QMINE_ISSUER (ADMIN_ADDRESS) now holds 1000 - - // Give SC 1000 QMINE for its treasury - qrwa.transferManagementRights(QMINE_ISSUER, QMINE_ASSET, 1000, QRWA_CONTRACT_INDEX); - EXPECT_EQ(qrwa.donateToTreasury(QMINE_ISSUER, 1000), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.getTreasuryBalance(), 1000); - - qrwa.beginEpoch(); - - // Not Admin - QRWA::CreateAssetReleasePoll_input pollInput = {}; - pollInput.proposalName = id::randomValue(); - pollInput.asset = QMINE_ASSET; - pollInput.amount = 100; - pollInput.destination = DESTINATION_ADDR; - - auto pollOut = qrwa.createAssetReleasePoll(HOLDER_A, pollInput); // HOLDER_A is not admin - EXPECT_EQ(pollOut.status, QRWA_STATUS_FAILURE_NOT_AUTHORIZED); - - // Admin creates poll - pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); - EXPECT_EQ(pollOut.status, QRWA_STATUS_SUCCESS); - EXPECT_EQ(pollOut.proposalId, 0); - - // Not a holder - EXPECT_EQ(qrwa.voteAssetRelease(USER_D, 0, 1), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); - - // Holders vote - EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, 0, 1), QRWA_STATUS_SUCCESS); // 700k YES - EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_B, 0, 0), QRWA_STATUS_SUCCESS); // 300k NO - - // Add revenue to Pool A so the contract can pay the release fee - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); - EXPECT_EQ(qrwa.getDividendBalances().revenuePoolA, 1000000); - - // Count at end epoch (Pass) - qrwa.endEpoch(); - - auto poll = qrwa.getAssetReleasePoll(0); - EXPECT_EQ(poll.status, QRWA_STATUS_SUCCESS); - EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); // Should pass now - EXPECT_EQ(poll.proposal.votesYes, 700000); - EXPECT_EQ(poll.proposal.votesNo, 300000); - - // Verify balances - EXPECT_EQ(qrwa.getTreasuryBalance(), 900); // 1000 - 100 - EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }, - { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 100); // Should be 100 now - - // Count at end epoch (Fail Vote) - qrwa.beginEpoch(); - pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); // Poll 1 - EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, 1, 0), QRWA_STATUS_SUCCESS); // 700k NO - EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_B, 1, 1), QRWA_STATUS_SUCCESS); // 300k YES - qrwa.endEpoch(); - - poll = qrwa.getAssetReleasePoll(1); - EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); - EXPECT_EQ(qrwa.getTreasuryBalance(), 900); // Unchanged - - // Count at end epoch (Fail Execution - Insufficient) - qrwa.beginEpoch(); - pollInput.amount = 1000; // Try to release 1000 (only 900 left) - pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); // Poll 2 - EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, 2, 1), QRWA_STATUS_SUCCESS); // 700k YES - qrwa.endEpoch(); - - poll = qrwa.getAssetReleasePoll(2); - EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_PASSED_FAILED_EXECUTION); - EXPECT_EQ(qrwa.getTreasuryBalance(), 900); // Unchanged -} - -TEST(ContractQRWA, Governance_AssetRelease_FailAndRevoke) -{ - ContractTestingQRWA qrwa; - - const sint64 initialEnergy = 1000000000; - increaseEnergy(HOLDER_A, initialEnergy); - increaseEnergy(HOLDER_B, initialEnergy); - increaseEnergy(ADMIN_ADDRESS, initialEnergy + QX_ISSUE_ASSET_FEE); - increaseEnergy(DESTINATION_ADDR, initialEnergy); - - const sint64 treasuryAmount = 1000; - const sint64 voterShares = 1000000; - const sint64 releaseAmount = 500; - - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, voterShares + treasuryAmount); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 700000); - qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); - - // Give qRWA management rights over the treasury shares - qrwa.transferManagementRights(QMINE_ISSUER, QMINE_ASSET, treasuryAmount, QRWA_CONTRACT_INDEX); - - // Verify management rights were transferred - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QRWA_CONTRACT_INDEX }, - { QMINE_ISSUER, QRWA_CONTRACT_INDEX }), treasuryAmount); - - // Donate the shares to the treasury - EXPECT_EQ(qrwa.donateToTreasury(QMINE_ISSUER, treasuryAmount), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.getTreasuryBalance(), treasuryAmount); - - // Verify Revenue Pool A (for fees) is empty. - auto divBalances = qrwa.getDividendBalances(); - EXPECT_EQ(divBalances.revenuePoolA, 0); - - qrwa.beginEpoch(); - // Total voting power = 1,000,000 (HOLDER_A + HOLDER_B) - // Quorum = (1,000,000 * 2 / 3) + 1 = 666,667 - - QRWA::CreateAssetReleasePoll_input pollInput = {}; - pollInput.proposalName = id::randomValue(); - pollInput.asset = QMINE_ASSET; - pollInput.amount = releaseAmount; - pollInput.destination = DESTINATION_ADDR; - - // create poll - auto pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); - EXPECT_EQ(pollOut.status, QRWA_STATUS_SUCCESS); - uint64 pollId = pollOut.proposalId; - - // HOLDER_A votes YES, passing the poll (700k > 666k quorum) - EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, pollId, 1), QRWA_STATUS_SUCCESS); - - qrwa.endEpoch(); - - // Check poll status - // It should have passed the vote but failed execution (due to lack of 100 QUs fee for QX management transfer) - auto poll = qrwa.getAssetReleasePoll(pollId); - EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_PASSED_FAILED_EXECUTION); - EXPECT_EQ(poll.proposal.votesYes, 700000); - - // Check SC asset state - // Asserts the INTERNAL counter is now decreased - EXPECT_EQ(qrwa.getTreasuryBalance(), treasuryAmount - releaseAmount); // 1000 - 500 = 500 - - // the SC balance is decreased - sint64 scOwnedBalance = numberOfShares(QMINE_ASSET, - { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }, - { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }); - EXPECT_EQ(scOwnedBalance, treasuryAmount - releaseAmount); // 1000 - 500 = 500 - - // DESTINATION_ADDR should now owns the shares, but they are MANAGED by qRWA - sint64 destManagedByQrwa = numberOfShares(QMINE_ASSET, - { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }, - { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }); - EXPECT_EQ(destManagedByQrwa, releaseAmount); // 500 shares are stuck - - // DESTINATION_ADDR should have 0 shares managed by QX - sint64 destManagedByQx = numberOfShares(QMINE_ASSET, - { DESTINATION_ADDR, QX_CONTRACT_INDEX }, - { DESTINATION_ADDR, QX_CONTRACT_INDEX }); - EXPECT_EQ(destManagedByQx, 0); - - // Test Revoke - qrwa.beginEpoch(); - - // Fund DESTINATION_ADDR with the fee for the revoke procedure - increaseEnergy(DESTINATION_ADDR, QRWA_RELEASE_MANAGEMENT_FEE); - sint64 destBalanceBeforeRevoke = getBalance(DESTINATION_ADDR); - - // DESTINATION_ADDR calls revokeAssetManagementRights - auto revokeOut = qrwa.revokeAssetManagementRights(DESTINATION_ADDR, QMINE_ASSET, releaseAmount); - - // check the outcome - EXPECT_EQ(revokeOut.status, QRWA_STATUS_SUCCESS); - EXPECT_EQ(revokeOut.transferredNumberOfShares, releaseAmount); - - // check final on-chain asset state - // DESTINATION_ADDR should be no longer have shares managed by qRWA - destManagedByQrwa = numberOfShares(QMINE_ASSET, - { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }, - { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }); - EXPECT_EQ(destManagedByQrwa, 0); - - // DESTINATION_ADDR's shares should now be managed by QX - destManagedByQx = numberOfShares(QMINE_ASSET, - { DESTINATION_ADDR, QX_CONTRACT_INDEX }, - { DESTINATION_ADDR, QX_CONTRACT_INDEX }); - EXPECT_EQ(destManagedByQx, releaseAmount); - - // check if the fee was paid by the user - sint64 destBalanceAfterRevoke = getBalance(DESTINATION_ADDR); - EXPECT_EQ(destBalanceAfterRevoke, destBalanceBeforeRevoke - QRWA_RELEASE_MANAGEMENT_FEE); - - // Critical check: - // Verify that the fee sent to the SC was NOT permanently added to Pool B. - // The POST_INCOMING_TRANSFER adds 100 QU to Pool B. - // The procedure executes, spends 100 QU to QX, and logic must subtract 100 from Pool B. - // Net result for Pool B must be 0. - auto finalDivBalances = qrwa.getDividendBalances(); - EXPECT_EQ(finalDivBalances.revenuePoolB, 0); -} - -TEST(ContractQRWA, Treasury_Donation) -{ - ContractTestingQRWA qrwa; - - // Issue QMINE to the temporary treasury holder - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 150000000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 150000000); - - qrwa.transferAsset(QMINE_ISSUER, TREASURY_HOLDER, QMINE_ASSET, 150000000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { TREASURY_HOLDER, QX_CONTRACT_INDEX }, - { TREASURY_HOLDER, QX_CONTRACT_INDEX }), 150000000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 0); - - increaseEnergy(TREASURY_HOLDER, 1000000); - - // Fail (No Management Rights) - EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, 1000), QRWA_STATUS_FAILURE_INSUFFICIENT_BALANCE); - - // Success (With Management Rights) - // Give SC management rights - qrwa.transferManagementRights(TREASURY_HOLDER, QMINE_ASSET, 150000000, QRWA_CONTRACT_INDEX); - - // Verify rights - sint64 managedBalance = numberOfShares(QMINE_ASSET, - { TREASURY_HOLDER, QRWA_CONTRACT_INDEX }, - { TREASURY_HOLDER, QRWA_CONTRACT_INDEX }); - EXPECT_EQ(managedBalance, 150000000); - - // Donate - EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, 150000000), QRWA_STATUS_SUCCESS); - - // Verify treasury balance in SC - EXPECT_EQ(qrwa.getTreasuryBalance(), 150000000); - - // Verify SC now owns the shares - sint64 scOwnedBalance = numberOfShares(QMINE_ASSET, - { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }, - { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }); - EXPECT_EQ(scOwnedBalance, 150000000); -} - -TEST(ContractQRWA, Payout_FullDistribution) -{ - ContractTestingQRWA qrwa; - - // Issue QMINE, distribute, and run BEGIN_EPOCH - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000000); - - increaseEnergy(HOLDER_A, 1000000); - increaseEnergy(HOLDER_B, 1000000); - increaseEnergy(HOLDER_C, 1000000); - increaseEnergy(USER_D, 1000000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 200000); // Holder A - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, - { HOLDER_A, QX_CONTRACT_INDEX }), 200000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 800000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // Holder B - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, - { HOLDER_B, QX_CONTRACT_INDEX }), 300000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 500000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 100000); // Holder C - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, - { HOLDER_C, QX_CONTRACT_INDEX }), 100000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 400000); - - qrwa.beginEpoch(); - // mTotalQmineBeginEpoch = 1,000,000 - - // Mid-epoch transfers - qrwa.transferAsset(HOLDER_A, USER_D, QMINE_ASSET, 50000); // Holder A ends with 150k - EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, - { USER_D, QX_CONTRACT_INDEX }), 50000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, - { HOLDER_A, QX_CONTRACT_INDEX }), 150000); - - qrwa.transferAsset(HOLDER_C, USER_D, QMINE_ASSET, 100000); // Holder C ends with 0 - EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, - { USER_D, QX_CONTRACT_INDEX }), 150000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, - { HOLDER_C, QX_CONTRACT_INDEX }), 0); - - // Deposit revenue - // Pool A (from SC) - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); - - // Pool B (from User) - Untestable. We will proceed using only Pool A. - - qrwa.endEpoch(); - - // Set time to payout day - etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 7; // A Friday - etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; - - // Use helper to reset payout time - qrwa.resetPayoutTime(); // Reset time to allow payout - - // Call END_TICK to trigger DistributeRewards - qrwa.endTick(); - - // Verification - // Fees: Pool A = 1M - // Elec (35%) = 350,000 - // Maint (5%) = 50,000 - // Reinv (10%) = 100,000 - // Total Fees = 500,000 - EXPECT_EQ(getBalance(FEE_ADDR_E), 350000); - EXPECT_EQ(getBalance(FEE_ADDR_M), 50000); - EXPECT_EQ(getBalance(FEE_ADDR_R), 100000); - - // Distribution Pool - // Y_revenue = 1,000,000 - 500,000 = 500,000 - // totalDistribution = 500,000 (Y) + 0 (B) = 500,000 - // mQmineDividendPool = 500k * 90% = 450,000 - // mQRWADividendPool = 500k * 10% = 50,000 - - // qRWA Payout (50,000 QUs) - uint64 qrwaPerShare = 50000 / NUMBER_OF_COMPUTORS; // 73 - auto distTotals = qrwa.getTotalDistributed(); - EXPECT_EQ(distTotals.totalQRWADistributed, qrwaPerShare * NUMBER_OF_COMPUTORS); // 73 * 676 = 49328 - - // QMINE Payout (450,000 QUs) - // mPayoutTotalQmineBegin = 1,000,000 - - // Eligible Balances: - // H1: min(200k, 150k) = 150,000 - // H2: min(300k, 300k) = 300,000 - // H3: min(100k, 0) = 0 - // Issuer: min(400k, 400k) = 400,000 - // Total Eligible = 850,000 - - // Payouts: - // H1 Payout: (150,000 * 450,000) / 1,000,000 = 67,500 - // H2 Payout: (300,000 * 450,000) / 1,000,000 = 135,000 - // H3 Payout: 0 - // Issuer Payout: (400,000 * 450,000) / 1,000,000 = 180,000 - // Total Eligible Paid = 67,500 + 135,000 + 180,000 = 382,500 - // QMINE_DEV Payout (Remainder) = 450,000 - 382,500 = 67,500 - - EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 67500); - EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 135000); - EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 0); - EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 67500); - - // Re-check balances - EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 135000); - - // Check pools are empty (or contain only dust from integer division) - auto divBalances = qrwa.getDividendBalances(); - EXPECT_EQ(divBalances.revenuePoolA, 0); - EXPECT_EQ(divBalances.revenuePoolB, 0); - EXPECT_EQ(divBalances.qmineDividendPool, 0); - EXPECT_EQ(divBalances.qrwaDividendPool, 50000 - (qrwaPerShare * NUMBER_OF_COMPUTORS)); // Dust -} - -TEST(ContractQRWA, Payout_SnapshotLogic) -{ - ContractTestingQRWA qrwa; - - // Give energy to all participants - increaseEnergy(QMINE_ISSUER, 1000000000); - increaseEnergy(HOLDER_A, 1000000); - increaseEnergy(HOLDER_B, 1000000); - increaseEnergy(HOLDER_C, 1000000); - increaseEnergy(USER_D, 1000000); - - // Issue 3500 QMINE - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 3500); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, { QMINE_ISSUER, QX_CONTRACT_INDEX }), 3500); - - // Epoch 1 Setup: Distribute initial shares - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 1000); - qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 1000); - qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 1000); - // QMINE_ISSUER keeps 500 - - qrwa.beginEpoch(); - // Snapshot (Begin Epoch 1): - // Total: 3500 (A, B, C, Issuer) - // A: 1000 - // B: 1000 - // C: 1000 - // D: 0 - // Issuer: 500 - - // Epoch 1 Mid-Epoch Transfers - qrwa.transferAsset(HOLDER_A, USER_D, QMINE_ASSET, 500); // A: 500, D: 500 - qrwa.transferAsset(HOLDER_B, USER_D, QMINE_ASSET, 1000); // B: 0, D: 1500 - qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 500); // C: 1500, Issuer: 0 - - // Deposit 1M QUs into Pool A - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); - - qrwa.endEpoch(); - // Payout Snapshots (Epoch 1): - // mPayoutTotalQmineBegin: 3500 - // Eligible: - // A: min(1000, 500) = 500 - // B: min(1000, 0) = 0 - // C: min(1000, 1500) = 1000 - // D: (not in begin map) = 0 - // Issuer: min(500, 0) = 0 - // Total Eligible: 1500 - - // Payout Calculation (Epoch 1): - // Pool A: 1,000,000 -> Fees (50%) = 500,000 -> Y_revenue = 500,000 - // mQmineDividendPool (90%): 450,000 - // mQRWADividendPool (10%): 50,000 - - // Payouts: - // A: (500 * 450,000) / 3,500 = 64,285 - // B: 0 - // C: (1000 * 450,000) / 3,500 = 128,571 - // D: 0 - // Issuer: 0 - // totalEligiblePaid = 192,856 - // movedSharesPayout (QMINE_DEV) = 450,000 - 192,856 = 257,144 - - // Trigger Payout - etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 14; // Next Friday - etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; - qrwa.resetPayoutTime(); - qrwa.endTick(); - - // Verify Payout 1 - EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 64285); - EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 0); - EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 128571); - EXPECT_EQ(getBalance(USER_D), 1000000 + 0); - EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 257144); - - // Check C's balance again - EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 128571); - - - // Epoch 2 - qrwa.beginEpoch(); - // Snapshot (Begin Epoch 2): - // Total: 3500 - // A: 500, B: 0, C: 1500, D: 1500, Issuer: 0 - - // Epoch 2 Mid-Epoch Transfers - qrwa.transferAsset(USER_D, HOLDER_A, QMINE_ASSET, 500); // A: 1000, D: 1000 - qrwa.transferAsset(HOLDER_C, HOLDER_B, QMINE_ASSET, 1000); // C: 500, B: 1000 - - // Deposit 1M QUs into Pool A - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); - - qrwa.endEpoch(); - // Snapshot (End Epoch 2): - // A: 1000, B: 1000, C: 500, D: 1000, Issuer: 0 - // - // Payout Snapshots (Epoch 2): - // mPayoutTotalQmineBegin: 3500 - // Eligible: - // A: min(500, 1000) = 500 - // B: min(0, 1000) = 0 - // C: min(1500, 500) = 500 - // D: min(1500, 1000) = 1000 - // Total Eligible: 2000 - - // Payout Calculation (Epoch 2): - // Pool A: 1,000,000 -> Fees (50%) = 500,000 -> Y_revenue = 500,000 - // mQmineDividendPool (90%): 450,000 - // Payouts: - // A: (500 * 450,000) / 3,500 = 64,285 - // B: 0 - // C: (500 * 450,000) / 3,500 = 64,285 - // D: (1000 * 450,000) / 3,500 = 128,571 - // totalEligiblePaid = 257,141 - // movedSharesPayout (QMINE_DEV) = 450,000 - 257,141 = 192,859 - - // Trigger Payout 2 - etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 21; // Next Friday - etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; - qrwa.resetPayoutTime(); - qrwa.endTick(); - - // Verify Payout 2 (Cumulative) - // A: Base + payout1 + payout2 - EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 64285 + 64285); - // B: Base + payout1 + payout2 - EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 0 + 0); - // C: Base + payout1 + payout2 - EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 128571 + 64285); - // D: Base + payout1 + payout2 - EXPECT_EQ(getBalance(USER_D), 1000000 + 0 + 128571); - // QMINE dev: payout1 + payout2 - EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 257144 + 192859); -} - -TEST(ContractQRWA, Payout_FullDistribution2) -{ - ContractTestingQRWA qrwa; - - // Issue QMINE, distribute, and run BEGIN_EPOCH - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000000); - - increaseEnergy(HOLDER_A, 1000000); - increaseEnergy(HOLDER_B, 1000000); - increaseEnergy(HOLDER_C, 1000000); - increaseEnergy(USER_D, 1000000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 200000); // Holder A - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, - { HOLDER_A, QX_CONTRACT_INDEX }), 200000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 800000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // Holder B - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, - { HOLDER_B, QX_CONTRACT_INDEX }), 300000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 500000); - - qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 100000); // Holder C - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, - { HOLDER_C, QX_CONTRACT_INDEX }), 100000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, - { QMINE_ISSUER, QX_CONTRACT_INDEX }), 400000); - - qrwa.beginEpoch(); - // mTotalQmineBeginEpoch = 1,000,000 (A:200k, B:300k, C:100k, Issuer:400k) - - // Mid-epoch transfers - qrwa.transferAsset(HOLDER_A, USER_D, QMINE_ASSET, 50000); // Holder A ends with 150k - EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, - { USER_D, QX_CONTRACT_INDEX }), 50000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, - { HOLDER_A, QX_CONTRACT_INDEX }), 150000); - - qrwa.transferAsset(HOLDER_C, USER_D, QMINE_ASSET, 100000); // Holder C ends with 0 - EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, - { USER_D, QX_CONTRACT_INDEX }), 150000); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, - { HOLDER_C, QX_CONTRACT_INDEX }), 0); - - // Deposit revenue - // Pool A (from SC) - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 3000000); // Increased revenue - - // Pool B (from User): Untestable. We will proceed using only Pool A. - - qrwa.endEpoch(); - - // Set time to payout day - etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 7; // A Friday - etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; - - // Use helper to reset payout time - qrwa.resetPayoutTime(); // Reset time to allow payout - - // Call END_TICK to trigger DistributeRewards - qrwa.endTick(); - - // Verification - // Fees: Pool A = 3M - // Elec (35%) = 1,050,000 - // Maint (5%) = 150,000 - // Reinv (10%) = 300,000 - // Total Fees = 1,500,000 - EXPECT_EQ(getBalance(FEE_ADDR_E), 1050000); - EXPECT_EQ(getBalance(FEE_ADDR_M), 150000); - EXPECT_EQ(getBalance(FEE_ADDR_R), 300000); - - // Distribution Pool - // Y_revenue = 3,000,000 - 1,500,000 = 1,500,000 - // totalDistribution = 1,500,000 (Y) + 0 (B) = 1,500,000 - // mQmineDividendPool = 1.5M * 90% = 1,350,000 - // mQRWADividendPool = 1.5M * 10% = 150,000 - - // qRWA Payout (150,000 QUs) - uint64 qrwaPerShare = 150000 / NUMBER_OF_COMPUTORS; // 150000 / 676 = 221 - auto distTotals = qrwa.getTotalDistributed(); - EXPECT_EQ(distTotals.totalQRWADistributed, qrwaPerShare * NUMBER_OF_COMPUTORS); // 221 * 676 = 149416 - - // QMINE Payout (1,350,000 QUs) - // mPayoutTotalQmineBegin = 1,000,000 (A:200k, B:300k, C:100k, Issuer:400k) - - // Eligible: - // H1: min(200k, 150k) = 150,000 - // H2: min(300k, 300k) = 300,000 - // H3: min(100k, 0) = 0 - // Issuer: min(400k, 400k) = 400,000 - // Total Eligible = 850,000 - - // Payouts: - // H1 Payout: (150,000 * 1,350,000) / 1,000,000 = 202,500 - // H2 Payout: (300,000 * 1,350,000) / 1,000,000 = 405,000 - // H3 Payout: 0 - // Issuer Payout: (400,000 * 1,350,000) / 1,000,000 = 540,000 - // Total Eligible Paid = 202,500 + 405,000 + 540,000 = 1,147,500 - // QMINE dev Payout (Remainder) = 1,350,000 - 1,147,500 = 202,500 - - EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 202500); - EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 405000); - EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 0); - EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 202500); - - // Re-check B's balance - EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 405000); - - - // Check pools are empty (or contain only dust from integer division) - auto divBalances = qrwa.getDividendBalances(); - EXPECT_EQ(divBalances.revenuePoolA, 0); - EXPECT_EQ(divBalances.revenuePoolB, 0); - EXPECT_EQ(divBalances.qmineDividendPool, 0); // QMINE dev gets the remainder - EXPECT_EQ(divBalances.qrwaDividendPool, 150000 - (qrwaPerShare * NUMBER_OF_COMPUTORS)); // Dust (584) -} - -TEST(ContractQRWA, FullScenario_DividendsAndGovernance) -{ - ContractTestingQRWA qrwa; - - /* --- SETUP --- */ - - etalonTick.year = 25; // 2025 - etalonTick.month = 11; // November - etalonTick.day = 7; // 7th (Friday) - etalonTick.hour = 12; - etalonTick.minute = 1; - etalonTick.second = 0; - etalonTick.millisecond = 0; - - // Helper to handle month rollovers for this test - auto advanceTime7Days = [&]() - { - etalonTick.day += 7; - // Simple logic for Nov/Dec 2025 - if (etalonTick.month == 11 && etalonTick.day > 30) { - etalonTick.day -= 30; - etalonTick.month++; - } - else if (etalonTick.month == 12 && etalonTick.day > 31) { - etalonTick.day -= 31; - etalonTick.month = 1; - etalonTick.year++; - } - }; - - // Constants - const sint64 TOTAL_SUPPLY = 1000000000000LL; // 1,000,000,000,000 = 1 Trillion - const sint64 TREASURY_INIT = 150000000000LL; // 150 Billion - const sint64 SHAREHOLDERS_TOTAL = 850000000000LL; // 850 Billion - const sint64 SHAREHOLDER_AMT = SHAREHOLDERS_TOTAL / 5; // 170 Billion each - const sint64 REVENUE_AMT = 10000000LL; // 10 Million QUs per epoch revenue - - // Known Pool Amounts derived from REVENUE_AMT and 50% total fees - // Revenue 10M -> Fees 5M -> Net 5M - const sint64 QMINE_POOL_AMT = 4500000LL; // 90% of 5M - const sint64 QRWA_POOL_AMT_BASE = 500000LL; // 10% of 5M - - const sint64 QRWA_TOTAL_SHARES = 676LL; - - // Track dust for qRWA pool to calculate accurate rates per epoch - sint64 currentQrwaDust = 0; - sint64 currentQXReleaseFee = 0; - - auto getQrwaRateForEpoch = [&](sint64 poolAmount) -> sint64 { - sint64 totalPool = poolAmount + currentQrwaDust; - sint64 rate = totalPool / QRWA_TOTAL_SHARES; - currentQrwaDust = totalPool % QRWA_TOTAL_SHARES; // Update dust for next epoch - return rate; - }; - - // Entities - const id S1 = id::randomValue(); // Hybrid: Holds QMINE + qRWA shares - const id S2 = id::randomValue(); // Control QMINE: Holds only QMINE - const id S3 = id::randomValue(); // QMINE only - const id S4 = id::randomValue(); // QMINE only - const id S5 = id::randomValue(); // QMINE only - const id Q1 = id::randomValue(); // Control qRWA: Holds only qRWA shares - const id Q2 = id::randomValue(); // qRWA only - - // Energy Funding - increaseEnergy(QMINE_ISSUER, QX_ISSUE_ASSET_FEE * 2 + 100000000); - increaseEnergy(TREASURY_HOLDER, 100000000); - increaseEnergy(S1, 100000000); - increaseEnergy(S2, 100000000); - increaseEnergy(S3, 100000000); - increaseEnergy(S4, 100000000); - increaseEnergy(S5, 100000000); - increaseEnergy(Q1, 100000000); - increaseEnergy(Q2, 100000000); - increaseEnergy(DESTINATION_ADDR, 1000000); - increaseEnergy(ADMIN_ADDRESS, 1000000); - - // Issue QMINE - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, TOTAL_SUPPLY); - - // Distribute to Treasury Holder - qrwa.transferAsset(QMINE_ISSUER, TREASURY_HOLDER, QMINE_ASSET, TREASURY_INIT); - - // Distribute to 5 Shareholders (170B each) - qrwa.transferAsset(QMINE_ISSUER, S1, QMINE_ASSET, SHAREHOLDER_AMT); - qrwa.transferAsset(QMINE_ISSUER, S2, QMINE_ASSET, SHAREHOLDER_AMT); - qrwa.transferAsset(QMINE_ISSUER, S3, QMINE_ASSET, SHAREHOLDER_AMT); - qrwa.transferAsset(QMINE_ISSUER, S4, QMINE_ASSET, SHAREHOLDER_AMT); - qrwa.transferAsset(QMINE_ISSUER, S5, QMINE_ASSET, SHAREHOLDER_AMT); - - // Issue and Distribute qrwa Contract Shares - std::vector> qrwaShares{ - {S1, 200}, - {Q1, 200}, - {Q2, 276} - }; - issueContractShares(QRWA_CONTRACT_INDEX, qrwaShares); - - // Snapshot balances - std::map prevBalances; - auto snapshotBalances = [&]() { - prevBalances[S1] = getBalance(S1); - prevBalances[S2] = getBalance(S2); - prevBalances[S3] = getBalance(S3); - prevBalances[S4] = getBalance(S4); - prevBalances[S5] = getBalance(S5); - prevBalances[Q1] = getBalance(Q1); - prevBalances[Q2] = getBalance(Q2); - prevBalances[DESTINATION_ADDR] = getBalance(DESTINATION_ADDR); - }; - snapshotBalances(); - - // Helper to calculate exact QMINE payout matching contract logic - // Payout = (EligibleBalance * DividendPool) / PayoutBase - auto calculateQminePayout = [&](sint64 balance, sint64 payoutBase, sint64 poolAmount) -> sint64 { - if (payoutBase == 0) return 0; - // Contract uses: div((uint128)balance * pool, totalEligible) - // We mimic that integer math here - uint128 res = (uint128)balance * (uint128)poolAmount; - res = res / (uint128)payoutBase; - return (sint64)res.low; - }; - - // Helper that uses the calculated rate for the current epoch - auto calculateQrwaPayout = [&](sint64 shares, sint64 currentRate) -> sint64 { - return shares * currentRate; - }; - -#if ENABLE_BALANCE_DEBUG - auto print_balances = [&]() - { - std::cout << "\n--- Current Balances ---" << std::endl; - std::cout << "S1: " << getBalance(S1) << std::endl; - std::cout << "S2: " << getBalance(S2) << std::endl; - std::cout << "S3: " << getBalance(S3) << std::endl; - std::cout << "S4: " << getBalance(S4) << std::endl; - std::cout << "S5: " << getBalance(S5) << std::endl; - std::cout << "Q1: " << getBalance(Q1) << std::endl; - std::cout << "Q2: " << getBalance(Q2) << std::endl; - std::cout << "Dest: " << getBalance(DESTINATION_ADDR) << std::endl; - std::cout << "Treasury: " << getBalance(TREASURY_HOLDER) << std::endl; - std::cout << "Dev: " << getBalance(QMINE_DEV_ADDR_TEST) << std::endl; - std::cout << "------------------------\n" << std::endl; - }; - - std::cout << "PRE-EPOCH 1\n"; - print_balances(); -#endif - // epoch 1 - qrwa.beginEpoch(); - - //Shareholders Exchange - qrwa.transferAsset(S1, S2, QMINE_ASSET, 10000000000LL); - qrwa.transferAsset(S3, S4, QMINE_ASSET, 5000000000LL); - - // Treasury Donation - qrwa.transferManagementRights(TREASURY_HOLDER, QMINE_ASSET, 10, QRWA_CONTRACT_INDEX); - EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, 10), QRWA_STATUS_SUCCESS); - - //Revenue - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - - qrwa.endEpoch(); - - // Checks Ep 1 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << "END-EPOCH 1\n"; - print_balances(); -#endif - - // Contract holds 10 shares. Base = Total Supply - 10 - sint64 payoutBaseEp1 = TOTAL_SUPPLY - 10; - sint64 qrwaRateEp1 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); // Standard pool for Ep 1 - - sint64 divS1 = calculateQminePayout(160000000000LL, payoutBaseEp1, QMINE_POOL_AMT); - sint64 divS2 = calculateQminePayout(170000000000LL, payoutBaseEp1, QMINE_POOL_AMT); - sint64 divS3 = calculateQminePayout(165000000000LL, payoutBaseEp1, QMINE_POOL_AMT); - sint64 divS4 = calculateQminePayout(170000000000LL, payoutBaseEp1, QMINE_POOL_AMT); - sint64 divS5 = calculateQminePayout(170000000000LL, payoutBaseEp1, QMINE_POOL_AMT); - - sint64 divQS1 = calculateQrwaPayout(200, qrwaRateEp1); - sint64 divQQ1 = calculateQrwaPayout(200, qrwaRateEp1); - sint64 divQQ2 = calculateQrwaPayout(276, qrwaRateEp1); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); - - snapshotBalances(); - -#if ENABLE_BALANCE_DEBUG - std::cout << "PRE-EPOCH 2\n"; - print_balances(); -#endif - - // epoch 2 - qrwa.beginEpoch(); - - // Treasury Donation (Remaining) - sint64 treasuryRemaining = TREASURY_INIT - 10; - qrwa.transferManagementRights(TREASURY_HOLDER, QMINE_ASSET, treasuryRemaining, QRWA_CONTRACT_INDEX); - EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, treasuryRemaining), QRWA_STATUS_SUCCESS); - - // Exchange - qrwa.transferAsset(S1, S2, QMINE_ASSET, 10000000000LL); - qrwa.transferAsset(S2, S3, QMINE_ASSET, 10000000000LL); - qrwa.transferAsset(S3, S4, QMINE_ASSET, 10000000000LL); - qrwa.transferAsset(S4, S5, QMINE_ASSET, 10000000000LL); - - // Revenue - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - - // Release Poll - QRWA::CreateAssetReleasePoll_input pollInput; - pollInput.proposalName = id::randomValue(); - pollInput.asset = QMINE_ASSET; - pollInput.amount = 1000; - pollInput.destination = DESTINATION_ADDR; - - auto pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); - uint64 pollIdEp2 = pollOut.proposalId; - - EXPECT_EQ(qrwa.voteAssetRelease(S1, pollIdEp2, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S2, pollIdEp2, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S3, pollIdEp2, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S4, pollIdEp2, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S5, pollIdEp2, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(Q1, pollIdEp2, 1), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); - - qrwa.endEpoch(); - - // Checks Ep 2 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << "END-EPOCH 2\n"; - print_balances(); -#endif - - auto pollResultEp2 = qrwa.getAssetReleasePoll(pollIdEp2); - EXPECT_EQ(pollResultEp2.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 1000); - - // Calculate Pools based on Revenue - 100 QU Fee - sint64 netRevenueEp2 = REVENUE_AMT - 100; - sint64 feeAmtEp2 = (netRevenueEp2 * 500) / 1000; // 50% fees - sint64 distributableEp2 = netRevenueEp2 - feeAmtEp2; - sint64 qminePoolEp2 = (distributableEp2 * 900) / 1000; - sint64 qrwaPoolEp2 = distributableEp2 - qminePoolEp2; - - // Correct Base: TOTAL_SUPPLY - 10 (Shares held by SC at START of epoch) - sint64 payoutBaseEp2 = TOTAL_SUPPLY - 10; - - sint64 qrwaRateEp2 = getQrwaRateForEpoch(qrwaPoolEp2); - - divS1 = calculateQminePayout(150000000000LL, payoutBaseEp2, qminePoolEp2); - divS2 = calculateQminePayout(180000000000LL, payoutBaseEp2, qminePoolEp2); - divS3 = calculateQminePayout(165000000000LL, payoutBaseEp2, qminePoolEp2); - divS4 = calculateQminePayout(175000000000LL, payoutBaseEp2, qminePoolEp2); - divS5 = calculateQminePayout(170000000000LL, payoutBaseEp2, qminePoolEp2); - - divQS1 = calculateQrwaPayout(200, qrwaRateEp2); - divQQ1 = calculateQrwaPayout(200, qrwaRateEp2); - divQQ2 = calculateQrwaPayout(276, qrwaRateEp2); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); - - snapshotBalances(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " PRE-EPOCH 3\n"; - print_balances(); -#endif - - // epoch 3 - qrwa.beginEpoch(); - - // Exchange - qrwa.transferAsset(S1, S2, QMINE_ASSET, 5000000000LL); - qrwa.transferAsset(S2, S3, QMINE_ASSET, 5000000000LL); - qrwa.transferAsset(S3, S4, QMINE_ASSET, 5000000000LL); - qrwa.transferAsset(S4, S5, QMINE_ASSET, 5000000000LL); - - // Revenue - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - - // Release Poll - pollInput.amount = 500; - pollInput.proposalName = id::randomValue(); - pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); - uint64 pollIdEp3 = pollOut.proposalId; - - EXPECT_EQ(qrwa.voteAssetRelease(S1, pollIdEp3, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S2, pollIdEp3, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S3, pollIdEp3, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S4, pollIdEp3, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S5, pollIdEp3, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(Q1, pollIdEp3, 1), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); - - // Gov Vote - QRWA::QRWAGovParams newParams = qrwa.getGovParams(); - newParams.electricityPercent = 300; - newParams.maintenancePercent = 100; - - newParams.mAdminAddress = ADMIN_ADDRESS; - newParams.qmineDevAddress = QMINE_DEV_ADDR_TEST; - newParams.electricityAddress = FEE_ADDR_E; - newParams.maintenanceAddress = FEE_ADDR_M; - newParams.reinvestmentAddress = FEE_ADDR_R; - - EXPECT_EQ(qrwa.voteGovParams(S1, newParams), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteGovParams(S2, newParams), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteGovParams(S3, newParams), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteGovParams(S4, newParams), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteGovParams(S5, newParams), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteGovParams(Q1, newParams), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); - - qrwa.endEpoch(); - - // Checks Ep 3 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " END-EPOCH 3\n"; - print_balances(); -#endif - - auto pollResultEp3 = qrwa.getAssetReleasePoll(pollIdEp3); - EXPECT_EQ(pollResultEp3.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 1000 + 500); - - auto activeParams = qrwa.getGovParams(); - EXPECT_EQ(activeParams.electricityPercent, 300); - EXPECT_EQ(activeParams.maintenancePercent, 100); - - // Calculate Pools based on Revenue - 100 QU Fee - sint64 netRevenueEp3 = REVENUE_AMT - 100; - sint64 feeAmtEp3 = (netRevenueEp3 * 500) / 1000; // 50% fees still (params update next epoch) - sint64 distributableEp3 = netRevenueEp3 - feeAmtEp3; - sint64 qminePoolEp3 = (distributableEp3 * 900) / 1000; - sint64 qrwaPoolEp3 = distributableEp3 - qminePoolEp3; - - // Contract released 1000 + 500. Balance = 150B - 1500. - sint64 payoutBaseEp3 = TOTAL_SUPPLY - (TREASURY_INIT - 1500); - - sint64 qrwaRateEp3 = getQrwaRateForEpoch(qrwaPoolEp3); - - divS1 = calculateQminePayout(145000000000LL, payoutBaseEp3, qminePoolEp3); - divS2 = calculateQminePayout(180000000000LL, payoutBaseEp3, qminePoolEp3); - divS3 = calculateQminePayout(165000000000LL, payoutBaseEp3, qminePoolEp3); - divS4 = calculateQminePayout(175000000000LL, payoutBaseEp3, qminePoolEp3); - divS5 = calculateQminePayout(180000000000LL, payoutBaseEp3, qminePoolEp3); - - divQS1 = calculateQrwaPayout(200, qrwaRateEp3); - divQQ1 = calculateQrwaPayout(200, qrwaRateEp3); - divQQ2 = calculateQrwaPayout(276, qrwaRateEp3); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); - - snapshotBalances(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " PRE-EPOCH 4\n"; - print_balances(); -#endif - - // epoch 4 (no transfers) - qrwa.beginEpoch(); - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - qrwa.endEpoch(); - - // Checks Ep 4 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " END-EPOCH 4\n"; - print_balances(); -#endif - - // Payout base remains same as previous epoch (no new releases) - sint64 payoutBaseEp4 = payoutBaseEp3; - // Revenue is full 10M (no releases) - sint64 qminePoolEp4 = QMINE_POOL_AMT; - - sint64 qrwaRateEp4 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); - - divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); - divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); - divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); - divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); - divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); - - divQS1 = calculateQrwaPayout(200, qrwaRateEp4); - divQQ1 = calculateQrwaPayout(200, qrwaRateEp4); - divQQ2 = calculateQrwaPayout(276, qrwaRateEp4); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); - - snapshotBalances(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " PRE-EPOCH 5\n"; - print_balances(); -#endif - - // epoch 5 - qrwa.beginEpoch(); - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - - // Release Poll - pollInput.amount = 100; - pollInput.proposalName = id::randomValue(); - pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); - uint64 pollIdEp5 = pollOut.proposalId; - - // Vote NO (3/5 Majority) - EXPECT_EQ(qrwa.voteAssetRelease(S1, pollIdEp5, 0), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S2, pollIdEp5, 0), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S3, pollIdEp5, 0), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S4, pollIdEp5, 1), QRWA_STATUS_SUCCESS); - EXPECT_EQ(qrwa.voteAssetRelease(S5, pollIdEp5, 1), QRWA_STATUS_SUCCESS); - - qrwa.endEpoch(); - - // Checks Ep 5 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " END-EPOCH 5\n"; - print_balances(); -#endif - - auto pollResultEp5 = qrwa.getAssetReleasePoll(pollIdEp5); - EXPECT_EQ(pollResultEp5.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); - EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 1500); // Unchanged - - // Failed vote = No release = No fee = Full Revenue. Base unchanged. - sint64 qrwaRateEp5 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); - - divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); - divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); - divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); - divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); - divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); - - divQS1 = calculateQrwaPayout(200, qrwaRateEp5); - divQQ1 = calculateQrwaPayout(200, qrwaRateEp5); - divQQ2 = calculateQrwaPayout(276, qrwaRateEp5); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); - - snapshotBalances(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " PRE-EPOCH 6\n"; - print_balances(); -#endif - - // epoch 6 - qrwa.beginEpoch(); - - // Revenue - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - - // Create Gov Proposal - QRWA::QRWAGovParams failParams = qrwa.getGovParams(); - failParams.reinvestmentPercent = 200; - - // Only S1 votes (< 20% supply). Quorum fail - EXPECT_EQ(qrwa.voteGovParams(S1, failParams), QRWA_STATUS_SUCCESS); - - qrwa.endEpoch(); - - // Checks Ep 6 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " END-EPOCH 6\n"; - print_balances(); -#endif - - auto paramsEp6 = qrwa.getGovParams(); - EXPECT_EQ(paramsEp6.reinvestmentPercent, 100); - EXPECT_NE(paramsEp6.reinvestmentPercent, 200); - - sint64 qrwaRateEp6 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); - - divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); - divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); - divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); - divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); - divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); - - divQS1 = calculateQrwaPayout(200, qrwaRateEp6); - divQQ1 = calculateQrwaPayout(200, qrwaRateEp6); - divQQ2 = calculateQrwaPayout(276, qrwaRateEp6); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); - - snapshotBalances(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " PRE-EPOCH 7\n"; - print_balances(); -#endif - - // epoch 7 - qrwa.beginEpoch(); - - // Revenue - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); - - // Create poll, no votes - pollInput.amount = 100; - pollInput.proposalName = id::randomValue(); - pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); - uint64 pollIdEp7 = pollOut.proposalId; - - qrwa.endEpoch(); - - // Checks Ep 7 - advanceTime7Days(); - qrwa.resetPayoutTime(); - qrwa.endTick(); - -#if ENABLE_BALANCE_DEBUG - std::cout << " END-EPOCH 7\n"; - print_balances(); -#endif - - auto pollResultEp7 = qrwa.getAssetReleasePoll(pollIdEp7); - EXPECT_EQ(pollResultEp7.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); - - sint64 qrwaRateEp7 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); - - divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); - divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); - divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); - divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); - divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); - - divQS1 = calculateQrwaPayout(200, qrwaRateEp7); - divQQ1 = calculateQrwaPayout(200, qrwaRateEp7); - divQQ2 = calculateQrwaPayout(276, qrwaRateEp7); - - EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); - EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); - EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); - EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); - EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); - EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); - EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); -} - -TEST(ContractQRWA, Payout_MultiContractManagement) -{ - ContractTestingQRWA qrwa; - - const sint64 totalShares = 1000000; - const sint64 qxManagedShares = 700000; - const sint64 qswapManagedShares = 300000; // 30% moved to QSWAP management - - // Issue QMINE and give to HOLDER_A - // Initially, all 1M shares are managed by QX (default for transfers via QX) - increaseEnergy(QMINE_ISSUER, 1000000000); - increaseEnergy(HOLDER_A, 1000000); // For fees - - qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, totalShares); - qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, totalShares); - - // Verify initial state managed by QX - EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), totalShares); - EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), 0); - - // Transfer management rights of 300k shares to QSWAP - // The user (HOLDER_A) remains the Possessor. - qrwa.transferManagementRights(HOLDER_A, QMINE_ASSET, qswapManagedShares, QSWAP_CONTRACT_INDEX); - - // Verify the split in management rights - // 700k should remain under QX - EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), qxManagedShares); - // 300k should now be under QSWAP - EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), qswapManagedShares); - - qrwa.beginEpoch(); - - // Generate Revenue - // pool A revenue: 1,000,000 QUs - // fees (50%): 500,000 - // net revenue: 500,000 - // QMINE pool (90%): 450,000 - qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); - - qrwa.endEpoch(); - - // trigger Payout - etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 7; // Friday - etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; - qrwa.resetPayoutTime(); - - // snapshot balances for check - sint64 balanceBefore = getBalance(HOLDER_A); - - qrwa.endTick(); - - // Calculate Expected Payout - // Payout = (UserTotalShares * PoolAmount) / TotalSupply - // UserTotalShares = 1,000,000 (regardless of manager) - // PoolAmount = 450,000 - // TotalSupply = 1,000,000 - // Expected = 450,000 - sint64 expectedPayout = (totalShares * 450000) / totalShares; - - sint64 balanceAfter = getBalance(HOLDER_A); - - // If qRWA only counted QX shares, the payout would be (700k/1M * 450k) = 315,000. - // If qRWA counts ALL shares, the payout is 450,000. - EXPECT_EQ(balanceAfter - balanceBefore, expectedPayout); - EXPECT_EQ(balanceAfter - balanceBefore, 450000); -} diff --git a/test/contract_qswap.cpp b/test/contract_qswap.cpp deleted file mode 100644 index 3291262dc..000000000 --- a/test/contract_qswap.cpp +++ /dev/null @@ -1,810 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -//#define PRINT_DETAILS 0 - -static constexpr uint64 QSWAP_ISSUE_ASSET_FEE = 1000000000ull; -static constexpr uint64 QSWAP_TRANSFER_ASSET_FEE = 10000000ull; -static constexpr uint64 QSWAP_CREATE_POOL_FEE = 1000000000ull; - -static const id QSWAP_CONTRACT_ID(QSWAP_CONTRACT_INDEX, 0, 0, 0); - -//constexpr uint32 SWAP_FEE_IDX = 1; -constexpr uint32 GET_POOL_BASIC_STATE_IDX = 2; -constexpr uint32 GET_LIQUIDITY_OF_IDX = 3; -constexpr uint32 QUOTE_EXACT_QU_INPUT_IDX = 4; -constexpr uint32 QUOTE_EXACT_QU_OUTPUT_IDX = 5; -constexpr uint32 QUOTE_EXACT_ASSET_INPUT_IDX = 6; -constexpr uint32 QUOTE_EXACT_ASSET_OUTPUT_IDX = 7; -constexpr uint32 INVEST_REWARDS_INFO_IDX = 8; -// -constexpr uint32 ISSUE_ASSET_IDX = 1; -constexpr uint32 TRANSFER_SHARE_OWNERSHIP_AND_POSSESSION_IDX = 2; -constexpr uint32 CREATE_POOL_IDX = 3; -constexpr uint32 ADD_LIQUIDITY_IDX = 4; -constexpr uint32 REMOVE_LIQUIDITY_IDX = 5; -constexpr uint32 SWAP_EXACT_QU_FOR_ASSET_IDX = 6; -constexpr uint32 SWAP_QU_FOR_EXACT_ASSET_IDX = 7; -constexpr uint32 SWAP_EXACT_ASSET_FOR_QU_IDX = 8; -constexpr uint32 SWAP_ASSET_FOR_EXACT_QU_IDX = 9; -constexpr uint32 SET_INVEST_REWARDS_INFO_IDX = 10; -constexpr uint32 TRANSFER_SHARE_MANAGEMENT_RIGHTS_IDX = 11; - - -class QswapChecker : public QSWAP -{ -// public: -// void checkCollectionConsistency() { -// } -}; - - -class ContractTestingQswap : protected ContractTesting -{ -public: - ContractTestingQswap() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QSWAP); - callSystemProcedure(QSWAP_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - QswapChecker* getState() - { - return (QswapChecker*)contractStates[QSWAP_CONTRACT_INDEX]; - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(QSWAP_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - bool loadState(const CHAR16* filename) - { - return load(filename, sizeof(QSWAP), contractStates[QSWAP_CONTRACT_INDEX]) == sizeof(QSWAP); - } - - QSWAP::InvestRewardsInfo_output investRewardsInfo() - { - QSWAP::InvestRewardsInfo_input input{}; - QSWAP::InvestRewardsInfo_output output; - callFunction(QSWAP_CONTRACT_INDEX, INVEST_REWARDS_INFO_IDX, input, output); - return output; - } - - bool setInvestRewardsInfo(const id& issuer, QSWAP::SetInvestRewardsInfo_input input) - { - QSWAP::SetInvestRewardsInfo_output output; - invokeUserProcedure(QSWAP_CONTRACT_INDEX, SET_INVEST_REWARDS_INFO_IDX, input, output, issuer, 0); - return output.success; - } - - sint64 issueAsset(const id& issuer, QSWAP::IssueAsset_input input) - { - QSWAP::IssueAsset_output output; - invokeUserProcedure(QSWAP_CONTRACT_INDEX, ISSUE_ASSET_IDX, input, output, issuer, QSWAP_ISSUE_ASSET_FEE); - return output.issuedNumberOfShares; - } - - sint64 transferAsset(const id& issuer, QSWAP::TransferShareOwnershipAndPossession_input input) - { - QSWAP::TransferShareOwnershipAndPossession_output output; - invokeUserProcedure(QSWAP_CONTRACT_INDEX, TRANSFER_SHARE_OWNERSHIP_AND_POSSESSION_IDX, input, output, issuer, QSWAP_TRANSFER_ASSET_FEE); - return output.transferredAmount; - } - - bool createPool(const id& issuer, uint64 assetName) - { - QSWAP::CreatePool_input input{assetName}; - QSWAP::CreatePool_output output; - invokeUserProcedure(QSWAP_CONTRACT_INDEX, CREATE_POOL_IDX, input, output, issuer, QSWAP_CREATE_POOL_FEE); - return output.success; - } - - QSWAP::GetPoolBasicState_output getPoolBasicState(const id& issuer, uint64 assetName) - { - QSWAP::GetPoolBasicState_input input{issuer, assetName}; - QSWAP::GetPoolBasicState_output output; - - callFunction(QSWAP_CONTRACT_INDEX, GET_POOL_BASIC_STATE_IDX, input, output); - return output; - } - - QSWAP::AddLiquidity_output addLiquidity(const id& issuer, QSWAP::AddLiquidity_input input, uint64 inputValue) - { - QSWAP::AddLiquidity_output output; - invokeUserProcedure( - QSWAP_CONTRACT_INDEX, - ADD_LIQUIDITY_IDX, - input, - output, - issuer, - inputValue - ); - return output; - } - - QSWAP::RemoveLiquidity_output removeLiquidity(const id& issuer, QSWAP::RemoveLiquidity_input input, uint64 inputValue) - { - QSWAP::RemoveLiquidity_output output; - invokeUserProcedure( - QSWAP_CONTRACT_INDEX, - REMOVE_LIQUIDITY_IDX, - input, - output, - issuer, - inputValue - ); - return output; - } - - QSWAP::GetLiquidityOf_output getLiquidityOf(QSWAP::GetLiquidityOf_input input) - { - QSWAP::GetLiquidityOf_output output; - callFunction(QSWAP_CONTRACT_INDEX, GET_LIQUIDITY_OF_IDX, input, output); - return output; - } - - QSWAP::SwapExactQuForAsset_output swapExactQuForAsset( const id& issuer, QSWAP::SwapExactQuForAsset_input input, uint64 inputValue) - { - QSWAP::SwapExactQuForAsset_output output; - invokeUserProcedure( - QSWAP_CONTRACT_INDEX, - SWAP_EXACT_QU_FOR_ASSET_IDX, - input, - output, - issuer, - inputValue - ); - - return output; - } - - QSWAP::SwapQuForExactAsset_output swapQuForExactAsset( const id& issuer, QSWAP::SwapQuForExactAsset_input input, uint64 inputValue) - { - QSWAP::SwapQuForExactAsset_output output; - invokeUserProcedure( - QSWAP_CONTRACT_INDEX, - SWAP_QU_FOR_EXACT_ASSET_IDX, - input, - output, - issuer, - inputValue - ); - - return output; - } - - QSWAP::SwapExactAssetForQu_output swapExactAssetForQu(const id& issuer, QSWAP::SwapExactAssetForQu_input input, uint64 inputValue) - { - QSWAP::SwapExactAssetForQu_output output; - invokeUserProcedure( - QSWAP_CONTRACT_INDEX, - SWAP_EXACT_ASSET_FOR_QU_IDX, - input, - output, - issuer, - inputValue - ); - - return output; - } - - QSWAP::SwapAssetForExactQu_output swapAssetForExactQu(const id& issuer, QSWAP::SwapAssetForExactQu_input input, uint64 inputValue) - { - QSWAP::SwapAssetForExactQu_output output; - invokeUserProcedure( - QSWAP_CONTRACT_INDEX, - SWAP_ASSET_FOR_EXACT_QU_IDX, - input, - output, - issuer, - inputValue - ); - - return output; - } - - QSWAP::TransferShareManagementRights_output transferShareManagementRights(const id& invocator, QSWAP::TransferShareManagementRights_input input, uint64 inputValue) - { - QSWAP::TransferShareManagementRights_output output; - invokeUserProcedure(QSWAP_CONTRACT_INDEX, TRANSFER_SHARE_MANAGEMENT_RIGHTS_IDX, input, output, invocator, inputValue); - return output; - } - - QSWAP::QuoteExactQuInput_output quoteExactQuInput(QSWAP::QuoteExactQuInput_input input) - { - QSWAP::QuoteExactQuInput_output output; - callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_QU_INPUT_IDX, input, output); - return output; - } - - QSWAP::QuoteExactQuOutput_output quoteExactQuOutput(QSWAP::QuoteExactQuOutput_input input) - { - QSWAP::QuoteExactQuOutput_output output; - callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_QU_OUTPUT_IDX, input, output); - return output; - } - - QSWAP::QuoteExactAssetInput_output quoteExactAssetInput(QSWAP::QuoteExactAssetInput_input input) - { - QSWAP::QuoteExactAssetInput_output output; - callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_ASSET_INPUT_IDX, input, output); - return output; - } - - QSWAP::QuoteExactAssetOutput_output quoteExactAssetOutput(QSWAP::QuoteExactAssetOutput_input input) - { - QSWAP::QuoteExactAssetOutput_output output; - callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_ASSET_OUTPUT_IDX, input, output); - return output; - } -}; - -TEST(ContractSwap, InvestRewardsInfoTest) -{ - ContractTestingQswap qswap; - - { - QSWAP::InvestRewardsInfo_output info = qswap.investRewardsInfo(); - - auto expectIdentity = (const unsigned char*)"VJGRUFWJCUSNHCQJRWRRYXAUEJFCVHYPXWKTDLYKUACPVVYBGOLVCJSF"; - m256i expectPubkey; - getPublicKeyFromIdentity(expectIdentity, expectPubkey.m256i_u8); - EXPECT_EQ(info.investRewardsId, expectPubkey); - EXPECT_EQ(info.investRewardsFee, 3); - } - - { - id newInvestRewardsId(6,6,6,6); - QSWAP::SetInvestRewardsInfo_input input = {newInvestRewardsId}; - - id invalidIssuer(1,2,3,4); - - increaseEnergy(invalidIssuer, 100); - bool res1 = qswap.setInvestRewardsInfo(invalidIssuer, input); - // printf("res1: %d\n", res1); - EXPECT_FALSE(res1); - - auto investRewardsIdentity = (const unsigned char*)"VJGRUFWJCUSNHCQJRWRRYXAUEJFCVHYPXWKTDLYKUACPVVYBGOLVCJSF"; - m256i investRewardsPubkey; - getPublicKeyFromIdentity(investRewardsIdentity, investRewardsPubkey.m256i_u8); - - increaseEnergy(investRewardsPubkey, 100); - bool res2 = qswap.setInvestRewardsInfo(investRewardsPubkey, input); - // printf("res2: %d\n", res2); - EXPECT_TRUE(res2); - - QSWAP::InvestRewardsInfo_output info = qswap.investRewardsInfo(); - EXPECT_EQ(info.investRewardsId, newInvestRewardsId); - // printf("%d\n", info.investRewardsId == newInvestRewardsId); - } -} - -TEST(ContractSwap, QuoteTest) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 10000 * 1000; - - // issue an asset and create a pool, and init liquidity - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - - sint64 inputValue = 30*1000; - increaseEnergy(issuer, inputValue); - QSWAP::AddLiquidity_input alInput = { issuer, assetName, 30*1000, 0, 0 }; - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); - - QSWAP::QuoteExactQuInput_input qi_input = {issuer, assetName, 1000}; - QSWAP::QuoteExactQuInput_output qi_output = qswap.quoteExactQuInput(qi_input); - // printf("quote exact qu input: %lld\n", qi_output.assetAmountOut); - EXPECT_EQ(qi_output.assetAmountOut, 964); - - QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, 1000}; - QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input); - // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn); - EXPECT_EQ(qo_output.assetAmountIn, 1038); - - QSWAP::QuoteExactAssetInput_input ai_input = {issuer, assetName, 1000}; - QSWAP::QuoteExactAssetInput_output ai_output = qswap.quoteExactAssetInput(ai_input); - // printf("quote exact asset input: %lld\n", ai_output.quAmountOut); - EXPECT_EQ(ai_output.quAmountOut, 964); - - QSWAP::QuoteExactAssetOutput_input ao_input = {issuer, assetName, 1000}; - QSWAP::QuoteExactAssetOutput_output ao_output = qswap.quoteExactAssetOutput(ao_input); - // printf("quote exact asset output: %lld\n", ao_output.quAmountIn); - EXPECT_EQ(ao_output.quAmountIn, 1038); - } -} - -/* -0. normally issue asset -1. not enough qu for asset issue fee -2. issue duplicate asset -3. issue asset with invalid input params, such as numberOfShares: 0 -*/ -TEST(ContractSwap, IssueAssetAndTransferShareManagementRights) -{ - ContractTestingQswap qswap; - qswap.beginEpoch(); - - id issuer(1, 2, 3, 4); - - // 0. normally issue asset and transfer - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 1000000; - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), 0); - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), QSWAP_ISSUE_ASSET_FEE); - - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - sint64 transferAmount = 1000; - id newId(2,3,4,5); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, newId, newId, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), 0); - QSWAP::TransferShareOwnershipAndPossession_input ts_input = {issuer, assetName, newId, transferAmount}; - // printf("ts amount: %lld\n", transferAmount); - EXPECT_EQ(qswap.transferAsset(issuer, ts_input), transferAmount); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, newId, newId, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), transferAmount); - // printf("%lld\n", getBalance(QSWAP_CONTRACT_ID)); - increaseEnergy(issuer, 100); - uint64 qswapIdBalance = getBalance(QSWAP_CONTRACT_ID); - uint64 issuerBalance = getBalance(issuer); - QSWAP::TransferShareManagementRights_input tsr_input = {Asset{issuer, assetName}, transferAmount, QX_CONTRACT_INDEX}; - EXPECT_EQ(qswap.transferShareManagementRights(issuer, tsr_input, 100).transferredNumberOfShares, transferAmount); - EXPECT_EQ(getBalance(id(QX_CONTRACT_INDEX, 0, 0, 0)), 100); - EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), qswapIdBalance); - EXPECT_EQ(getBalance(issuer), issuerBalance - 100); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), transferAmount); - } - - // 1. not enough energy for asset issue fee - { - decreaseEnergy(spectrumIndex(issuer), getBalance(issuer)); - uint64 assetName = assetNameFromString("QSWAP1"); - sint64 numberOfShares = 1000000; - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), 0); - } - - // 2. issue duplicate asset, related to test.0 - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 1000000; - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), 0); - } - - // 3. issue asset with invalid input params, such as numberOfShares: 0 - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - uint64 assetName = assetNameFromString("QSWAP1"); - sint64 numberOfShares = 0; - QSWAP::IssueAsset_input input = {assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), 0); - } -} - -TEST(ContractSwap, SwapExactQuForAsset) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 10000 * 1000; - - // issue an asset and create a pool, and init liquidity - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - - sint64 inputValue = 200*1000; - increaseEnergy(issuer, inputValue); - QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); - // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); - } - - { - // swap in 100*1000 qu, get about 1000*50 asset - id user(2,3,4,5); - sint64 inputValue = 200*1000; - increaseEnergy(user, inputValue); - - QSWAP::QuoteExactQuInput_input qi_input = {issuer, assetName, inputValue}; - QSWAP::QuoteExactQuInput_output qi_output = qswap.quoteExactQuInput(qi_input); - // printf("quote_exact_qu_input, asset out: %lld\n", qi_output.assetAmountOut); - - QSWAP::SwapExactQuForAsset_input input = {issuer, assetName, 0}; - QSWAP::SwapExactQuForAsset_output output = qswap.swapExactQuForAsset(user, input, inputValue); - // printf("swap_exact_qu_for_asset, asset out: %lld\n", output.assetAmountOut); - - EXPECT_EQ(qi_output.assetAmountOut, output.assetAmountOut); // 49924 - - EXPECT_TRUE(output.assetAmountOut <= 50000); // 49924 if swapFee 0.3% - - QSWAP::GetPoolBasicState_output psOutput = qswap.getPoolBasicState(issuer, assetName); - // printf("%lld, %lld, %lld\n", psOutput.reservedAssetAmount, psOutput.reservedQuAmount, psOutput.totalLiquidity); - // swapFee is 200_000 * 0.3% = 600, shareholders 27%: 162, QX 5%: 30, invest&rewards 3%: 18, burn 1%: 6 = 216 - EXPECT_TRUE(psOutput.reservedQuAmount >= 399784); // 399784 = (400_000 - 216) - EXPECT_TRUE(psOutput.reservedAssetAmount >= 50000 ); // 50076 - EXPECT_EQ(psOutput.totalLiquidity, 141421); // liquidity stay the same - } -} - -TEST(ContractSwap, SwapQuForExactAsset) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 10000 * 1000; - - // issue an asset and create a pool, and init liquidity - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - - sint64 inputValue = 200*1000; - increaseEnergy(issuer, inputValue); - QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); - // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); - } - - { - id user(2,3,4,5); - sint64 inputValue = 1000 * 200; - sint64 expectQuAmountIn = 22289; - sint64 assetAmountOut = 10 * 1000; - increaseEnergy(user, inputValue); - - QSWAP::QuoteExactAssetOutput_input ao_input = {issuer, assetName, assetAmountOut}; - QSWAP::QuoteExactAssetOutput_output ao_output = qswap.quoteExactAssetOutput(ao_input); - // printf("quote_exact_asset_output, qu in %lld\n", ao_output.quAmountIn); - - QSWAP::SwapQuForExactAsset_input input = {issuer, assetName, assetAmountOut}; - QSWAP::SwapQuForExactAsset_output output = qswap.swapQuForExactAsset(user, input, inputValue); - - EXPECT_EQ(ao_output.quAmountIn, output.quAmountIn); // 22289 - - // EXPECT_EQ(output.quAmountIn, 22289); - // printf("swap_qu_for_exact_asset, asset in: %lld\n", output.quAmountIn); - } -} - -TEST(ContractSwap, SwapExactAssetForQu) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 10000 * 1000; - - // issue an asset and create a pool, and init liquidity - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - - sint64 inputValue = 200*1000; - increaseEnergy(issuer, inputValue); - QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); - // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); - } - - { - id user(1, 2,3,4); - sint64 inputValue = 0; - sint64 assetAmountIn = 100*1000; - sint64 expectQuAmountOut = 99700; - increaseEnergy(user, inputValue); - - QSWAP::QuoteExactAssetInput_input ai_input = {issuer, assetName, assetAmountIn}; - QSWAP::QuoteExactAssetInput_output ai_output = qswap.quoteExactAssetInput(ai_input); - // printf("quote exact asset input: %lld\n", ai_output.quAmountOut); - - QSWAP::SwapExactAssetForQu_input input = {issuer, assetName, assetAmountIn, 0}; - QSWAP::SwapExactAssetForQu_output output = qswap.swapExactAssetForQu(user, input, inputValue); - // printf("swap qu out: %lld\n", output.quAmountOut); - EXPECT_EQ(ai_output.quAmountOut, output.quAmountOut); // 99700 - } -} - -TEST(ContractSwap, SwapAssetForExactQu) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 10000 * 1000; - - // issue an asset and create a pool, and init liquidity - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - - sint64 inputValue = 200*1000; - increaseEnergy(issuer, inputValue); - QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); - // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); - - // QSWAP::GetPoolBasicState_output gp_output = qswap.getPoolBasicState(issuer, assetName); - // printf("%lld, %lld, %lld\n", gp_output.reservedQuAmount, gp_output.reservedAssetAmount, gp_output.totalLiquidity); - } - - { - id user(1,2,3,4); - sint64 inputValue = 0; - sint64 quAmountOut = 200*1000 - 1; - - QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, quAmountOut}; - QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input); - // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn); - EXPECT_EQ(qo_output.assetAmountIn, -1); - } - - { - id user(1,2,3,4); - sint64 inputValue = 0; - sint64 quAmountOut = 100*1000; - sint64 expectAssetAmountIn = 100604; - - QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, quAmountOut}; - QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input); - // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn); - EXPECT_EQ(qo_output.assetAmountIn, expectAssetAmountIn); - - increaseEnergy(user, inputValue); - sint64 assetAmountInMax = 200*1000; - QSWAP::SwapAssetForExactQu_input input = {issuer, assetName, assetAmountInMax, quAmountOut}; - QSWAP::SwapAssetForExactQu_output output = qswap.swapAssetForExactQu(user, input, inputValue); - // printf("swap asset in: %lld\n", output.assetAmountIn); - EXPECT_EQ(qo_output.assetAmountIn, output.assetAmountIn); - } -} - -/* -0. check pool state before create -1. normal create pool, check pool existance, pool states -2. create duplicate pool -3. create pool with invalid asset -*/ -TEST(ContractSwap, CreatePool) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - sint64 numberOfShares = 1000000; - - // issue asset first - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = {assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - } - - // 0. check not exsit pool state before create - { - QSWAP::GetPoolBasicState_output output = qswap.getPoolBasicState(issuer, assetName); - EXPECT_FALSE(output.poolExists); - } - - // 1. normal create pool, check pool existance, pool states - { - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - - // initial pool state - QSWAP::GetPoolBasicState_output output = qswap.getPoolBasicState(issuer, assetName); - EXPECT_EQ(output.poolExists, true); - EXPECT_EQ(output.reservedQuAmount, 0); - EXPECT_EQ(output.reservedAssetAmount, 0); - EXPECT_EQ(output.totalLiquidity, 0); - } - - // 2. create duplicate pool - { - EXPECT_FALSE(qswap.createPool(issuer, assetName)); - } - - // 3. ceate pool with not issued asset - { - uint64 assetName2 = assetNameFromString("QswapX"); - EXPECT_FALSE(qswap.createPool(issuer, assetName2)); - } -} - -/* -add liquidity 2 times, and then remove -*/ -TEST(ContractSwap, LiqTest1) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - uint64 invalidAssetName = assetNameFromString("QSWAP1"); - sint64 numberOfShares = 1000*1000; - - // 0. issue an asset and create a pool - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - } - - // 1. add liquidity to a initial pool, first time - { - sint64 quStakeAmount = 200*1000; - sint64 inputValue = quStakeAmount; - sint64 assetStakeAmount = 100*1000; - increaseEnergy(issuer, quStakeAmount); - QSWAP::AddLiquidity_input addLiqInput = { - issuer, - assetName, - assetStakeAmount, - 0, - 0 - }; - - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, addLiqInput, inputValue); - // actually, 141421 liquidity add to the pool, but the first 1000 liquidity is retainedd by the pool rather than the staker - EXPECT_EQ(output.userIncreaseLiquidity, 140421); - EXPECT_EQ(output.quAmount, 200*1000); - EXPECT_EQ(output.assetAmount, 100*1000); - - QSWAP::GetPoolBasicState_output output2 = qswap.getPoolBasicState(issuer, assetName); - EXPECT_EQ(output2.poolExists, true); - EXPECT_EQ(output2.reservedQuAmount, 200*1000); - EXPECT_EQ(output2.reservedAssetAmount, 100*1000); - EXPECT_EQ(output2.totalLiquidity, 141421); - // printf("pool state: %lld, %lld, %lld\n", output2.reservedQuAmount, output2.reservedAssetAmount, output2.totalLiquidity); - - QSWAP::GetLiquidityOf_input getLiqInput = { - issuer, - assetName, - issuer - }; - QSWAP::GetLiquidityOf_output getLiqOutput = qswap.getLiquidityOf(getLiqInput); - EXPECT_EQ(getLiqOutput.liquidity, 140421); - - // 2. add liquidity second time - increaseEnergy(issuer, quStakeAmount); - addLiqInput = { - issuer, - assetName, - assetStakeAmount, - 0, - 0 - }; - - QSWAP::AddLiquidity_output output3 = qswap.addLiquidity(issuer, addLiqInput, inputValue); - EXPECT_EQ(output3.userIncreaseLiquidity, 141421); - EXPECT_EQ(output3.quAmount, 200*1000); - EXPECT_EQ(output3.assetAmount, 100*1000); - - getLiqOutput = qswap.getLiquidityOf(getLiqInput); - EXPECT_EQ(getLiqOutput.liquidity, 281842); // 140421 + 141421 - - QSWAP::RemoveLiquidity_input rmLiqInput = { - issuer, - assetName, - 141421, - 200*1000, // should lte 1000*200 - 100*1000, // should lte 1000*100 - }; - - // 3. remove liquidity - QSWAP::RemoveLiquidity_output rmLiqOutput = qswap.removeLiquidity(issuer, rmLiqInput, 0); - // printf("qu: %lld, asset: %lld\n", rmLiqOutput.quAmount, rmLiqOutput.assetAmount); - EXPECT_EQ(rmLiqOutput.quAmount, 1000 * 200); - EXPECT_EQ(rmLiqOutput.assetAmount, 1000 * 100); - - getLiqOutput = qswap.getLiquidityOf(getLiqInput); - // printf("liq: %lld\n", getLiqOutput.liquidity); - EXPECT_EQ(getLiqOutput.liquidity, 140421); // 281842 - 141421 - } -} - -// failed case -TEST(ContractSwap, LiqTest2) -{ - ContractTestingQswap qswap; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QSWAP0"); - uint64 invalidAssetName = assetNameFromString("QSWAP1"); - sint64 numberOfShares = 1000*1000; - - // 0. issue an asset and create a pool - { - increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); - QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; - EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); - - increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); - EXPECT_TRUE(qswap.createPool(issuer, assetName)); - } - - // add liquidity to invalid pool, - { - // decreaseEnergy(getBalance(issuer)); - uint64 quAmount = 1000; - increaseEnergy(issuer, quAmount); - QSWAP::AddLiquidity_input addLiqInput = { - issuer, - invalidAssetName, - 1000, - 0, - 0 - }; - - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, addLiqInput, 1000); - EXPECT_EQ(output.userIncreaseLiquidity, 0); - EXPECT_EQ(output.quAmount, 0); - EXPECT_EQ(output.assetAmount, 0); - } - - // add liquidity with asset more than holdings - { - increaseEnergy(issuer, 1000); - QSWAP::AddLiquidity_input addLiqInput = { - issuer, - assetName, - 1000*1000 + 100, // excced 1000*1000 - 0, - 0 - }; - - QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, addLiqInput, 1000); - EXPECT_EQ(output.userIncreaseLiquidity, 0); - EXPECT_EQ(output.quAmount, 0); - EXPECT_EQ(output.assetAmount, 0); - } -} diff --git a/test/contract_qtf.cpp b/test/contract_qtf.cpp deleted file mode 100644 index af7c5961e..000000000 --- a/test/contract_qtf.cpp +++ /dev/null @@ -1,3115 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -#include -#include -#include - -// Procedure/function indices (must match REGISTER_USER_FUNCTIONS_AND_PROCEDURES in `src/contracts/QThirtyFour.h`). -constexpr uint16 QTF_PROCEDURE_BUY_TICKET = 1; -constexpr uint16 QTF_PROCEDURE_SET_PRICE = 2; -constexpr uint16 QTF_PROCEDURE_SET_SCHEDULE = 3; -constexpr uint16 QTF_PROCEDURE_SET_TARGET_JACKPOT = 4; -constexpr uint16 QTF_PROCEDURE_SET_DRAW_HOUR = 5; - -constexpr uint16 QTF_FUNCTION_GET_TICKET_PRICE = 1; -constexpr uint16 QTF_FUNCTION_GET_NEXT_EPOCH_DATA = 2; -constexpr uint16 QTF_FUNCTION_GET_WINNER_DATA = 3; -constexpr uint16 QTF_FUNCTION_GET_POOLS = 4; -constexpr uint16 QTF_FUNCTION_GET_SCHEDULE = 5; -constexpr uint16 QTF_FUNCTION_GET_DRAW_HOUR = 6; -constexpr uint16 QTF_FUNCTION_GET_STATE = 7; -constexpr uint16 QTF_FUNCTION_GET_FEES = 8; -constexpr uint16 QTF_FUNCTION_ESTIMATE_PRIZE_PAYOUTS = 9; - -using QTFRandomValues = Array; - -namespace -{ - static void issueRlSharesTo(std::vector>& initialOwnerShares) - { - issueContractShares(RL_CONTRACT_INDEX, initialOwnerShares, false); - } - - static void primeQpiFunctionContext(QpiContextUserFunctionCall& qpi) - { - QTF::GetTicketPrice_input input{}; - qpi.call(QTF_FUNCTION_GET_TICKET_PRICE, &input, sizeof(input)); - } - - static void primeQpiProcedureContext(QpiContextUserProcedureCall& qpi, uint8 drawHour) - { - QTF::SetDrawHour_input input{}; - input.newDrawHour = drawHour; - qpi.call(QTF_PROCEDURE_SET_DRAW_HOUR, &input, sizeof(input)); - ASSERT_EQ(contractError[QTF_CONTRACT_INDEX], 0); - } - - static bool valuesEqual(const QTFRandomValues& a, const QTFRandomValues& b) - { - return memcmp(&a, &b, sizeof(a)) == 0; - } - - static void expectWinnerValuesValidAndUnique(const QTF::GetWinnerData_output& winnerData) - { - std::set unique; - for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) - { - const uint8 v = winnerData.winnerData.winnerValues.get(i); - EXPECT_GE(v, 1u) << "Winning value " << i << " should be >= 1"; - EXPECT_LE(v, QTF_MAX_RANDOM_VALUE) << "Winning value " << i << " should be <= 30"; - unique.insert(v); - } - EXPECT_EQ(unique.size(), static_cast(QTF_RANDOM_VALUES_COUNT)) << "All 4 winning numbers should be unique"; - EXPECT_GT(static_cast(winnerData.winnerData.epoch), 0u) << "Epoch should be recorded after draw"; - } - - static void computeBaselinePrizePools(uint64 revenue, const QTF::GetFees_output& fees, uint64& winnersBlock, uint64& k2Pool, uint64& k3Pool) - { - winnersBlock = (revenue * static_cast(fees.winnerFeePercent)) / 100ULL; - k2Pool = (winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000ULL; - k3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000ULL; - } -} // namespace - -constexpr uint8 QTF_ANY_DAY_SCHEDULE = 0xFF; - -// Test helper class exposing internal state -class QTFChecker : public QTF -{ -public: - uint64 getNumberOfPlayers() const { return numberOfPlayers; } - uint64 getTicketPriceInternal() const { return ticketPrice; } - uint64 getJackpot() const { return jackpot; } - uint64 getTargetJackpotInternal() const { return targetJackpot; } - uint32 getDrawHourInternal() const { return drawHour; } - bool getFrActive() const { return frActive; } - uint32 getFrRoundsSinceK4() const { return frRoundsSinceK4; } - uint32 getFrRoundsAtOrAboveTarget() const { return frRoundsAtOrAboveTarget; } - const id& team() const { return teamAddress; } - - void setScheduleMask(uint8 newMask) { schedule = newMask; } - void setJackpot(uint64 value) { jackpot = value; } - void setTargetJackpotInternal(uint64 value) { targetJackpot = value; } - void setTicketPriceInternal(uint64 value) { ticketPrice = value; } - void setFrActive(bit value) { frActive = value; } - void setFrRoundsSinceK4(uint16 value) { frRoundsSinceK4 = value; } - void setFrRoundsAtOrAboveTarget(uint16 value) { frRoundsAtOrAboveTarget = value; } - void setOverflowAlphaBP(uint64 value) { overflowAlphaBP = value; } - - const PlayerData& getPlayer(uint64 index) const { return players.get(index); } - void addPlayerDirect(const id& playerId, const QTFRandomValues& randomValues) { players.set(numberOfPlayers++, {playerId, randomValues}); } - - // ---- Private method wrappers (private->protected in this TU) -------------- - ValidateNumbers_output callValidateNumbers(const QPI::QpiContextFunctionCall& qpi, const QTFRandomValues& numbers) const - { - ValidateNumbers_input input{}; - ValidateNumbers_output output{}; - ValidateNumbers_locals locals{}; - - input.numbers = numbers; - ValidateNumbers(qpi, *this, input, output, locals); - return output; - } - - GetRandomValues_output callGetRandomValues(const QPI::QpiContextFunctionCall& qpi, uint64 seed) const - { - GetRandomValues_input input{}; - GetRandomValues_output output{}; - GetRandomValues_locals locals{}; - - input.seed = seed; - GetRandomValues(qpi, *this, input, output, locals); - return output; - } - - CountMatches_output callCountMatches(const QPI::QpiContextFunctionCall& qpi, const QTFRandomValues& playerValues, - const QTFRandomValues& winningValues) const - { - CountMatches_input input{}; - CountMatches_output output{}; - CountMatches_locals locals{}; - - input.playerValues = playerValues; - input.winningValues = winningValues; - CountMatches(qpi, *this, input, output, locals); - return output; - } - - CheckContractBalance_output callCheckContractBalance(const QPI::QpiContextFunctionCall& qpi, uint64 expectedRevenue) const - { - CheckContractBalance_input input{}; - CheckContractBalance_output output{}; - CheckContractBalance_locals locals{}; - - input.expectedRevenue = expectedRevenue; - CheckContractBalance(qpi, *this, input, output, locals); - return output; - } - - PowerFixedPoint_output callPowerFixedPoint(const QPI::QpiContextFunctionCall& qpi, uint64 base, uint64 exp) const - { - PowerFixedPoint_input input{}; - PowerFixedPoint_output output{}; - PowerFixedPoint_locals locals{}; - - input.base = base; - input.exp = exp; - PowerFixedPoint(qpi, *this, input, output, locals); - return output; - } - - CalculateExpectedRoundsToK4_output callCalculateExpectedRoundsToK4(const QPI::QpiContextFunctionCall& qpi, uint64 N) const - { - CalculateExpectedRoundsToK4_input input{}; - CalculateExpectedRoundsToK4_output output{}; - CalculateExpectedRoundsToK4_locals locals{}; - - input.N = N; - CalculateExpectedRoundsToK4(qpi, *this, input, output, locals); - return output; - } - - CalcReserveTopUp_output callCalcReserveTopUp(const QPI::QpiContextFunctionCall& qpi, uint64 totalQRPBalance, uint64 needed, - uint64 perWinnerCapTotal, uint64 ticketPrice) const - { - CalcReserveTopUp_input input{}; - CalcReserveTopUp_output output{}; - CalcReserveTopUp_locals locals{}; - - input.totalQRPBalance = totalQRPBalance; - input.needed = needed; - input.perWinnerCapTotal = perWinnerCapTotal; - input.ticketPrice = ticketPrice; - CalcReserveTopUp(qpi, *this, input, output, locals); - return output; - } - - CalculatePrizePools_output callCalculatePrizePools(const QPI::QpiContextFunctionCall& qpi, uint64 revenue, bit applyFRRake) const - { - CalculatePrizePools_input input{}; - CalculatePrizePools_output output{}; - CalculatePrizePools_locals locals{}; - - input.revenue = revenue; - input.applyFRRake = applyFRRake; - CalculatePrizePools(qpi, *this, input, output, locals); - return output; - } - - CalculateBaseGain_output callCalculateBaseGain(const QPI::QpiContextFunctionCall& qpi, uint64 revenue, uint64 winnersBlock) const - { - CalculateBaseGain_input input{}; - CalculateBaseGain_output output{}; - CalculateBaseGain_locals locals{}; - - input.revenue = revenue; - input.winnersBlock = winnersBlock; - CalculateBaseGain(qpi, *this, input, output, locals); - return output; - } - - CalculateExtraRedirectBP_output callCalculateExtraRedirectBP(const QPI::QpiContextFunctionCall& qpi, uint64 N, uint64 delta, uint64 revenue, - uint64 baseGain) const - { - CalculateExtraRedirectBP_input input{}; - CalculateExtraRedirectBP_output output{}; - CalculateExtraRedirectBP_locals locals{}; - - input.N = N; - input.delta = delta; - input.revenue = revenue; - input.baseGain = baseGain; - CalculateExtraRedirectBP(qpi, *this, input, output, locals); - return output; - } - - void callReturnAllTickets(const QPI::QpiContextProcedureCall& qpi) - { - ReturnAllTickets_input input{}; - ReturnAllTickets_output output{}; - ReturnAllTickets_locals locals{}; - - ReturnAllTickets(qpi, *this, input, output, locals); - } - - ProcessTierPayout_output callProcessTierPayout(const QPI::QpiContextProcedureCall& qpi, uint64 floorPerWinner, uint64 winnerCount, - uint64 payoutPool, uint64 perWinnerCap, uint64 totalQRPBalance, uint64 ticketPrice) - { - ProcessTierPayout_input input{}; - ProcessTierPayout_output output{}; - ProcessTierPayout_locals locals{}; - - input.floorPerWinner = floorPerWinner; - input.winnerCount = winnerCount; - input.payoutPool = payoutPool; - input.perWinnerCap = perWinnerCap; - input.totalQRPBalance = totalQRPBalance; - input.ticketPrice = ticketPrice; - ProcessTierPayout(qpi, *this, input, output, locals); - return output; - } -}; - -class ContractTestingQTF : protected ContractTesting -{ -public: - ContractTestingQTF() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QRP); - INIT_CONTRACT(RL); - INIT_CONTRACT(QTF); - - // Initialize QRP first (QTF depends on it for reserve operations) - callSystemProcedure(QRP_CONTRACT_INDEX, INITIALIZE); - // Initialize RL (RandomLottery contract) - callSystemProcedure(RL_CONTRACT_INDEX, INITIALIZE); - // Initialize QTF - system.epoch = contractDescriptions[QTF_CONTRACT_INDEX].constructionEpoch; - callSystemProcedure(QTF_CONTRACT_INDEX, INITIALIZE); - } - - // Access internal contract state - QTFChecker* state() { return reinterpret_cast(contractStates[QTF_CONTRACT_INDEX]); } - - id qtfSelf() { return id(QTF_CONTRACT_INDEX, 0, 0, 0); } - id qrpSelf() { return id(QRP_CONTRACT_INDEX, 0, 0, 0); } - void addPlayerDirect(const id& playerId, const QTFRandomValues& randomValues) { state()->addPlayerDirect(playerId, randomValues); } - - // Public function wrappers - QTF::GetTicketPrice_output getTicketPrice() - { - QTF::GetTicketPrice_input input{}; - QTF::GetTicketPrice_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_TICKET_PRICE, input, output); - return output; - } - - QTF::GetNextEpochData_output getNextEpochData() - { - QTF::GetNextEpochData_input input{}; - QTF::GetNextEpochData_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_NEXT_EPOCH_DATA, input, output); - return output; - } - - QTF::GetWinnerData_output getWinnerData() - { - QTF::GetWinnerData_input input{}; - QTF::GetWinnerData_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_WINNER_DATA, input, output); - return output; - } - - QTF::GetPools_output getPools() - { - QTF::GetPools_input input{}; - QTF::GetPools_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_POOLS, input, output); - return output; - } - - QTF::GetSchedule_output getSchedule() - { - QTF::GetSchedule_input input{}; - QTF::GetSchedule_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_SCHEDULE, input, output); - return output; - } - - QTF::GetDrawHour_output getDrawHour() - { - QTF::GetDrawHour_input input{}; - QTF::GetDrawHour_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_DRAW_HOUR, input, output); - return output; - } - - QTF::GetState_output getStateInfo() - { - QTF::GetState_input input{}; - QTF::GetState_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_STATE, input, output); - return output; - } - - QTF::GetFees_output getFees() - { - QTF::GetFees_input input{}; - QTF::GetFees_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_FEES, input, output); - return output; - } - - QTF::EstimatePrizePayouts_output estimatePrizePayouts(uint64 k2WinnerCount, uint64 k3WinnerCount) - { - QTF::EstimatePrizePayouts_input input{}; - input.k2WinnerCount = k2WinnerCount; - input.k3WinnerCount = k3WinnerCount; - QTF::EstimatePrizePayouts_output output{}; - callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_ESTIMATE_PRIZE_PAYOUTS, input, output); - return output; - } - - // Procedure wrappers - QTF::BuyTicket_output buyTicket(const id& user, uint64 reward, const QTFRandomValues& numbers) - { - QTF::BuyTicket_input input{}; - input.randomValues = numbers; - QTF::BuyTicket_output output{}; - if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_BUY_TICKET, input, output, user, reward)) - { - output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); - } - return output; - } - - QTF::SetPrice_output setPrice(const id& invocator, uint64 newPrice) - { - QTF::SetPrice_input input{}; - input.newPrice = newPrice; - QTF::SetPrice_output output{}; - if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_PRICE, input, output, invocator, 0)) - { - output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); - } - return output; - } - - QTF::SetSchedule_output setSchedule(const id& invocator, uint8 newSchedule) - { - QTF::SetSchedule_input input{}; - input.newSchedule = newSchedule; - QTF::SetSchedule_output output{}; - if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_SCHEDULE, input, output, invocator, 0)) - { - output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); - } - return output; - } - - QTF::SetTargetJackpot_output setTargetJackpot(const id& invocator, uint64 newTarget) - { - QTF::SetTargetJackpot_input input{}; - input.newTargetJackpot = newTarget; - QTF::SetTargetJackpot_output output{}; - if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_TARGET_JACKPOT, input, output, invocator, 0)) - { - output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); - } - return output; - } - - QTF::SetDrawHour_output setDrawHour(const id& invocator, uint8 newHour) - { - QTF::SetDrawHour_input input{}; - input.newDrawHour = newHour; - QTF::SetDrawHour_output output{}; - if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_DRAW_HOUR, input, output, invocator, 0)) - { - output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); - } - return output; - } - - // System procedure wrappers - void beginEpoch() { callSystemProcedure(QTF_CONTRACT_INDEX, BEGIN_EPOCH); } - void endEpoch() { callSystemProcedure(QTF_CONTRACT_INDEX, END_EPOCH); } - void beginTick() { callSystemProcedure(QTF_CONTRACT_INDEX, BEGIN_TICK); } - - void setDateTime(uint16 year, uint8 month, uint8 day, uint8 hour) - { - updateTime(); - utcTime.Year = year; - utcTime.Month = month; - utcTime.Day = day; - utcTime.Hour = hour; - utcTime.Minute = 0; - utcTime.Second = 0; - utcTime.Nanosecond = 0; - updateQpiTime(); - } - - void forceBeginTick() - { - system.tick = system.tick + (RL_TICK_UPDATE_PERIOD - (system.tick % RL_TICK_UPDATE_PERIOD)); - beginTick(); - } - - void beginEpochWithDate(uint16 year, uint8 month, uint8 day, uint8 hour = 12) - { - setDateTime(year, month, day, hour); - beginEpoch(); - } - - void beginEpochWithValidTime() { beginEpochWithDate(2025, 1, 20); } - - // Force schedule mask directly in state - void forceSchedule(uint8 scheduleMask) { state()->setScheduleMask(scheduleMask); } - - void forceFRDisabledForBaseline() - { - state()->setFrActive(false); - state()->setFrRoundsSinceK4(QTF_FR_POST_K4_WINDOW_ROUNDS); - state()->setOverflowAlphaBP(QTF_BASELINE_OVERFLOW_ALPHA_BP); - } - - void forceFREnabledWithinWindow(uint16 roundsSinceK4 = 1) - { - state()->setFrActive(true); - state()->setFrRoundsSinceK4(roundsSinceK4); - } - - void startAnyDayEpoch() - { - forceSchedule(QTF_ANY_DAY_SCHEDULE); - beginEpochWithValidTime(); - } - - // Trigger a tick that performs the draw (time is set to a scheduled day and hour). - void triggerDrawTick() - { - constexpr uint16 y = 2025; - constexpr uint8 m = 1; - constexpr uint8 d = 10; - setDateTime(y, m, d, 12); - __pauseLogMessage(); - forceBeginTick(); - } - - // Helper to create valid random values - QTFRandomValues makeValidNumbers(uint8 n1, uint8 n2, uint8 n3, uint8 n4) - { - QTFRandomValues values; - values.set(0, n1); - values.set(1, n2); - values.set(2, n3); - values.set(3, n4); - return values; - } - - // Fund user and buy a ticket - void fundAndBuyTicket(const id& user, uint64 ticketPrice, const QTFRandomValues& numbers) - { - increaseEnergy(user, ticketPrice * 2); - const QTF::BuyTicket_output out = buyTicket(user, ticketPrice, numbers); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - } - - // Set prevSpectrumDigest for deterministic random number generation - // This allows tests to predict winning numbers by fixing the RNG seed - void setPrevSpectrumDigest(const m256i& digest) { etalonTick.prevSpectrumDigest = digest; } - - void drawWithDigest(const m256i& digest) - { - setPrevSpectrumDigest(digest); - triggerDrawTick(); - } - - // Compute the winning numbers that would be generated for a given prevSpectrumDigest. - // Uses the contract GetRandomValues() implementation (so tests don't duplicate RNG logic). - // Returns values in generation order (not sorted). - QTFRandomValues computeWinningNumbersForDigest(const m256i& digest) - { - m256i hashResult; - KangarooTwelve((const uint8*)&digest, sizeof(m256i), (uint8*)&hashResult, sizeof(m256i)); - const uint64 seed = hashResult.m256i_u64[0]; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - const auto out = state()->callGetRandomValues(qpi, seed); - return out.values; - } - - struct WinningAndLosing - { - QTFRandomValues winning; - QTFRandomValues losing; - }; - - QTFRandomValues makeLosingNumbers(const QTFRandomValues& winningNumbers) - { - bool isWinning[31] = {}; - for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) - { - isWinning[winningNumbers.get(i)] = true; - } - - QTFRandomValues losingNumbers; - uint64 outIndex = 0; - for (uint8 candidate = 1; candidate <= QTF_MAX_RANDOM_VALUE && outIndex < QTF_RANDOM_VALUES_COUNT; ++candidate) - { - if (!isWinning[candidate]) - { - losingNumbers.set(outIndex++, candidate); - } - } - EXPECT_EQ(outIndex, static_cast(QTF_RANDOM_VALUES_COUNT)); - return losingNumbers; - } - - WinningAndLosing computeWinningAndLosing(const m256i& digest) - { - WinningAndLosing out; - out.winning = computeWinningNumbersForDigest(digest); - out.losing = makeLosingNumbers(out.winning); - return out; - } - - void buyRandomTickets(uint64 count, uint64 ticketPrice, const QTFRandomValues& numbers) - { - for (uint64 i = 0; i < count; ++i) - { - const id user = id::randomValue(); - fundAndBuyTicket(user, ticketPrice, numbers); - } - } - - // Create a ticket that matches exactly `matchCount` numbers with `winningNumbers`. - // `variant` makes it deterministic to generate multiple distinct tickets for the same winning set. - // Guarantees values are unique and in [1..30]. - QTFRandomValues makeNumbersWithExactMatches(const QTFRandomValues& winningNumbers, uint8 matchCount, uint8 variant = 0) - { - EXPECT_LE(matchCount, static_cast(QTF_RANDOM_VALUES_COUNT)); - - bool isWinning[31] = {}; - bool used[31] = {}; - for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) - { - const uint8 v = winningNumbers.get(i); - EXPECT_GE(v, 1u); - EXPECT_LE(v, QTF_MAX_RANDOM_VALUE); - EXPECT_FALSE(isWinning[v]) << "winningNumbers must be unique"; - isWinning[v] = true; - } - - QTFRandomValues ticket; - uint64 outIndex = 0; - - // Take `matchCount` winning numbers as the matches (variant-dependent, wrap around 4). - for (uint8 i = 0; i < matchCount; ++i) - { - const uint8 v = winningNumbers.get((variant + i) % QTF_RANDOM_VALUES_COUNT); - used[v] = true; - ticket.set(outIndex++, v); - } - - // Fill the remaining positions with non-winning numbers. - const uint8 start = static_cast((variant * 7) % QTF_MAX_RANDOM_VALUE + 1); - for (uint8 step = 0; step < QTF_MAX_RANDOM_VALUE && outIndex < QTF_RANDOM_VALUES_COUNT; ++step) - { - const uint8 candidate = static_cast(((start - 1 + step) % QTF_MAX_RANDOM_VALUE) + 1); - if (!isWinning[candidate] && !used[candidate]) - { - used[candidate] = true; - ticket.set(outIndex++, candidate); - } - } - - EXPECT_EQ(outIndex, static_cast(QTF_RANDOM_VALUES_COUNT)); - - // Verify exact overlap count and uniqueness (debug safety for tests). - uint64 overlap = 0; - std::set uniqueValues; - for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) - { - const uint8 v = ticket.get(i); - EXPECT_GE(v, 1u); - EXPECT_LE(v, QTF_MAX_RANDOM_VALUE); - uniqueValues.insert(v); - if (isWinning[v]) - { - ++overlap; - } - } - EXPECT_EQ(uniqueValues.size(), static_cast(QTF_RANDOM_VALUES_COUNT)); - EXPECT_EQ(overlap, static_cast(matchCount)); - - return ticket; - } - - QTFRandomValues makeK2Numbers(const QTFRandomValues& winningNumbers, uint8 variant = 0) - { - return makeNumbersWithExactMatches(winningNumbers, 2, variant); - } - QTFRandomValues makeK3Numbers(const QTFRandomValues& winningNumbers, uint8 variant = 0) - { - return makeNumbersWithExactMatches(winningNumbers, 3, variant); - } -}; - -// ============================================================================ -// PRIVATE METHOD TESTS -// ============================================================================ - -TEST(ContractQThirtyFour_Private, CountMatches_CountsOverlappingNumbers) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - // Include values > 8 to cover the full [1..30] bitmask range. - const QTFRandomValues player = ctl.makeValidNumbers(1, 16, 29, 30); - const QTFRandomValues winning = ctl.makeValidNumbers(16, 29, 2, 3); - const auto out = ctl.state()->callCountMatches(qpi, player, winning); - EXPECT_EQ(out.matches, 2); -} - -TEST(ContractQThirtyFour_Private, ValidateNumbers_WorksForValidDuplicateAndRangeErrors) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const QTFRandomValues ok = ctl.makeValidNumbers(1, 2, 3, 4); - EXPECT_TRUE(ctl.state()->callValidateNumbers(qpi, ok).isValid); - - QTFRandomValues dup = ctl.makeValidNumbers(1, 2, 3, 4); - dup.set(3, 2); - EXPECT_FALSE(ctl.state()->callValidateNumbers(qpi, dup).isValid); - - QTFRandomValues outOfRange = ctl.makeValidNumbers(1, 2, 3, 4); - outOfRange.set(2, 31); - EXPECT_FALSE(ctl.state()->callValidateNumbers(qpi, outOfRange).isValid); -} - -TEST(ContractQThirtyFour_Private, GetRandomValues_IsDeterministicAndUniqueInRange) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const uint64 seed = 0x123456789ABCDEF0ULL; - const auto out1 = ctl.state()->callGetRandomValues(qpi, seed); - const auto out2 = ctl.state()->callGetRandomValues(qpi, seed); - EXPECT_TRUE(valuesEqual(out1.values, out2.values)); - - std::set seen; - for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) - { - const uint8 v = out1.values.get(i); - EXPECT_GE(v, 1); - EXPECT_LE(v, QTF_MAX_RANDOM_VALUE); - seen.insert(v); - EXPECT_EQ(out1.values.get(i), out2.values.get(i)); - } - EXPECT_EQ(seen.size(), static_cast(QTF_RANDOM_VALUES_COUNT)); -} - -TEST(ContractQThirtyFour_Private, CheckContractBalance_UsesIncomingMinusOutgoing) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const uint64 balance = 123456; - increaseEnergy(ctl.qtfSelf(), balance); - - const auto outExact = ctl.state()->callCheckContractBalance(qpi, balance); - EXPECT_TRUE(outExact.hasEnough); - EXPECT_EQ(outExact.actualBalance, balance); - - const auto outTooHigh = ctl.state()->callCheckContractBalance(qpi, balance + 1); - EXPECT_FALSE(outTooHigh.hasEnough); - EXPECT_EQ(outTooHigh.actualBalance, balance); -} - -TEST(ContractQThirtyFour_Private, PowerFixedPoint_ComputesFastExponentiationInFixedPoint) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - // 0.5^2 = 0.25 - const auto out025 = ctl.state()->callPowerFixedPoint(qpi, QTF_FIXED_POINT_SCALE / 2, 2); - EXPECT_EQ(out025.result, QTF_FIXED_POINT_SCALE / 4); - - // 2.0^3 = 8.0 - const auto out8 = ctl.state()->callPowerFixedPoint(qpi, 2 * QTF_FIXED_POINT_SCALE, 3); - EXPECT_EQ(out8.result, 8 * QTF_FIXED_POINT_SCALE); -} - -TEST(ContractQThirtyFour_Private, CalculateExpectedRoundsToK4_HandlesEdgeCaseAndMonotonicity) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const auto out0 = ctl.state()->callCalculateExpectedRoundsToK4(qpi, 0); - EXPECT_EQ(out0.expectedRounds, UINT64_MAX); - - const auto out1 = ctl.state()->callCalculateExpectedRoundsToK4(qpi, 1); - const auto out100 = ctl.state()->callCalculateExpectedRoundsToK4(qpi, 100); - EXPECT_GT(out1.expectedRounds, 0ULL); - EXPECT_GT(out100.expectedRounds, 0ULL); - EXPECT_LE(out1.expectedRounds, QTF_FIXED_POINT_SCALE); - EXPECT_LE(out100.expectedRounds, QTF_FIXED_POINT_SCALE); - EXPECT_GT(out1.expectedRounds, out100.expectedRounds); -} - -TEST(ContractQThirtyFour_Private, CalcReserveTopUp_RespectsSoftFloorPerRoundAndPerWinnerCaps) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const uint64 P = 1000000ULL; - - // Below soft floor => nothing can be topped up. - { - const uint64 softFloor = smul(P, QTF_RESERVE_SOFT_FLOOR_MULT); - const auto out = ctl.state()->callCalcReserveTopUp(qpi, softFloor - 1, 1000ULL, 1000000000ULL, P); - EXPECT_EQ(out.topUpAmount, 0ULL); - } - - // Soft floor binds availableAboveFloor and per-round is 10% of total. - { - const auto out = ctl.state()->callCalcReserveTopUp(qpi, 25000000ULL, 10000000ULL, 1000000000ULL, P); - EXPECT_EQ(out.topUpAmount, 2500000ULL); - } - - // Per-winner cap binds. - { - const auto out = ctl.state()->callCalcReserveTopUp(qpi, 1000000000ULL, 50000000ULL, 1000000ULL, P); - EXPECT_EQ(out.topUpAmount, 1000000ULL); - } - - // Needed is below all caps. - { - const auto out = ctl.state()->callCalcReserveTopUp(qpi, 1000000000ULL, 12345ULL, 1000000000ULL, P); - EXPECT_EQ(out.topUpAmount, 12345ULL); - } -} - -TEST(ContractQThirtyFour_Private, CalculatePrizePools_MatchesFeeAndRakeMath) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const auto fees = ctl.getFees(); - ASSERT_NE(fees.winnerFeePercent, 0); - - const uint64 revenue = 1000000ULL; - const uint64 winnersBlockBeforeRake = (revenue * static_cast(fees.winnerFeePercent)) / 100ULL; - - { - const auto out = ctl.state()->callCalculatePrizePools(qpi, revenue, false); - EXPECT_EQ(out.winnersRake, 0ULL); - EXPECT_EQ(out.winnersBlock, winnersBlockBeforeRake); - EXPECT_EQ(out.k3Pool, (out.winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000ULL); - EXPECT_EQ(out.k2Pool, (out.winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000ULL); - } - - { - const auto out = ctl.state()->callCalculatePrizePools(qpi, revenue, true); - const uint64 expectedRake = (winnersBlockBeforeRake * QTF_FR_WINNERS_RAKE_BP) / 10000ULL; - EXPECT_EQ(out.winnersRake, expectedRake); - EXPECT_EQ(out.winnersBlock, winnersBlockBeforeRake - expectedRake); - EXPECT_EQ(out.k3Pool, (out.winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000ULL); - EXPECT_EQ(out.k2Pool, (out.winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000ULL); - } -} - -TEST(ContractQThirtyFour_Private, CalculateBaseGain_FollowsConfiguredRedirectsAndOverflowBias) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - const uint64 revenue = 1000000ULL; - const uint64 winnersBlock = 680000ULL; - - const auto out = ctl.state()->callCalculateBaseGain(qpi, revenue, winnersBlock); - EXPECT_EQ(out.baseGain, 118600ULL); -} - -TEST(ContractQThirtyFour_Private, CalculateExtraRedirectBP_ReturnsZeroOrClampsToMax) -{ - ContractTestingQTF ctl; - - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - - // Early exits - EXPECT_EQ(ctl.state()->callCalculateExtraRedirectBP(qpi, 0, 1, 1, 0).extraBP, 0ULL); - EXPECT_EQ(ctl.state()->callCalculateExtraRedirectBP(qpi, 1, 0, 1, 0).extraBP, 0ULL); - EXPECT_EQ(ctl.state()->callCalculateExtraRedirectBP(qpi, 1, 1, 0, 0).extraBP, 0ULL); - - // Clamp to max under large deficit. - { - const uint64 revenue = 1000000ULL; - const uint64 delta = revenue * 1000ULL; - const auto out = ctl.state()->callCalculateExtraRedirectBP(qpi, 100, delta, revenue, 0); - EXPECT_EQ(out.extraBP, QTF_FR_EXTRA_MAX_BP); - } - - // Base gain already covers required gain -> zero. - { - const auto out = ctl.state()->callCalculateExtraRedirectBP(qpi, 100, 1000ULL, 1000000ULL, 2000ULL); - EXPECT_EQ(out.extraBP, 0ULL); - } -} - -TEST(ContractQThirtyFour_Private, ProcessTierPayout_ComputesPayoutAndOptionalTopUp) -{ - ContractTestingQTF ctl; - - const id originator = id::randomValue(); - QpiContextUserProcedureCall qpi(QTF_CONTRACT_INDEX, originator, 0); - primeQpiProcedureContext(qpi, static_cast(ctl.state()->getDrawHourInternal())); - - // No winners -> all overflow. - { - const auto out = ctl.state()->callProcessTierPayout(qpi, 50, 0, 123, 100, 0, 1000000ULL); - EXPECT_EQ(out.perWinnerPayout, 0ULL); - EXPECT_EQ(out.overflow, 123ULL); - EXPECT_EQ(out.topUpReceived, 0ULL); - } - - // Top-up from QRP to meet floor. - { - const uint64 qrpBalanceBefore = 1000000000ULL; - increaseEnergy(ctl.qrpSelf(), qrpBalanceBefore); - - const uint64 qtfBalanceBefore = getBalance(ctl.qtfSelf()); - const uint64 qrpBalanceBeforeActual = getBalance(ctl.qrpSelf()); - - const auto out = ctl.state()->callProcessTierPayout(qpi, 50, 2, 10, 100, qrpBalanceBeforeActual, 1000000ULL); - EXPECT_EQ(out.perWinnerPayout, 50ULL); - EXPECT_EQ(out.overflow, 0ULL); - EXPECT_EQ(out.topUpReceived, 90ULL); - - EXPECT_EQ(getBalance(ctl.qtfSelf()), qtfBalanceBefore + 90); - EXPECT_EQ(getBalance(ctl.qrpSelf()), qrpBalanceBeforeActual - 90); - } - - // Per-winner cap applies and leaves overflow. - { - const uint64 P = 1000000ULL; - const uint64 cap = smul(P, QTF_TOPUP_PER_WINNER_CAP_MULT); - const auto out = ctl.state()->callProcessTierPayout(qpi, div(P, 2), 1, sadd(cap, 1234ULL), cap, 0, P); - EXPECT_EQ(out.perWinnerPayout, cap); - EXPECT_EQ(out.topUpReceived, 0ULL); - EXPECT_EQ(out.overflow, 1234ULL); - } -} - -TEST(ContractQThirtyFour_Private, ReturnAllTickets_RefundsEachPlayerAndClearsViaSettleEpochRevenueZeroBranch) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - const id originator = id::randomValue(); - QpiContextUserProcedureCall qpi(QTF_CONTRACT_INDEX, originator, 0); - primeQpiProcedureContext(qpi, static_cast(ctl.state()->getDrawHourInternal())); - - // Setup a few players and refund them. - const uint64 ticketPrice = 10; - ctl.state()->setTicketPriceInternal(ticketPrice); - - const id p1 = id::randomValue(); - const id p2 = id::randomValue(); - const QTFRandomValues n1 = ctl.makeValidNumbers(1, 2, 3, 4); - const QTFRandomValues n2 = ctl.makeValidNumbers(5, 6, 7, 8); - ctl.addPlayerDirect(p1, n1); - ctl.addPlayerDirect(p2, n2); - - increaseEnergy(ctl.qtfSelf(), ticketPrice * 2); - const uint64 balBeforeContract = getBalance(ctl.qtfSelf()); - const uint64 balBeforeP1 = getBalance(p1); - const uint64 balBeforeP2 = getBalance(p2); - - ctl.state()->callReturnAllTickets(qpi); - - EXPECT_EQ(getBalance(p1), balBeforeP1 + ticketPrice); - EXPECT_EQ(getBalance(p2), balBeforeP2 + ticketPrice); - EXPECT_EQ(getBalance(ctl.qtfSelf()), balBeforeContract - (ticketPrice * 2)); - - // Now exercise SettleEpoch revenue==0 branch, which must clear players. - ctl.state()->setTicketPriceInternal(0); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 2ULL); - ctl.triggerDrawTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0ULL); -} - -// ============================================================================ -// BUY TICKET TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, BuyTicket_WhenSellingClosed_RefundsAndFails) -{ - ContractTestingQTF ctl; - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Selling is closed initially (before beginEpoch with valid time) - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 2); - const uint64 balBefore = getBalance(user); - - QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); - const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, nums); - - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::TICKET_SELLING_CLOSED)); - EXPECT_EQ(getBalance(user), balBefore); // Refunded - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, BuyTicket_TooLowPrice_RefundsAndFails) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 5); - const uint64 balBefore = getBalance(user); - - QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); - - // Test with price too low - should fail and refund - const QTF::BuyTicket_output outLow = ctl.buyTicket(user, ticketPrice - 1, nums); - EXPECT_EQ(outLow.returnCode, static_cast(QTF::EReturnCode::INVALID_TICKET_PRICE)); - EXPECT_EQ(getBalance(user), balBefore); // Fully refunded - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, BuyTicket_ZeroPrice_RefundsAndFails) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 2); - const uint64 balBefore = getBalance(user); - - const QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); - - const QTF::BuyTicket_output out = ctl.buyTicket(user, 0, nums); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::INVALID_TICKET_PRICE)); - EXPECT_EQ(getBalance(user), balBefore); // Fully refunded (0) - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, BuyTicket_OverpaidPrice_AcceptsAndReturnsExcess) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - const uint64 overpayment = ticketPrice * 2; // Pay double - increaseEnergy(user, overpayment * 2); - const uint64 balBefore = getBalance(user); - - QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); - - // Test with overpayment - should accept ticket and return excess - const QTF::BuyTicket_output outHigh = ctl.buyTicket(user, overpayment, nums); - EXPECT_EQ(outHigh.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - - // Should have paid exactly ticketPrice, excess returned - const uint64 excess = overpayment - ticketPrice; - EXPECT_EQ(getBalance(user), balBefore - ticketPrice) << "User should pay exactly ticket price, excess returned"; - - // Ticket should be registered - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); -} - -TEST(ContractQThirtyFour, BuyTicket_OverpaidInvalidNumbers_RefundsFull_NoLeak) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - const uint64 overpayment = ticketPrice * 2; - increaseEnergy(user, overpayment * 2); - const uint64 balBefore = getBalance(user); - - // Invalid: out of range - const QTFRandomValues invalidNums = ctl.makeValidNumbers(1, 2, 3, 31); - const QTF::BuyTicket_output out = ctl.buyTicket(user, overpayment, invalidNums); - - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); - EXPECT_EQ(getBalance(user), balBefore) << "Full invocationReward must be refunded once"; - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, BuyTicket_InvalidNumbers_OutOfRange_Fails) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 10); - const uint64 balBefore = getBalance(user); - - // Number 0 is invalid (valid range is 1-30) - QTFRandomValues numsWithZero; - numsWithZero.set(0, 0); - numsWithZero.set(1, 2); - numsWithZero.set(2, 3); - numsWithZero.set(3, 4); - - const QTF::BuyTicket_output out1 = ctl.buyTicket(user, ticketPrice, numsWithZero); - EXPECT_EQ(out1.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); - EXPECT_EQ(getBalance(user), balBefore); - - // Number 31 is invalid (valid range is 1-30) - QTFRandomValues numsOver30; - numsOver30.set(0, 1); - numsOver30.set(1, 2); - numsOver30.set(2, 3); - numsOver30.set(3, 31); - - const QTF::BuyTicket_output out2 = ctl.buyTicket(user, ticketPrice, numsOver30); - EXPECT_EQ(out2.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); - EXPECT_EQ(getBalance(user), balBefore); - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, BuyTicket_DuplicateNumbers_Fails) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 5); - const uint64 balBefore = getBalance(user); - - // Duplicate number 5 - QTFRandomValues dupNums; - dupNums.set(0, 5); - dupNums.set(1, 5); - dupNums.set(2, 10); - dupNums.set(3, 15); - - const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, dupNums); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); - EXPECT_EQ(getBalance(user), balBefore); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, BuyTicket_ValidPurchase_Success) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 2); - const uint64 balBefore = getBalance(user); - - QTFRandomValues nums = ctl.makeValidNumbers(5, 10, 15, 20); - - const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, nums); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - EXPECT_EQ(getBalance(user), balBefore - ticketPrice); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); - - // Verify player data stored correctly - const QTF::PlayerData& player = ctl.state()->getPlayer(0); - EXPECT_EQ(player.player, user); - EXPECT_EQ(player.randomValues.get(0), 5); - EXPECT_EQ(player.randomValues.get(1), 10); - EXPECT_EQ(player.randomValues.get(2), 15); - EXPECT_EQ(player.randomValues.get(3), 20); -} - -TEST(ContractQThirtyFour, BuyTicket_MultiplePlayers_Success) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Add 10 different players - for (int i = 0; i < 10; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = ctl.makeValidNumbers(static_cast(1 + i), static_cast(11 + i), 21, 30); - - ctl.fundAndBuyTicket(user, ticketPrice, nums); - } - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 10u); -} - -TEST(ContractQThirtyFour, BuyTicket_MaxPlayersReached_Fails) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Fill up to max players (1024) - for (uint64 i = 0; i < QTF_MAX_NUMBER_OF_PLAYERS; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i % 27) + 1), static_cast(((i + 1) % 27) + 1), - static_cast(((i + 2) % 27) + 1), static_cast(((i + 3) % 27) + 1)); - - // Only fund and buy; we expect all to succeed until max - increaseEnergy(user, ticketPrice * 2); - const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, nums); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - } - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), QTF_MAX_NUMBER_OF_PLAYERS); - - // Try one more - should fail - const id extraUser = id::randomValue(); - increaseEnergy(extraUser, ticketPrice * 2); - const uint64 balBefore = getBalance(extraUser); - QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); - - const QTF::BuyTicket_output out = ctl.buyTicket(extraUser, ticketPrice, nums); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::MAX_PLAYERS_REACHED)); - EXPECT_EQ(getBalance(extraUser), balBefore); // Refunded -} - -TEST(ContractQThirtyFour, BuyTicket_SamePlayerMultipleTickets_Allowed) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 10); - - // Same player buys multiple tickets with different numbers - QTFRandomValues nums1 = ctl.makeValidNumbers(1, 2, 3, 4); - QTFRandomValues nums2 = ctl.makeValidNumbers(5, 6, 7, 8); - QTFRandomValues nums3 = ctl.makeValidNumbers(9, 10, 11, 12); - - EXPECT_EQ(ctl.buyTicket(user, ticketPrice, nums1).returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - EXPECT_EQ(ctl.buyTicket(user, ticketPrice, nums2).returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - EXPECT_EQ(ctl.buyTicket(user, ticketPrice, nums3).returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 3u); -} - -// ============================================================================ -// CONFIGURATION CHANGE TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, SetPrice_AccessControl) -{ - ContractTestingQTF ctl; - - const uint64 oldPrice = ctl.state()->getTicketPriceInternal(); - const uint64 newPrice = oldPrice * 2; - - // Random user should be denied - const id randomUser = id::randomValue(); - increaseEnergy(randomUser, 1); - const QTF::SetPrice_output outDenied = ctl.setPrice(randomUser, newPrice); - EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); - - // Price unchanged - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); -} - -TEST(ContractQThirtyFour, SetPrice_ZeroNotAllowed) -{ - ContractTestingQTF ctl; - increaseEnergy(ctl.state()->team(), 1); - - const uint64 oldPrice = ctl.state()->getTicketPriceInternal(); - const QTF::SetPrice_output outInvalid = ctl.setPrice(ctl.state()->team(), 0); - EXPECT_EQ(outInvalid.returnCode, static_cast(QTF::EReturnCode::INVALID_TICKET_PRICE)); - - // Price unchanged - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); -} - -TEST(ContractQThirtyFour, SetPrice_AppliesAfterEndEpoch) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - increaseEnergy(ctl.state()->team(), 1); - - const uint64 oldPrice = ctl.state()->getTicketPriceInternal(); - const uint64 newPrice = oldPrice * 3; - - const QTF::SetPrice_output outOk = ctl.setPrice(ctl.state()->team(), newPrice); - EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - - // Queued in NextEpochData - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newTicketPrice, newPrice); - - // Old price still active - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); - - // Apply after END_EPOCH - ctl.endEpoch(); - ctl.beginEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); - - // NextEpochData cleared - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newTicketPrice, 0u); -} - -TEST(ContractQThirtyFour, SetSchedule_AccessControl) -{ - ContractTestingQTF ctl; - - const id randomUser = id::randomValue(); - increaseEnergy(randomUser, 1); - const QTF::SetSchedule_output outDenied = ctl.setSchedule(randomUser, QTF_ANY_DAY_SCHEDULE); - EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); -} - -TEST(ContractQThirtyFour, SetSchedule_ZeroNotAllowed) -{ - ContractTestingQTF ctl; - increaseEnergy(ctl.state()->team(), 1); - - const QTF::SetSchedule_output outInvalid = ctl.setSchedule(ctl.state()->team(), 0); - EXPECT_EQ(outInvalid.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); -} - -TEST(ContractQThirtyFour, SetSchedule_AppliesAfterEndEpoch) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - increaseEnergy(ctl.state()->team(), 1); - - const uint8 newSchedule = 0x7F; // All days - - const QTF::SetSchedule_output outOk = ctl.setSchedule(ctl.state()->team(), newSchedule); - EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - - // Queued - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newSchedule, newSchedule); - - // Apply - ctl.endEpoch(); - ctl.beginEpoch(); - EXPECT_EQ(ctl.getSchedule().schedule, newSchedule); -} - -TEST(ContractQThirtyFour, SetTargetJackpot_AccessControl) -{ - ContractTestingQTF ctl; - - const id randomUser = id::randomValue(); - increaseEnergy(randomUser, 1); - const QTF::SetTargetJackpot_output outDenied = ctl.setTargetJackpot(randomUser, 2000000000ULL); - EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); -} - -TEST(ContractQThirtyFour, SetTargetJackpot_ZeroNotAllowed) -{ - ContractTestingQTF ctl; - increaseEnergy(ctl.state()->team(), 1); - - const QTF::SetTargetJackpot_output outInvalid = ctl.setTargetJackpot(ctl.state()->team(), 0); - EXPECT_EQ(outInvalid.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); -} - -TEST(ContractQThirtyFour, SetTargetJackpot_AppliesAfterEndEpoch) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - increaseEnergy(ctl.state()->team(), 1); - - const uint64 newTarget = 5000000000ULL; - - const QTF::SetTargetJackpot_output outOk = ctl.setTargetJackpot(ctl.state()->team(), newTarget); - EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - - // Queued - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newTargetJackpot, newTarget); - - // Apply - ctl.endEpoch(); - ctl.beginEpoch(); - EXPECT_EQ(ctl.state()->getTargetJackpotInternal(), newTarget); -} - -TEST(ContractQThirtyFour, SetDrawHour_AccessControl) -{ - ContractTestingQTF ctl; - - const id randomUser = id::randomValue(); - increaseEnergy(randomUser, 1); - const QTF::SetDrawHour_output outDenied = ctl.setDrawHour(randomUser, 15); - EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); -} - -TEST(ContractQThirtyFour, SetDrawHour_InvalidValues) -{ - ContractTestingQTF ctl; - increaseEnergy(ctl.state()->team(), 2); - - // 0 is invalid - const QTF::SetDrawHour_output out0 = ctl.setDrawHour(ctl.state()->team(), 0); - EXPECT_EQ(out0.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); - - // 24+ is invalid - const QTF::SetDrawHour_output out24 = ctl.setDrawHour(ctl.state()->team(), 24); - EXPECT_EQ(out24.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); -} - -TEST(ContractQThirtyFour, SetDrawHour_AppliesAfterEndEpoch) -{ - ContractTestingQTF ctl; - ctl.beginEpochWithValidTime(); - increaseEnergy(ctl.state()->team(), 1); - - const uint8 newHour = 18; - - const QTF::SetDrawHour_output outOk = ctl.setDrawHour(ctl.state()->team(), newHour); - EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); - - // Queued - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newDrawHour, newHour); - - // Apply - ctl.endEpoch(); - ctl.beginEpoch(); - EXPECT_EQ(ctl.getDrawHour().drawHour, newHour); -} - -// ============================================================================ -// STATE AND POOLS TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, GetState_NoneThenSelling) -{ - ContractTestingQTF ctl; - - // Initially not selling - EXPECT_EQ(ctl.getStateInfo().currentState, static_cast(QTF::EState::STATE_NONE)); - - // After epoch start with valid time it should sell - ctl.beginEpochWithValidTime(); - EXPECT_EQ(ctl.getStateInfo().currentState, static_cast(QTF::EState::STATE_SELLING)); -} - -TEST(ContractQThirtyFour, GetPools_ReserveReflectsQRPAvailable) -{ - ContractTestingQTF ctl; - - const QTF::GetPools_output poolsBefore = ctl.getPools(); - const uint64 before = poolsBefore.pools.reserve; - - constexpr uint64 qrpFunding = 10'000'000'000ULL; - increaseEnergy(ctl.qrpSelf(), qrpFunding); - - const QTF::GetPools_output poolsAfter = ctl.getPools(); - const uint64 after = poolsAfter.pools.reserve; - - EXPECT_GE(after, before); - EXPECT_GT(after, 0u); - EXPECT_LE(after, before + qrpFunding); -} - -// ============================================================================ -// SETTLEMENT AND PAYOUT TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, Settlement_WithPlayers_FeesDistributed) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - ctl.forceFRDisabledForBaseline(); - - // Fix RNG so we can deterministically avoid winners (and especially k=4). - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x1010101010101010ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const QTF::GetFees_output fees = ctl.getFees(); - constexpr uint64 numPlayers = 10; - - // Ensure RL shares exist so distribution path is exercised deterministically. - const id shareholder1 = id::randomValue(); - const id shareholder2 = id::randomValue(); - constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 3; - constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; - std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; - issueRlSharesTo(rlShares); - - // Verify FR is not active initially (baseline mode) - EXPECT_EQ(ctl.state()->getFrActive(), false); - - // Add players - ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); - - const uint64 totalRevenue = ticketPrice * numPlayers; - const uint64 devBalBefore = getBalance(ctl.state()->team()); - const uint64 sh1Before = getBalance(shareholder1); - const uint64 sh2Before = getBalance(shareholder2); - const uint64 rlBefore = getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)); - const uint64 contractBalBefore = getBalance(ctl.qtfSelf()); - - EXPECT_EQ(contractBalBefore, totalRevenue); - - ctl.drawWithDigest(testDigest); - - EXPECT_EQ(ctl.state()->getFrActive(), false); - - // In baseline mode (FR not active), dev receives full 10% of revenue - // No redirects are applied - const uint64 expectedDevFee = (totalRevenue * fees.teamFeePercent) / 100; - EXPECT_EQ(getBalance(ctl.state()->team()), devBalBefore + expectedDevFee) - << "In baseline mode, dev should receive full " << static_cast(fees.teamFeePercent) << "% of revenue"; - - // Distribution is paid to RL shareholders with flooring to dividendPerShare and payback remainder to RL contract. - const uint64 expectedDistFee = (totalRevenue * fees.distributionFeePercent) / 100; - const uint64 dividendPerShare = expectedDistFee / NUMBER_OF_COMPUTORS; - const uint64 expectedSh1Gain = static_cast(shares1) * dividendPerShare; - const uint64 expectedSh2Gain = static_cast(shares2) * dividendPerShare; - const uint64 expectedPayback = expectedDistFee - (dividendPerShare * NUMBER_OF_COMPUTORS); - EXPECT_EQ(getBalance(shareholder1), sh1Before + expectedSh1Gain); - EXPECT_EQ(getBalance(shareholder2), sh2Before + expectedSh2Gain); - EXPECT_EQ(getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)), rlBefore + expectedPayback); - - // No winners -> winnersOverflow == winnersBlock. In baseline: 50/50 split reserve/jackpot. - const uint64 winnersBlock = (totalRevenue * fees.winnerFeePercent) / 100; - const uint64 reserveAdd = (winnersBlock * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; - const uint64 expectedJackpotAdd = winnersBlock - reserveAdd; - EXPECT_EQ(ctl.state()->getJackpot(), expectedJackpotAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), expectedJackpotAdd) << "Contract balance should match carry (jackpot) after settlement"; - - // Players cleared - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, Settlement_NoPlayers_NoChanges) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - const uint64 jackpotBefore = ctl.state()->getJackpot(); - const QTF::GetWinnerData_output winnersBefore = ctl.getWinnerData(); - - ctl.triggerDrawTick(); - - // No changes when no players - EXPECT_EQ(ctl.state()->getJackpot(), jackpotBefore); - const QTF::GetWinnerData_output winnersAfter = ctl.getWinnerData(); - EXPECT_EQ(winnersAfter.winnerData.winnerCounter, winnersBefore.winnerData.winnerCounter); -} - -TEST(ContractQThirtyFour, Settlement_InsufficientBalance_ClearsPlayersAndAbortsSettlement) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x3030303030303030ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - constexpr uint64 numPlayers = 2; - - ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numPlayers); - - // Drain the contract so CheckContractBalance() fails in SettleEpoch. - const uint64 totalRevenue = ticketPrice * numPlayers; - const int qtfIndex = spectrumIndex(ctl.qtfSelf()); - ASSERT_GE(qtfIndex, 0); - ASSERT_TRUE(decreaseEnergy(qtfIndex, totalRevenue)); - EXPECT_EQ(getBalance(ctl.qtfSelf()), 0); - - ctl.drawWithDigest(testDigest); - - // Even if refunds can't be paid (because we drained balance), the contract must clear the epoch state. - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0ULL); -} - -TEST(ContractQThirtyFour, Settlement_WithPlayers_FeesDistributed_FRMode) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - // Fix RNG so we can deterministically avoid winners (and especially k=4). - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x2020202020202020ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Activate FR mode - ctl.state()->setJackpot(100000000ULL); // Below target - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); - ctl.forceFREnabledWithinWindow(5); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const QTF::GetFees_output fees = ctl.getFees(); - constexpr uint64 numPlayers = 10; - - // Verify FR is active - EXPECT_EQ(ctl.state()->getFrActive(), true); - - // Add players - ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); - - const uint64 totalRevenue = ticketPrice * numPlayers; - const uint64 devBalBefore = getBalance(ctl.state()->team()); - const uint64 contractBalBefore = getBalance(ctl.qtfSelf()); - const uint64 jackpotBefore = ctl.state()->getJackpot(); - const uint64 roundsSinceK4Before = ctl.state()->getFrRoundsSinceK4(); - - EXPECT_EQ(contractBalBefore, totalRevenue); - - ctl.drawWithDigest(testDigest); - - // In FR mode, dev receives less than full 10% of revenue - // Base redirect: 1% of revenue (QTF_FR_DEV_REDIRECT_BP = 100 basis points) - // Possible extra redirect depending on deficit - const uint64 baseDevRedirect = (totalRevenue * QTF_FR_DEV_REDIRECT_BP) / 10000; - - // Full dev fee from revenue split (10%) - const uint64 fullDevFee = (totalRevenue * fees.teamFeePercent) / 100; - - // Actual dev payout = fullDevFee - redirects - // Expected: fullDevFee - at least baseDevRedirect - const uint64 maxExpectedDevPayout = fullDevFee - baseDevRedirect; - - const uint64 actualDevPayout = getBalance(ctl.state()->team()) - devBalBefore; - - // Dev should receive less than full fee (due to redirects to jackpot) - EXPECT_LT(actualDevPayout, fullDevFee) << "In FR mode, dev payout should be reduced by redirects"; - - // Dev should receive at most fullDevFee - baseDevRedirect - EXPECT_LE(actualDevPayout, maxExpectedDevPayout) << "Dev payout should be reduced by at least base redirect (1%)"; - - // Jackpot should have grown (receives redirects) - EXPECT_GT(ctl.state()->getJackpot(), jackpotBefore) << "Jackpot should grow from dev/dist redirects in FR mode"; - - // No k=4 can happen (we buy losing tickets), so counter increments. - EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), roundsSinceK4Before + 1); - - // Players cleared - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, Settlement_JackpotGrowsFromOverflow) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - ctl.forceFRDisabledForBaseline(); - - // Fix RNG so we can deterministically create "no winners" tickets. - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xBADC0FFEE0DDF00DULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const uint64 jackpotBefore = ctl.state()->getJackpot(); - constexpr uint64 numPlayers = 20; - - // Add players - for (uint64 i = 0; i < numPlayers; ++i) - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - - // Calculate expected jackpot growth in baseline mode (FR not active) - const uint64 revenue = ticketPrice * numPlayers; - const QTF::GetFees_output fees = ctl.getFees(); - - // winnersBlock = revenue * winnerFeePercent / 100 (68%) - const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; - - // With no winners, the entire winners block becomes overflow (k2+k3 pools also roll into overflow). - const uint64 winnersOverflow = winnersBlock; - - // In baseline mode: 50% of overflow goes to jackpot, 50% to reserve. - const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; - const uint64 overflowToJackpot = winnersOverflow - reserveAdd; - - // Minimum expected jackpot growth (assuming no k2/k3 winners, all overflow goes to jackpot) - const uint64 minExpectedGrowth = overflowToJackpot; - - ctl.drawWithDigest(testDigest); - - // Verify jackpot growth - const uint64 jackpotAfter = ctl.state()->getJackpot(); - const uint64 actualGrowth = jackpotAfter - jackpotBefore; - - // Deterministic: losing tickets guarantee no winners, so growth should match exactly. - EXPECT_EQ(actualGrowth, minExpectedGrowth) << "Actual growth: " << actualGrowth << ", Expected: " << minExpectedGrowth - << ", Overflow to jackpot (50%): " << overflowToJackpot; - - // Verify the 50% overflow split is working correctly - const uint64 expected50Percent = winnersOverflow / 2; - EXPECT_GE(overflowToJackpot, expected50Percent - 1) << "50% overflow split verification"; - EXPECT_LE(overflowToJackpot, winnersOverflow) << "Overflow to jackpot should not exceed total overflow"; -} - -TEST(ContractQThirtyFour, Settlement_RoundsSinceK4_Increments) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x1111222233334444ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Run several rounds without k=4 win - for (int round = 0; round < 3; ++round) - { - ctl.beginEpochWithValidTime(); - - ctl.buyRandomTickets(5, ticketPrice, nums.losing); - - const uint64 roundsBefore = ctl.state()->getFrRoundsSinceK4(); - ctl.drawWithDigest(testDigest); - - // Deterministic: no ticket matches any winning number, so k=4 cannot occur. - EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), roundsBefore + 1); - } -} - -// ============================================================================ -// FAST-RECOVERY (FR) TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, FR_Activation_WhenBelowTarget) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - // Set jackpot below target to trigger FR - ctl.state()->setJackpot(100000000ULL); // 100M - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target - ctl.state()->setFrRoundsSinceK4(5); // Within post-k4 window - - EXPECT_EQ(ctl.state()->getFrActive(), false); - - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Add players and settle - for (int i = 0; i < 10; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i % 25) + 1), static_cast((i % 25) + 2), - static_cast((i % 25) + 3), static_cast((i % 25) + 4)); - ctl.fundAndBuyTicket(user, ticketPrice, nums); - } - - ctl.triggerDrawTick(); - - // FR should be active since jackpot < target and within window - EXPECT_EQ(ctl.state()->getFrActive(), true); -} - -TEST(ContractQThirtyFour, FR_Deactivation_AfterHysteresis) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x3030303030303030ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Set jackpot at target - ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT); - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); - ctl.state()->setFrActive(true); - ctl.state()->setFrRoundsAtOrAboveTarget(0); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Run rounds at or above target (hysteresis requirement) - for (int round = 0; round < QTF_FR_HYSTERESIS_ROUNDS; ++round) - { - ctl.beginEpochWithValidTime(); - - ctl.buyRandomTickets(5, ticketPrice, nums.losing); - - // Keep jackpot at target (add back what might be paid out) - ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT); - ctl.drawWithDigest(testDigest); - } - - // After 3 rounds at target, FR should deactivate - EXPECT_GE(ctl.state()->getFrRoundsAtOrAboveTarget(), QTF_FR_HYSTERESIS_ROUNDS); - EXPECT_EQ(ctl.state()->getFrActive(), false); -} - -TEST(ContractQThirtyFour, FR_OverflowBias_95PercentToJackpot) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - // Fix RNG so we can deterministically create "no winners" tickets. - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xCAFEBABEDEADBEEFULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Activate FR - ctl.state()->setJackpot(100000000ULL); // Below target - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); - ctl.state()->setFrActive(true); - ctl.state()->setFrRoundsSinceK4(5); - - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const uint64 jackpotBefore = ctl.state()->getJackpot(); - constexpr uint64 numPlayers = 50; - - // Add many players to generate significant overflow - ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); - - // Calculate expected jackpot growth - const uint64 revenue = ticketPrice * numPlayers; - const QTF::GetFees_output fees = ctl.getFees(); - - // winnersBlock = revenue * winnerFeePercent / 100 (68%) - const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; - - // In FR mode: 5% rake from winnersBlock goes to jackpot - const uint64 winnersRake = (winnersBlock * QTF_FR_WINNERS_RAKE_BP) / 10000; - const uint64 winnersBlockAfterRake = winnersBlock - winnersRake; - - // With no winners, the entire winners block after rake becomes overflow (k2+k3 pools also roll into overflow). - const uint64 winnersOverflow = winnersBlockAfterRake; - - // In FR mode: 95% of overflow goes to jackpot, 5% to reserve - const uint64 reserveAdd = (winnersOverflow * QTF_FR_ALPHA_BP) / 10000; - const uint64 overflowToJackpot = winnersOverflow - reserveAdd; - - // Dev and Dist redirects in FR mode: base (1% each) + extra (deficit-driven) - // First calculate base gain to pass to extra redirect calculation - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - const auto baseGainOut = ctl.state()->callCalculateBaseGain(qpi, revenue, winnersBlock); - - // Calculate extra redirect based on deficit - const uint64 delta = ctl.state()->getTargetJackpotInternal() - jackpotBefore; - const auto extraOut = ctl.state()->callCalculateExtraRedirectBP(qpi, numPlayers, delta, revenue, baseGainOut.baseGain); - - // Total redirect BP = base + extra (split 50/50 between dev and dist) - const uint64 devExtraBP = extraOut.extraBP / 2; - const uint64 distExtraBP = extraOut.extraBP - devExtraBP; - const uint64 totalDevRedirectBP = QTF_FR_DEV_REDIRECT_BP + devExtraBP; - const uint64 totalDistRedirectBP = QTF_FR_DIST_REDIRECT_BP + distExtraBP; - - const uint64 devRedirect = (revenue * totalDevRedirectBP) / 10000; - const uint64 distRedirect = (revenue * totalDistRedirectBP) / 10000; - - // Expected jackpot growth (with both base and extra redirects, assuming no k2/k3 winners) - // totalJackpotContribution = overflowToJackpot + winnersRake + devRedirect + distRedirect - const uint64 expectedGrowth = overflowToJackpot + winnersRake + devRedirect + distRedirect; - - ctl.drawWithDigest(testDigest); - - // Verify that jackpot grew by the expected amount - const uint64 actualGrowth = ctl.state()->getJackpot() - jackpotBefore; - - // Deterministic: losing tickets guarantee no winners, so growth should match exactly. - EXPECT_EQ(actualGrowth, expectedGrowth) << "Actual growth: " << actualGrowth << ", Expected: " << expectedGrowth - << ", Overflow to jackpot (95%): " << overflowToJackpot << ", Winners rake: " << winnersRake - << ", Extra redirect BP: " << extraOut.extraBP; - - // Verify the 95% overflow bias is working correctly - // overflowToJackpot should be ~95% of winnersOverflow - const uint64 expected95Percent = (winnersOverflow * 95) / 100; - EXPECT_GE(overflowToJackpot, expected95Percent - 1) << "95% overflow bias verification"; - EXPECT_LE(overflowToJackpot, winnersOverflow) << "Overflow to jackpot should not exceed total overflow"; -} - -// ============================================================================ -// WINNER COUNTING AND TIER TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, WinnerData_RecordsWinners) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // At least one ticket is required, otherwise END_EPOCH returns early and winner values are not generated. - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, ctl.makeValidNumbers(1, 2, 3, 4)); - - ctl.triggerDrawTick(); - - const QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); - expectWinnerValuesValidAndUnique(winnerData); -} - -TEST(ContractQThirtyFour, WinnerData_ResetEachRound) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - // Round 1: force a deterministic k=2 winner so winnerCounter becomes > 0. - m256i digest1 = {}; - digest1.m256i_u64[0] = 0x13579BDF2468ACE0ULL; - const auto nums1 = ctl.computeWinningAndLosing(digest1); - - ctl.beginEpochWithValidTime(); - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - QTFRandomValues k2Numbers = ctl.makeK2Numbers(nums1.winning); - const id k2Winner = id::randomValue(); - ctl.fundAndBuyTicket(k2Winner, ticketPrice, k2Numbers); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); - - ctl.drawWithDigest(digest1); - - const QTF::GetWinnerData_output afterRound1 = ctl.getWinnerData(); - EXPECT_GT(afterRound1.winnerData.winnerCounter, 0u); - - // Round 2: force a deterministic "no winners" round, winnerCounter must reset to 0. - m256i digest2 = {}; - digest2.m256i_u64[0] = 0x0F0E0D0C0B0A0908ULL; - const auto nums2 = ctl.computeWinningAndLosing(digest2); - - ctl.beginEpochWithValidTime(); - ctl.buyRandomTickets(5, ticketPrice, nums2.losing); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 5u); - - ctl.drawWithDigest(digest2); - - const QTF::GetWinnerData_output afterRound2 = ctl.getWinnerData(); - EXPECT_EQ(afterRound2.winnerData.winnerCounter, 0u) << "Winner snapshot must reset each round"; -} - -// ============================================================================ -// EDGE CASE TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, BuyTicket_ValidNumberSelections_EdgeCases_Success) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - static constexpr uint8 cases[][4] = { - {1, 2, 29, 30}, // boundary - {15, 16, 17, 18}, // consecutive - {27, 28, 29, 30}, // highest - {1, 2, 3, 4}, // lowest - }; - - for (uint64 i = 0; i < (sizeof(cases) / sizeof(cases[0])); ++i) - { - const id user = id::randomValue(); - const QTFRandomValues nums = ctl.makeValidNumbers(cases[i][0], cases[i][1], cases[i][2], cases[i][3]); - ctl.fundAndBuyTicket(user, ticketPrice, nums); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), i + 1); - } -} - -// ============================================================================ -// MULTIPLE ROUNDS TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, MultipleRounds_JackpotAccumulates) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x0DDC0FFEE0DDF00DULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - uint64 prevJackpot = 0; - - // Run multiple rounds - for (int round = 0; round < 5; ++round) - { - ctl.beginEpochWithValidTime(); - - ctl.buyRandomTickets(10, ticketPrice, nums.losing); - ctl.drawWithDigest(testDigest); - - // Jackpot should increase each round (no k=4 winners in this test) - const uint64 currentJackpot = ctl.state()->getJackpot(); - EXPECT_GT(currentJackpot, prevJackpot) << "Round " << round << ": jackpot should grow"; - - // Track for next iteration - prevJackpot = currentJackpot; - } -} - -TEST(ContractQThirtyFour, MultipleRounds_StateResetsCorrectly) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - for (int round = 0; round < 3; ++round) - { - ctl.beginEpochWithValidTime(); - - // Add different number of players each round - const int playersThisRound = 5 + round * 3; - for (int i = 0; i < playersThisRound; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i + round) % 27 + 1), static_cast((i + round + 5) % 27 + 1), - static_cast((i + round + 10) % 27 + 1), static_cast((i + round + 15) % 27 + 1)); - ctl.fundAndBuyTicket(user, ticketPrice, nums); - } - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), static_cast(playersThisRound)); - - ctl.triggerDrawTick(); - - // Players should be cleared after each round - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); - } -} - -// ============================================================================ -// POST_INCOMING_TRANSFER TEST -// ============================================================================ - -TEST(ContractQThirtyFour, PostIncomingTransfer_StandardTransaction_Refunded) -{ - ContractTestingQTF ctl; - constexpr uint64 transferAmount = 123456789; - - const id sender = id::randomValue(); - increaseEnergy(sender, transferAmount); - EXPECT_EQ(getBalance(sender), transferAmount); - - const id contractAddress = ctl.qtfSelf(); - EXPECT_EQ(getBalance(contractAddress), 0); - - // Standard transaction should be refunded - notifyContractOfIncomingTransfer(sender, contractAddress, transferAmount, QPI::TransferType::standardTransaction); - - // Amount should be refunded to sender - EXPECT_EQ(getBalance(sender), transferAmount); - EXPECT_EQ(getBalance(contractAddress), 0); -} - -// ============================================================================ -// SCHEDULE AND TIME TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, Schedule_WednesdayAlwaysDraws_IgnoresScheduleMask) -{ - ContractTestingQTF ctl; - - // Exclude Wednesday from schedule mask (e.g., Monday only). - constexpr uint8 mondayOnly = 1 << MONDAY; - ctl.forceSchedule(mondayOnly); - - ctl.beginEpochWithValidTime(); - - const m256i testDigest = {}; - ctl.setPrevSpectrumDigest(testDigest); - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - for (int i = 0; i < 5; ++i) - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 5u); - - // Wednesday should always trigger a draw at/after draw hour, even if schedule mask does not include it. - const uint8 drawHour = ctl.state()->getDrawHourInternal(); - ctl.setDateTime(2025, 1, 15, drawHour); - ctl.forceBeginTick(); - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, Schedule_DrawOnlyOnScheduledDays) -{ - ContractTestingQTF ctl; - - // Set schedule to Wednesday only (default) - constexpr uint8 wednesdayOnly = 1 << WEDNESDAY; - ctl.forceSchedule(wednesdayOnly); - - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Add players - for (int i = 0; i < 5; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = - ctl.makeValidNumbers(static_cast(i + 1), static_cast(i + 5), static_cast(i + 10), static_cast(i + 15)); - ctl.fundAndBuyTicket(user, ticketPrice, nums); - } - - const uint64 playersBefore = ctl.state()->getNumberOfPlayers(); - EXPECT_EQ(playersBefore, 5u); - - // Tuesday 2025-01-14 is not scheduled - should NOT trigger draw - ctl.setDateTime(2025, 1, 14, 12); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), playersBefore); // Unchanged - - // Wednesday 2025-01-15 IS scheduled - should trigger draw - ctl.setDateTime(2025, 1, 15, 12); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); // Cleared after draw -} - -TEST(ContractQThirtyFour, Schedule_DrawAtMostOncePerDay_LastDrawDateStampGuards) -{ - ContractTestingQTF ctl; - - // Use a non-Wednesday scheduled day so selling is re-enabled after the draw. - constexpr uint8 thursdayOnly = 1 << THURSDAY; - ctl.forceSchedule(thursdayOnly); - - ctl.beginEpochWithValidTime(); - - const m256i testDigest = {}; - ctl.setPrevSpectrumDigest(testDigest); - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - - const uint8 drawHour = ctl.state()->getDrawHourInternal(); - - // First draw on Thursday. - ctl.setDateTime(2025, 1, 16, drawHour); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); - - const uint64 jackpotAfterFirst = ctl.state()->getJackpot(); - const QTF::GetWinnerData_output winnersAfterFirst = ctl.getWinnerData(); - - // Buy another ticket on the same date (selling should be open on non-Wednesday). - { - const id user2 = id::randomValue(); - ctl.fundAndBuyTicket(user2, ticketPrice, nums.losing); - } - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); - - // Second tick on the same date must NOT trigger another draw. - ctl.setDateTime(2025, 1, 16, drawHour); - ctl.forceBeginTick(); - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); - EXPECT_EQ(ctl.state()->getJackpot(), jackpotAfterFirst); - const QTF::GetWinnerData_output winnersAfterSecondAttempt = ctl.getWinnerData(); - for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) - { - EXPECT_EQ(winnersAfterSecondAttempt.winnerData.winnerValues.get(i), winnersAfterFirst.winnerData.winnerValues.get(i)); - } - EXPECT_EQ((uint64)winnersAfterSecondAttempt.winnerData.epoch, (uint64)winnersAfterFirst.winnerData.epoch); -} - -TEST(ContractQThirtyFour, DrawHour_NoDrawBeforeScheduledHour) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Add players - for (int i = 0; i < 5; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = - ctl.makeValidNumbers(static_cast(i + 1), static_cast(i + 5), static_cast(i + 10), static_cast(i + 15)); - ctl.fundAndBuyTicket(user, ticketPrice, nums); - } - - const uint8 drawHour = ctl.state()->getDrawHourInternal(); - const uint64 playersBefore = ctl.state()->getNumberOfPlayers(); - - // Before draw hour - should NOT trigger draw - ctl.setDateTime(2025, 1, 15, drawHour - 1); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), playersBefore); - - // At or after draw hour - should trigger draw - ctl.setDateTime(2025, 1, 15, drawHour); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); -} - -TEST(ContractQThirtyFour, DrawHour_WednesdayDrawClosesTicketSelling) -{ - ContractTestingQTF ctl; - - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - ctl.beginEpochWithValidTime(); - - const m256i testDigest = {}; - ctl.setPrevSpectrumDigest(testDigest); - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - - const uint8 drawHour = ctl.state()->getDrawHourInternal(); - ctl.setDateTime(2025, 1, 15, drawHour); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); - - // After a Wednesday draw, selling must remain closed until next epoch. - const id lateBuyer = id::randomValue(); - increaseEnergy(lateBuyer, ticketPrice * 2); - const uint64 before = getBalance(lateBuyer); - const QTF::BuyTicket_output out = ctl.buyTicket(lateBuyer, ticketPrice, nums.losing); - EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::TICKET_SELLING_CLOSED)); - EXPECT_EQ(getBalance(lateBuyer), before); -} - -// ============================================================================ -// PROBABILITY AND COMBINATORICS VERIFICATION -// ============================================================================ - -TEST(ContractQThirtyFour, Combinatorics_P4Denominator) -{ - // Verify the P4 denominator constant matches combinatorics - // C(30,4) = 30! / (4! * 26!) = 27405 - constexpr uint64 numerator = QTF_MAX_RANDOM_VALUE * 29 * 28 * 27; - constexpr uint64 denominator = QTF_RANDOM_VALUES_COUNT * 3 * 2 * 1; - constexpr uint64 expected = numerator / denominator; - - EXPECT_EQ(expected, QTF_P4_DENOMINATOR); - EXPECT_EQ(QTF_P4_DENOMINATOR, 27405u); -} - -// ============================================================================ -// FEE CALCULATION VERIFICATION -// ============================================================================ - -TEST(ContractQThirtyFour, FeeCalculation_TotalEquals100Percent) -{ - ContractTestingQTF ctl; - const QTF::GetFees_output fees = ctl.getFees(); - - const uint32 total = fees.teamFeePercent + fees.distributionFeePercent + fees.winnerFeePercent + fees.burnPercent; - - EXPECT_EQ(total, 100u); -} - -// ============================================================================ -// PRIZE PAYOUT ESTIMATION -// ============================================================================ - -TEST(ContractQThirtyFour, EstimatePrizePayouts_NoTickets) -{ - ContractTestingQTF ctl; - - // No tickets sold, should return zero payouts - QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(1, 1); - - EXPECT_EQ(estimate.k2PayoutPerWinner, 0ull); - EXPECT_EQ(estimate.k3PayoutPerWinner, 0ull); - EXPECT_EQ(estimate.k2Pool, 0ull); - EXPECT_EQ(estimate.k3Pool, 0ull); - EXPECT_EQ(estimate.totalRevenue, 0ull); -} - -TEST(ContractQThirtyFour, EstimatePrizePayouts_WithTicketsSingleWinner) -{ - ContractTestingQTF ctl; - - ctl.startAnyDayEpoch(); - - // Buy 100 tickets - constexpr uint64 ticketPrice = 1000000ull; // 1M QU - constexpr uint64 numTickets = 100; - - const QTFRandomValues numbers = ctl.makeValidNumbers(1, 2, 3, 4); - ctl.buyRandomTickets(numTickets, ticketPrice, numbers); - - // Verify tickets were purchased - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numTickets); - - // Estimate for 1 k2 winner and 1 k3 winner - QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(1, 1); - - const uint64 expectedRevenue = ticketPrice * numTickets; - EXPECT_EQ(estimate.totalRevenue, expectedRevenue); - - // Check minimum floors and cap using constants from contract - constexpr uint64 expectedK2Floor = ticketPrice * QTF_K2_FLOOR_MULT / QTF_K2_FLOOR_DIV; - constexpr uint64 expectedK3Floor = ticketPrice * QTF_K3_FLOOR_MULT; - constexpr uint64 expectedCap = ticketPrice * QTF_TOPUP_PER_WINNER_CAP_MULT; - EXPECT_EQ(estimate.k2MinFloor, expectedK2Floor); - EXPECT_EQ(estimate.k3MinFloor, expectedK3Floor); - EXPECT_EQ(estimate.perWinnerCap, expectedCap); - - // Winners block using contract constants - const QTF::GetFees_output fees = ctl.getFees(); - uint64 winnersBlock = 0, k2PoolExpected = 0, k3PoolExpected = 0; - computeBaselinePrizePools(expectedRevenue, fees, winnersBlock, k2PoolExpected, k3PoolExpected); - - EXPECT_EQ(estimate.k2Pool, k2PoolExpected); - EXPECT_EQ(estimate.k3Pool, k3PoolExpected); - - // With 1 winner each: k2 payout equals pool (below cap), k3 payout is capped at 25*P - EXPECT_EQ(estimate.k2PayoutPerWinner, k2PoolExpected); // 19.04M < 25M cap - EXPECT_EQ(estimate.k3PayoutPerWinner, expectedCap); // 27.2M capped to 25M -} - -TEST(ContractQThirtyFour, EstimatePrizePayouts_WithMultipleWinners) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - // Buy 1000 tickets - const uint64 ticketPrice = 1000000ull; - const uint64 numTickets = 1000; - - const QTFRandomValues numbers = ctl.makeValidNumbers(5, 10, 15, 20); - ctl.buyRandomTickets(numTickets, ticketPrice, numbers); - - // Verify tickets were purchased - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numTickets); - - // Estimate for 10 k2 winners and 5 k3 winners - QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(10, 5); - - const uint64 expectedRevenue = ticketPrice * numTickets; - const QTF::GetFees_output fees = ctl.getFees(); - uint64 winnersBlock = 0, k2Pool = 0, k3Pool = 0; - computeBaselinePrizePools(expectedRevenue, fees, winnersBlock, k2Pool, k3Pool); - - // Verify pools - EXPECT_EQ(estimate.k2Pool, k2Pool); - EXPECT_EQ(estimate.k3Pool, k3Pool); - - // Verify per-winner payouts (should be pool / winner count, capped) - const uint64 k2ExpectedPerWinner = k2Pool / 10; - const uint64 k3ExpectedPerWinner = k3Pool / 5; - - EXPECT_EQ(estimate.k2PayoutPerWinner, std::min(k2ExpectedPerWinner, estimate.perWinnerCap)); - EXPECT_EQ(estimate.k3PayoutPerWinner, std::min(k3ExpectedPerWinner, estimate.perWinnerCap)); - - // Both should be above minimum floors - EXPECT_GE(estimate.k2PayoutPerWinner, estimate.k2MinFloor); - EXPECT_GE(estimate.k3PayoutPerWinner, estimate.k3MinFloor); -} - -TEST(ContractQThirtyFour, EstimatePrizePayouts_NoWinnersShowsPotential) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - // Buy 50 tickets - const uint64 ticketPrice = 1000000ull; - const uint64 numTickets = 50; - - const QTFRandomValues numbers = ctl.makeValidNumbers(7, 14, 21, 28); - ctl.buyRandomTickets(numTickets, ticketPrice, numbers); - - // Verify tickets were purchased - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numTickets); - - // Estimate with 0 winners (shows what a single winner would get) - QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(0, 0); - - const uint64 expectedRevenue = ticketPrice * numTickets; - const QTF::GetFees_output fees = ctl.getFees(); - uint64 winnersBlock = 0, k2Pool = 0, k3Pool = 0; - computeBaselinePrizePools(expectedRevenue, fees, winnersBlock, k2Pool, k3Pool); - - // When no winners specified, should show full pool (capped) - EXPECT_EQ(estimate.k2PayoutPerWinner, std::min(k2Pool, estimate.perWinnerCap)); - EXPECT_EQ(estimate.k3PayoutPerWinner, std::min(k3Pool, estimate.perWinnerCap)); -} - -// ============================================================================ -// DETERMINISTIC WINNER TESTING -// ============================================================================ -// Solution: By fixing prevSpectrumDigest, we can deterministically control winning numbers -// -// Background: -// Settlement generates winning numbers using: seed = K12(prevSpectrumDigest).u64._0 -// This seed is then used in GetRandomValues (QThirtyFour.h:1663-1698) to derive 4 numbers. -// -// Approach: -// 1. Create a fixed test prevSpectrumDigest (e.g., testDigest) -// 2. Compute expected winning numbers for that digest -// 3. Buy tickets with exact winning numbers (for k=4), partial matches (for k=2/k=3), etc. -// 4. Trigger settlement with drawWithDigest(testDigest) -// 5. Settlement will use our fixed digest, generating the pre-computed winning numbers -// 6. Verify actual payouts, jackpot depletion, FR resets, etc. -// -// This enables deterministic testing of: -// - Actual k=4 jackpot win payouts and jackpot depletion -// - Actual k=2/k=3 winner payouts with real matching logic -// - Actual FR reset behavior after k=4 win (frRoundsSinceK4 = 0) -// - Pool splitting among multiple winners -// - Revenue distribution and fee calculations with real winners - -TEST(ContractQThirtyFour, DeterministicWinner_K4JackpotWin_DepletesAndReseeds) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - // Ensure QRP has enough reserve to reseed to target. - increaseEnergy(ctl.qrpSelf(), QTF_DEFAULT_TARGET_JACKPOT + 1000000ULL); - const uint64 qrpBalanceBefore = static_cast(getBalance(ctl.qrpSelf())); - - // Create a deterministic prevSpectrumDigest - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x123456789ABCDEF0ULL; // Arbitrary seed - - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Setup: FR active with jackpot below target - const uint64 initialJackpot = 800000000ULL; // 800M QU - ctl.state()->setJackpot(initialJackpot); - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target - ctl.forceFREnabledWithinWindow(10); - // IMPORTANT: internal `state.jackpot` must be backed by actual contract balance, otherwise transfers will fail. - increaseEnergy(ctl.qtfSelf(), initialJackpot); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // User1: Buy ticket with EXACT winning numbers (k=4 winner) - const id k4Winner = id::randomValue(); - ctl.fundAndBuyTicket(k4Winner, ticketPrice, nums.winning); - - // User2: Buy ticket with 3 matching numbers (k=3 winner) - QTFRandomValues k3Numbers = ctl.makeK3Numbers(nums.winning); - const id k3Winner = id::randomValue(); - ctl.fundAndBuyTicket(k3Winner, ticketPrice, k3Numbers); - - // User3: Buy ticket with 2 matching numbers (k=2 winner) - QTFRandomValues k2Numbers = ctl.makeK2Numbers(nums.winning); - const id k2Winner = id::randomValue(); - ctl.fundAndBuyTicket(k2Winner, ticketPrice, k2Numbers); - - // User4: No match - const id loser = id::randomValue(); - QTFRandomValues loserNumbers = ctl.makeValidNumbers(1, 2, 3, 4); - ctl.fundAndBuyTicket(loser, ticketPrice, loserNumbers); - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 4ULL); - - // Verify state before settlement - const uint64 jackpotBefore = ctl.state()->getJackpot(); - const uint64 roundsSinceK4Before = ctl.state()->getFrRoundsSinceK4(); - EXPECT_EQ(jackpotBefore, initialJackpot); - EXPECT_EQ(roundsSinceK4Before, 10u); - - // Trigger settlement using our fixed prevSpectrumDigest - const uint64 k4WinnerBefore = getBalance(k4Winner); - ctl.drawWithDigest(testDigest); - const uint64 k4WinnerAfter = getBalance(k4Winner); - - // Verify k=4 jackpot win behavior: - const uint64 jackpotAfter = ctl.state()->getJackpot(); - EXPECT_GE(jackpotAfter, QTF_DEFAULT_TARGET_JACKPOT) << "Jackpot should be reseeded from QRP after k=4 win"; - EXPECT_LT(static_cast(getBalance(ctl.qrpSelf())), qrpBalanceBefore) << "QRP reserve should decrease due to reseed"; - - // FR counters reset - const uint64 roundsSinceK4After = ctl.state()->getFrRoundsSinceK4(); - EXPECT_EQ(roundsSinceK4After, 0u) << "frRoundsSinceK4 should reset to 0 after k=4 win"; - - const uint64 roundsAtTargetAfter = ctl.state()->getFrRoundsAtOrAboveTarget(); - EXPECT_EQ(roundsAtTargetAfter, 0u) << "frRoundsAtOrAboveTarget should reset to 0 after k=4 win"; - - // 3. Verify winner data contains our winning numbers - QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(0), nums.winning.get(0)); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(1), nums.winning.get(1)); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(2), nums.winning.get(2)); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(3), nums.winning.get(3)); - - // Verify k=4 winner received exact payout (jackpotBefore / countK4). - EXPECT_EQ(static_cast(k4WinnerAfter - k4WinnerBefore), initialJackpot); -} - -TEST(ContractQThirtyFour, DeterministicWinner_K4JackpotWin_MultipleWinners_SplitsEvenly) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - // Ensure QRP has enough reserve to reseed (so settlement completes without relying on carry math). - increaseEnergy(ctl.qrpSelf(), QTF_DEFAULT_TARGET_JACKPOT + 1000000ULL); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xA5A5A5A5A5A5A5A5ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 initialJackpot = 900000000ULL; - ctl.state()->setJackpot(initialJackpot); - ctl.forceFREnabledWithinWindow(1); - increaseEnergy(ctl.qtfSelf(), initialJackpot); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - const id w1 = id::randomValue(); - const id w2 = id::randomValue(); - ctl.fundAndBuyTicket(w1, ticketPrice, nums.winning); - ctl.fundAndBuyTicket(w2, ticketPrice, nums.winning); - - const uint64 w1Before = getBalance(w1); - const uint64 w2Before = getBalance(w2); - - ctl.drawWithDigest(testDigest); - - const uint64 expectedPerWinner = initialJackpot / 2; - EXPECT_EQ(static_cast(getBalance(w1) - w1Before), expectedPerWinner); - EXPECT_EQ(static_cast(getBalance(w2) - w2Before), expectedPerWinner); -} - -TEST(ContractQThirtyFour, DeterministicWinner_K4JackpotWin_ReseedLimitedByQRP) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - ctl.forceFRDisabledForBaseline(); - - // Fund QRP below target so reseed amount is limited by available reserve. - const uint64 qrpFunded = 200000000ULL; - increaseEnergy(ctl.qrpSelf(), qrpFunded); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x0A0B0C0D0E0F1011ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 initialJackpot = 800000000ULL; - ctl.state()->setJackpot(initialJackpot); - increaseEnergy(ctl.qtfSelf(), initialJackpot); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const id w1 = id::randomValue(); - ctl.fundAndBuyTicket(w1, ticketPrice, nums.winning); - - const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); - const uint64 w1Before = getBalance(w1); - - ctl.drawWithDigest(testDigest); - - EXPECT_EQ(static_cast(getBalance(w1) - w1Before), initialJackpot); - - // With a single winning ticket and baseline overflow split, winnersOverflow == winnersBlock, reserveAdd == winnersBlock/2, carryAdd == - // winnersBlock/2. - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 revenue = ticketPrice; - const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; - const uint64 reserveAdd = (winnersBlock * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; - const uint64 carryAdd = winnersBlock - reserveAdd; - - EXPECT_EQ(ctl.state()->getJackpot(), qrpFunded + carryAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore - qrpFunded + reserveAdd); -} - -// Test k=2 and k=3 payouts with deterministic winning numbers -TEST(ContractQThirtyFour, DeterministicWinner_K2K3Payouts_VerifyRevenueSplit) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - // This test validates baseline k2/k3 pool splitting (no FR rake). - // Force FR activation window to be expired so SettleEpoch cannot auto-enable FR. - ctl.forceFRDisabledForBaseline(); - - // Create deterministic prevSpectrumDigest - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xFEDCBA9876543210ULL; // Different seed - - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Create multiple k=2 and k=3 winners to test pool splitting - // 2 k=3 winners - QTFRandomValues k3Numbers1 = ctl.makeK3Numbers(nums.winning, 0); - const id k3Winner1 = id::randomValue(); - ctl.fundAndBuyTicket(k3Winner1, ticketPrice, k3Numbers1); - - QTFRandomValues k3Numbers2 = ctl.makeK3Numbers(nums.winning, 1); - const id k3Winner2 = id::randomValue(); - ctl.fundAndBuyTicket(k3Winner2, ticketPrice, k3Numbers2); - - // 3 k=2 winners - QTFRandomValues k2Numbers1 = ctl.makeK2Numbers(nums.winning, 0); - const id k2Winner1 = id::randomValue(); - ctl.fundAndBuyTicket(k2Winner1, ticketPrice, k2Numbers1); - - QTFRandomValues k2Numbers2 = ctl.makeK2Numbers(nums.winning, 1); - const id k2Winner2 = id::randomValue(); - ctl.fundAndBuyTicket(k2Winner2, ticketPrice, k2Numbers2); - - QTFRandomValues k2Numbers3 = ctl.makeK2Numbers(nums.winning, 2); - const id k2Winner3 = id::randomValue(); - ctl.fundAndBuyTicket(k2Winner3, ticketPrice, k2Numbers3); - - // 5 losers (no matches) - for (int i = 0; i < 5; ++i) - { - const id loser = id::randomValue(); - QTFRandomValues loserNumbers = ctl.makeValidNumbers(1, 2, 3, 4); - ctl.fundAndBuyTicket(loser, ticketPrice, loserNumbers); - } - - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 10ULL); - - // Calculate expected pools - const uint64 revenue = ticketPrice * 10; - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; // 68% - const uint64 expectedK2Pool = (winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000; // 28% of winners block - const uint64 expectedK3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000; // 40% of winners block - - // Get balances before settlement - const uint64 k3Winner1Before = getBalance(k3Winner1); - const uint64 k2Winner1Before = getBalance(k2Winner1); - - // Trigger settlement - ctl.drawWithDigest(testDigest); - - // Verify winner payouts - // k=3 pool split between 2 winners - const uint64 expectedK3PayoutPerWinner = expectedK3Pool / 2; - const uint64 k3Winner1After = getBalance(k3Winner1); - const uint64 k3Winner1Gained = k3Winner1After - k3Winner1Before; - EXPECT_EQ(static_cast(k3Winner1Gained), expectedK3PayoutPerWinner) << "k=3 winner should receive half of k3 pool"; - - // k=2 pool split between 3 winners - const uint64 expectedK2PayoutPerWinner = expectedK2Pool / 3; - const uint64 k2Winner1After = getBalance(k2Winner1); - const uint64 k2Winner1Gained = k2Winner1After - k2Winner1Before; - EXPECT_EQ(static_cast(k2Winner1Gained), expectedK2PayoutPerWinner) << "k=2 winner should receive one-third of k2 pool"; - - // Verify winning numbers in winner data - QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(0), nums.winning.get(0)); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(1), nums.winning.get(1)); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(2), nums.winning.get(2)); - EXPECT_EQ(winnerData.winnerData.winnerValues.get(3), nums.winning.get(3)); - - // Jackpot should have grown (no k=4 winner) - EXPECT_GT(ctl.state()->getJackpot(), 0ULL); -} - -TEST(ContractQThirtyFour, EstimatePrizePayouts_FRMode_AppliesRakeToPools) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Enable FR so EstimatePrizePayouts applies the 5% winners rake. - ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT / 2); - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); - ctl.forceFREnabledWithinWindow(1); - - constexpr uint64 numPlayers = 100; - for (uint64 i = 0; i < numPlayers; ++i) - { - const id user = id::randomValue(); - QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i % 26) + 1), static_cast((i % 26) + 2), - static_cast((i % 26) + 3), static_cast((i % 26) + 4)); - ctl.fundAndBuyTicket(user, ticketPrice, nums); - } - - const QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(0, 0); - - const uint64 revenue = ticketPrice * numPlayers; - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; - const uint64 winnersRake = (winnersBlock * QTF_FR_WINNERS_RAKE_BP) / 10000; - const uint64 winnersBlockAfterRake = winnersBlock - winnersRake; - - const uint64 expectedK2Pool = (winnersBlockAfterRake * QTF_BASE_K2_SHARE_BP) / 10000; - const uint64 expectedK3Pool = (winnersBlockAfterRake * QTF_BASE_K3_SHARE_BP) / 10000; - - EXPECT_EQ(estimate.totalRevenue, revenue); - EXPECT_EQ(estimate.k2Pool, expectedK2Pool); - EXPECT_EQ(estimate.k3Pool, expectedK3Pool); -} - -// ============================================================================ -// RESERVE TOP-UP AND FLOOR GUARANTEE TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, Settlement_PerWinnerCap_AppliesToK3Winner_OverflowAccountsForRemainder) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - ctl.forceFRDisabledForBaseline(); - - // Ensure RL shares exist so distribution payouts leave the contract (otherwise most of distPayout can remain in QTF balance). - const id shareholder1 = id::randomValue(); - const id shareholder2 = id::randomValue(); - constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 3; - constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; - std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; - issueRlSharesTo(rlShares); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xD1CEB00BD1CEB00BULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 P = ctl.state()->getTicketPriceInternal(); - const uint64 perWinnerCap = smul(P, QTF_TOPUP_PER_WINNER_CAP_MULT); - - const id k3Winner = id::randomValue(); - ctl.fundAndBuyTicket(k3Winner, P, ctl.makeK3Numbers(nums.winning, 0)); - - constexpr uint64 numLosers = 100; - ctl.buyRandomTickets(numLosers, P, nums.losing); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numLosers + 1); - - const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); - const uint64 k3Before = getBalance(k3Winner); - - ctl.drawWithDigest(testDigest); - - EXPECT_EQ(static_cast(getBalance(k3Winner) - k3Before), perWinnerCap); - - // Baseline settlement: with no k2 winners and exactly one k3 winner capped at 25*P, - // winnersOverflow ends up being winnersBlock - perWinnerCap. - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 revenue = smul(P, numLosers + 1); - const uint64 winnersBlock = div(smul(revenue, static_cast(fees.winnerFeePercent)), 100); - const uint64 winnersOverflow = winnersBlock - perWinnerCap; - const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; - const uint64 carryAdd = winnersOverflow - reserveAdd; - - EXPECT_EQ(ctl.state()->getJackpot(), carryAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), carryAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore + reserveAdd); -} - -TEST(ContractQThirtyFour, Settlement_FloorTopUp_LimitedBySafetyCaps_PayoutBelowFloor) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - ctl.forceFRDisabledForBaseline(); - - // Ensure RL shares exist so distribution payouts leave the contract (otherwise most of distPayout can remain in QTF balance). - const id shareholder1 = id::randomValue(); - const id shareholder2 = id::randomValue(); - constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 2; - constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; - std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; - issueRlSharesTo(rlShares); - - // Fund QRP just above soft floor so top-up is limited by both 10% cap and soft floor. - const uint64 P = ctl.state()->getTicketPriceInternal(); - const uint64 softFloor = smul(P, QTF_RESERVE_SOFT_FLOOR_MULT); // 20*P - const uint64 qrpFunding = softFloor + 5 * P; // 25*P - increaseEnergy(ctl.qrpSelf(), qrpFunding); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x0DDC0FFEE0DDF00DULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const id k3Winner = id::randomValue(); - ctl.fundAndBuyTicket(k3Winner, P, ctl.makeK3Numbers(nums.winning, 0)); - EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); - - const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); - const uint64 k3Before = getBalance(k3Winner); - - ctl.drawWithDigest(testDigest); - - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 revenue = P; - const uint64 winnersBlock = div(smul(revenue, static_cast(fees.winnerFeePercent)), 100); - const uint64 k3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000; - const uint64 k3Floor = smul(P, QTF_K3_FLOOR_MULT); - const uint64 needed = k3Floor - k3Pool; - const uint64 availableAboveFloor = qrpBefore - softFloor; // 5*P - const uint64 maxPerRound = (qrpBefore * QTF_TOPUP_RESERVE_PCT_BP) / 10000; // 10% of total - const uint64 perWinnerCapTotal = smul(P, QTF_TOPUP_PER_WINNER_CAP_MULT); // 25*P - const uint64 maxAllowed = std::min(std::min(maxPerRound, availableAboveFloor), perWinnerCapTotal); // 2.5*P - const uint64 expectedTopUp = std::min(needed, maxAllowed); - const uint64 expectedPayout = k3Pool + expectedTopUp; - - EXPECT_LT(expectedPayout, k3Floor); - EXPECT_EQ(static_cast(getBalance(k3Winner) - k3Before), expectedPayout); - - // With no k2 winners and k3 pool fully paid (top-ups only increase payouts), - // winnersOverflow equals winnersBlock - k3Pool. - const uint64 winnersOverflow = winnersBlock - k3Pool; - const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; - const uint64 carryAdd = winnersOverflow - reserveAdd; - - EXPECT_EQ(ctl.state()->getJackpot(), carryAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), carryAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore - expectedTopUp + reserveAdd); - EXPECT_GE(static_cast(getBalance(ctl.qrpSelf())), softFloor); -} - -TEST(ContractQThirtyFour, Settlement_FloorTopUp_Integration_K2K3FloorsMetWhenReserveSufficient) -{ - ContractTestingQTF ctl; - ctl.startAnyDayEpoch(); - ctl.forceFRDisabledForBaseline(); - - // Ensure RL shares exist so distribution path is exercised (and rounding/payback is deterministic). - const id shareholder1 = id::randomValue(); - const id shareholder2 = id::randomValue(); - constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 4; - constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; - std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; - issueRlSharesTo(rlShares); - - // Fund QRP enough so both tiers can be topped up to floors under all caps. - const uint64 qrpFunding = 100000000ULL; // 100M, 10% cap = 10M, soft floor = 20M. - increaseEnergy(ctl.qrpSelf(), qrpFunding); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x5566778899AABBCCULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - const uint64 P = ctl.state()->getTicketPriceInternal(); - - // Create deterministic winners: 2x k2 winners and 1x k3 winner => pools are small and must be topped up. - const id k2w1 = id::randomValue(); - const id k2w2 = id::randomValue(); - const id k3w1 = id::randomValue(); - ctl.fundAndBuyTicket(k2w1, P, ctl.makeK2Numbers(nums.winning, 0)); - ctl.fundAndBuyTicket(k2w2, P, ctl.makeK2Numbers(nums.winning, 1)); - ctl.fundAndBuyTicket(k3w1, P, ctl.makeK3Numbers(nums.winning, 2)); - - const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); - const uint64 qtfBefore = static_cast(getBalance(ctl.qtfSelf())); - const uint64 k2w1Before = getBalance(k2w1); - const uint64 k3w1Before = getBalance(k3w1); - const uint64 sh1Before = getBalance(shareholder1); - const uint64 sh2Before = getBalance(shareholder2); - const uint64 rlBefore = getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)); - - EXPECT_EQ(qtfBefore, 3 * P); - - ctl.drawWithDigest(testDigest); - - // Expected pools and top-ups. - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 revenue = 3 * P; - const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; - const uint64 k2Pool = (winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000; - const uint64 k3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000; - - const uint64 k2Floor = P / 2; - const uint64 k3Floor = 5 * P; - const uint64 k2TopUp = (k2Floor * 2 > k2Pool) ? (k2Floor * 2 - k2Pool) : 0; - const uint64 k3TopUp = (k3Floor > k3Pool) ? (k3Floor - k3Pool) : 0; - - // Winners must receive the floors (no per-winner cap binding in this scenario). - EXPECT_EQ(static_cast(getBalance(k2w1) - k2w1Before), k2Floor); - EXPECT_EQ(static_cast(getBalance(k3w1) - k3w1Before), k3Floor); - - // Baseline overflow is the unallocated 32% of winnersBlock (tier pools are fully paid out with floor top-ups, so no extra overflow). - const uint64 winnersOverflow = winnersBlock - k2Pool - k3Pool; - const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; - const uint64 carryAdd = winnersOverflow - reserveAdd; - - // Contract balance should match carry (jackpot) after settlement. - EXPECT_EQ(ctl.state()->getJackpot(), carryAdd); - EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), carryAdd); - - // QRP: receives reserveAdd, pays out top-ups. - EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore - k2TopUp - k3TopUp + reserveAdd); - - // Distribution: verify two holders and RL payback remainder. - const uint64 expectedDistFee = (revenue * fees.distributionFeePercent) / 100; - const uint64 dividendPerShare = expectedDistFee / NUMBER_OF_COMPUTORS; - const uint64 expectedSh1Gain = static_cast(shares1) * dividendPerShare; - const uint64 expectedSh2Gain = static_cast(shares2) * dividendPerShare; - const uint64 expectedPayback = expectedDistFee - (dividendPerShare * NUMBER_OF_COMPUTORS); - EXPECT_EQ(getBalance(shareholder1), sh1Before + expectedSh1Gain); - EXPECT_EQ(getBalance(shareholder2), sh2Before + expectedSh2Gain); - EXPECT_EQ(getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)), rlBefore + expectedPayback); -} - -// ============================================================================ -// HIGH-DEFICIT FR EXTRA REDIRECTS TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, FR_HighDeficit_ExtraRedirectsCalculated) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - // Fix RNG so we can deterministically avoid winners (and especially k=4). - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x4040404040404040ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Setup: High deficit scenario - // Jackpot = 0, Target = 1B, FR active - ctl.state()->setJackpot(0ULL); // Empty jackpot - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target - ctl.state()->setFrActive(true); - ctl.state()->setFrRoundsSinceK4(5); - - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - const QTF::GetFees_output fees = ctl.getFees(); - - // Add many players to generate high revenue - constexpr int numPlayers = 500; - ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); - - const uint64 revenue = ticketPrice * numPlayers; // 500M QU - const uint64 deficit = QTF_DEFAULT_TARGET_JACKPOT - 0; // 1B deficit - - // With high deficit (1B) and significant revenue (500M), extra redirects should be calculated - // Formula (from spec and QThirtyFour.h:1928-1965): - // - deficit Δ = 1B - // - E_k4(500) ≈ 55 rounds (expected rounds to k=4 with 500 tickets) - // - horizon H = min(55, 50) = 50 (capped) - // - required gain per round = Δ/H = 1B/50 = 20M - // - base gain (without extra) ≈ 1% dev + 1% dist + 5% rake + 95% overflow - // ≈ 5M + 5M + 17M + ~98M = ~125M (rough estimate) - // - Since base gain (125M) > required (20M), extra might be 0 or small - // But let's verify the mechanism is working - - const uint64 devBalBefore = getBalance(ctl.state()->team()); - const uint64 jackpotBefore = ctl.state()->getJackpot(); - EXPECT_EQ(jackpotBefore, 0ULL); - - ctl.drawWithDigest(testDigest); - - // After settlement with FR active and high deficit: - const uint64 devBalAfter = getBalance(ctl.state()->team()); - const uint64 jackpotAfter = ctl.state()->getJackpot(); - - // Verify FR is still active - EXPECT_EQ(ctl.state()->getFrActive(), true); - - // Dev should receive less than full 10% of revenue due to FR redirects - const uint64 fullDevPayout = (revenue * fees.teamFeePercent) / 100; // 50M (10% of 500M) - const uint64 actualDevPayout = devBalAfter - devBalBefore; - - // Base redirect alone is 1% of revenue = 5M - const uint64 baseDevRedirect = (revenue * QTF_FR_DEV_REDIRECT_BP) / 10000; // 5M - EXPECT_LT(actualDevPayout, fullDevPayout) << "Dev should receive less than full 10% in FR mode"; - EXPECT_LE(actualDevPayout, fullDevPayout - baseDevRedirect) << "Dev redirect should be at least base 1%"; - - // Jackpot should have grown significantly from: - // - Winners rake (5% of 340M winners block = 17M) - // - Dev/Dist redirects (base 1% each + possible extra) - // - Overflow bias (95% of overflow) - EXPECT_GT(jackpotAfter, 100000000ULL) << "Jackpot should grow by at least 100M from FR mechanisms"; - - // Verify extra redirect cap: dev redirect should not exceed base (1%) + extra max (0.35%) = 1.35% total - const uint64 maxDevRedirectTotal = (revenue * (QTF_FR_DEV_REDIRECT_BP + QTF_FR_EXTRA_MAX_BP / 2)) / 10000; // 1.35% - const uint64 actualDevRedirect = fullDevPayout - actualDevPayout; - EXPECT_LE(actualDevRedirect, maxDevRedirectTotal) << "Dev redirect should not exceed 1.35% of revenue"; - - // Note: The exact extra redirect amount depends on complex calculation in CalculateExtraRedirectBP - // (QThirtyFour.h:1928-1965), which uses fixed-point arithmetic, power calculations, and horizon capping. - // This test verifies the mechanism is active and within bounds. -} - -TEST(ContractQThirtyFour, Settlement_FRMode_ExtraRedirect_ClampsToMax_AndAffectsDevAndDist) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - // Ensure RL shares exist so distribution can be asserted. - const id shareholder1 = id::randomValue(); - const id shareholder2 = id::randomValue(); - constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 2; - constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; - std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; - issueRlSharesTo(rlShares); - - // Deterministic no-winner tickets. - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0x7777777777777777ULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Force FR on and create an extreme deficit to guarantee extra redirect clamps to max. - ctl.state()->setJackpot(0ULL); - ctl.state()->setTargetJackpotInternal(1000000000000000ULL); // 1e15 - ctl.state()->setFrActive(true); - ctl.state()->setFrRoundsSinceK4(1); - - ctl.beginEpochWithValidTime(); - - const uint64 P = ctl.state()->getTicketPriceInternal(); - constexpr uint64 numPlayers = 10; - ctl.buyRandomTickets(numPlayers, P, nums.losing); - - const QTF::GetFees_output fees = ctl.getFees(); - const uint64 revenue = P * numPlayers; - - const uint64 devBefore = getBalance(ctl.state()->team()); - const uint64 sh1Before = getBalance(shareholder1); - const uint64 sh2Before = getBalance(shareholder2); - const uint64 rlBefore = getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)); - - // Pre-compute expected extra BP using the same private helpers as the contract. - QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); - primeQpiFunctionContext(qpi); - const auto pools = ctl.state()->callCalculatePrizePools(qpi, revenue, true); - const auto baseGainOut = ctl.state()->callCalculateBaseGain(qpi, revenue, pools.winnersBlock); - const uint64 delta = ctl.state()->getTargetJackpotInternal() - ctl.state()->getJackpot(); - const auto extraOut = ctl.state()->callCalculateExtraRedirectBP(qpi, numPlayers, delta, revenue, baseGainOut.baseGain); - ASSERT_EQ(extraOut.extraBP, QTF_FR_EXTRA_MAX_BP); - - const uint64 devExtraBP = extraOut.extraBP / 2; - const uint64 distExtraBP = extraOut.extraBP - devExtraBP; - const uint64 totalDevRedirectBP = QTF_FR_DEV_REDIRECT_BP + devExtraBP; - const uint64 totalDistRedirectBP = QTF_FR_DIST_REDIRECT_BP + distExtraBP; - - const uint64 fullDevFee = (revenue * fees.teamFeePercent) / 100; - const uint64 fullDistFee = (revenue * fees.distributionFeePercent) / 100; - - const uint64 expectedDevRedirect = (revenue * totalDevRedirectBP) / 10000; - const uint64 expectedDistRedirect = (revenue * totalDistRedirectBP) / 10000; - const uint64 expectedDevPayout = fullDevFee - expectedDevRedirect; - const uint64 expectedDistPayout = fullDistFee - expectedDistRedirect; - - ctl.drawWithDigest(testDigest); - - // Dev payout must match exact base+extra redirect math (no caps expected in this scenario). - EXPECT_EQ(static_cast(getBalance(ctl.state()->team()) - devBefore), expectedDevPayout); - - // Distribution must match expectedDistPayout (dividendPerShare flooring + payback). - const uint64 dividendPerShare = expectedDistPayout / NUMBER_OF_COMPUTORS; - const uint64 expectedSh1Gain = static_cast(shares1) * dividendPerShare; - const uint64 expectedSh2Gain = static_cast(shares2) * dividendPerShare; - const uint64 expectedPayback = expectedDistPayout - (dividendPerShare * NUMBER_OF_COMPUTORS); - EXPECT_EQ(getBalance(shareholder1), sh1Before + expectedSh1Gain); - EXPECT_EQ(getBalance(shareholder2), sh2Before + expectedSh2Gain); - EXPECT_EQ(getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)), rlBefore + expectedPayback); -} - -// ============================================================================ -// POST-K4 WINDOW EXPIRY TESTS -// ============================================================================ - -TEST(ContractQThirtyFour, FR_PostK4WindowExpiry_DoesNotActivateWhenInactive) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xABCDABCDABCDABCDULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Setup: Jackpot below target, but window expired and FR inactive. - ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT / 2); - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); - ctl.state()->setFrActive(false); - ctl.state()->setFrRoundsSinceK4(QTF_FR_POST_K4_WINDOW_ROUNDS); - - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - constexpr int numPlayers = 10; - ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); - - ctl.drawWithDigest(testDigest); - - EXPECT_EQ(ctl.state()->getFrActive(), false); - EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS + 1); -} - -TEST(ContractQThirtyFour, FR_PostK4WindowExpiry_DoesNotReactivateWhenWindowExpired) -{ - ContractTestingQTF ctl; - ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); - - m256i testDigest = {}; - testDigest.m256i_u64[0] = 0xFACEFEEDFACEFEEDULL; - const auto nums = ctl.computeWinningAndLosing(testDigest); - - // Setup: FR active, jackpot below target, but approaching window expiry - ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT / 2); // 500M (below target) - ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target - ctl.state()->setFrActive(true); - ctl.state()->setFrRoundsSinceK4(QTF_FR_POST_K4_WINDOW_ROUNDS - 1); // One round before window expiry (50 = QTF_FR_POST_K4_WINDOW_ROUNDS) - - ctl.beginEpochWithValidTime(); - - const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); - - // Add players - constexpr int numPlayers = 10; - for (int i = 0; i < numPlayers; ++i) - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - - // Verify FR is active before settlement - EXPECT_EQ(ctl.state()->getFrActive(), true); - EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS - 1); - EXPECT_LT(ctl.state()->getJackpot(), ctl.state()->getTargetJackpotInternal()); - - ctl.drawWithDigest(testDigest); - - // After settlement (deterministic: no k=4 win is possible): - // - roundsSinceK4 should increment to 50 - // - Next round starts outside the FR post-k4 window. - - const uint64 roundsSinceK4After = ctl.state()->getFrRoundsSinceK4(); - EXPECT_EQ(roundsSinceK4After, QTF_FR_POST_K4_WINDOW_ROUNDS) << "Counter should increment to 50 after draw"; - - // Run one more round: FR must be OFF because roundsSinceK4 >= 50. - ctl.beginEpochWithValidTime(); - - for (int i = 0; i < numPlayers; ++i) - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - - ctl.drawWithDigest(testDigest); - - // After second round: - // - Jackpot still below target - // - roundsSinceK4 = 51 (>= 50) - // - FR is forced OFF outside the window. - EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS + 1); - EXPECT_EQ(ctl.state()->getFrActive(), false); - - // Run a third round to ensure FR stays OFF while still outside the window. - ctl.beginEpochWithValidTime(); - for (int i = 0; i < numPlayers; ++i) - { - const id user = id::randomValue(); - ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); - } - ctl.drawWithDigest(testDigest); - - EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS + 2); - EXPECT_EQ(ctl.state()->getFrActive(), false); -} - diff --git a/test/contract_qutil.cpp b/test/contract_qutil.cpp deleted file mode 100644 index 18e66b7ea..000000000 --- a/test/contract_qutil.cpp +++ /dev/null @@ -1,1751 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -#include - -class ContractTestingQUtil : public ContractTesting { -public: - ContractTestingQUtil() { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QUTIL); - callSystemProcedure(QUTIL_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - - // init fees - callSystemProcedure(QUTIL_CONTRACT_INDEX, INITIALIZE, true); - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(QUTIL_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - callSystemProcedure(QX_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QUTIL_CONTRACT_INDEX, END_EPOCH, expectSuccess); - callSystemProcedure(QX_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - - QX::IssueAsset_output issueAsset(const id& issuer, uint64_t assetName, uint64_t numberOfShares) { - QX::IssueAsset_input input; - input.assetName = assetName; - input.numberOfShares = numberOfShares; - input.unitOfMeasurement = 0; - input.numberOfDecimalPlaces = 0; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, 1000000000); - return output; - } - - QX::TransferShareOwnershipAndPossession_output transferAsset(const id& from, const id& to, const Asset& asset, uint64_t amount) { - QX::TransferShareOwnershipAndPossession_input input; - input.issuer = asset.issuer; - input.newOwnerAndPossessor = to; - input.assetName = asset.assetName; - input.numberOfShares = amount; - QX::TransferShareOwnershipAndPossession_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, from, 1000000); - return output; - } - - QUTIL::CreatePoll_output createPoll(const id& creator, const QUTIL::CreatePoll_input& input, uint64_t fee) { - QUTIL::CreatePoll_output output; - memset(&output, 0, sizeof(output)); - invokeUserProcedure(QUTIL_CONTRACT_INDEX, 4, input, output, creator, fee); - return output; - } - - QUTIL::Vote_output vote(const id& voter, const QUTIL::Vote_input& input, uint64_t fee) { - QUTIL::Vote_output output; - memset(&output, 0, sizeof(output)); - invokeUserProcedure(QUTIL_CONTRACT_INDEX, 5, input, output, voter, fee); - return output; - } - - QUTIL::GetCurrentResult_output getCurrentResult(uint64_t poll_id) { - QUTIL::GetCurrentResult_input input; - input.poll_id = poll_id; - QUTIL::GetCurrentResult_output output; - memset(&output, 0, sizeof(output)); - callFunction(QUTIL_CONTRACT_INDEX, 3, input, output); - return output; - } - - QUTIL::GetPollsByCreator_output getPollsByCreator(const id& creator) { - QUTIL::GetPollsByCreator_input input; - input.creator = creator; - QUTIL::GetPollsByCreator_output output; - memset(&output, 0, sizeof(output)); - callFunction(QUTIL_CONTRACT_INDEX, 4, input, output); - return output; - } - - QUTIL::GetCurrentPollId_output getCurrentPollId() - { - QUTIL::GetCurrentPollId_input input; - QUTIL::GetCurrentPollId_output output; - memset(&output, 0, sizeof(output)); - callFunction(QUTIL_CONTRACT_INDEX, 5, input, output); - return output; - } - - QUTIL::GetPollInfo_output getPollInfo(uint64_t poll_id) - { - QUTIL::GetPollInfo_input input; - input.poll_id = poll_id; - QUTIL::GetPollInfo_output output; - memset(&output, 0, sizeof(output)); - callFunction(QUTIL_CONTRACT_INDEX, 6, input, output); - return output; - } - - QUTIL::DistributeQuToShareholders_output distributeQuToShareholders(const id& invocator, const Asset& asset, sint64 amount) { - QUTIL::DistributeQuToShareholders_input input{ asset }; - QUTIL::DistributeQuToShareholders_output output; - invokeUserProcedure(QUTIL_CONTRACT_INDEX, 7, input, output, invocator, amount); - return output; - } -}; - -// Helper function to generate random ID -id generateRandomId() { - return id::randomValue(); -} - -// Helper function to convert string to Array -Array stringToArray(const std::string& str) { - Array arr; - size_t len = std::min(str.size(), static_cast(256)); - for (size_t i = 0; i < len; ++i) { - arr.set(i, static_cast(str[i])); - } - for (size_t i = len; i < 256; ++i) { - arr.set(i, 0); - } - return arr; -} - -// Helper function to generate a dummy Asset -Asset generateAsset() { - Asset asset; - asset.issuer = generateRandomId(); - asset.assetName = 12345; // Simple name for testing - return asset; -} - -TEST(QUtilTest, CreateMultiplePolls_CheckIds) -{ - ContractTestingQUtil qutil; - id creator = generateRandomId(); - uint64_t num_polls = 16; - std::vector created_poll_ids; - - for (uint64_t i = 0; i < num_polls; ++i) - { - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(i)); - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - created_poll_ids.push_back(output.poll_id); - } - - auto current_poll_info = qutil.getCurrentPollId(); - EXPECT_EQ(current_poll_info.current_poll_id, num_polls); - EXPECT_EQ(current_poll_info.active_count, num_polls); - - std::set expected_ids(created_poll_ids.begin(), created_poll_ids.end()); - std::set active_ids; - for (uint64_t i = 0; i < current_poll_info.active_count; ++i) - { - active_ids.insert(current_poll_info.active_poll_ids.get(i)); - } - EXPECT_EQ(active_ids, expected_ids); -} - -TEST(QUtilTest, CreateMultiplePolls_CheckNames) -{ - ContractTestingQUtil qutil; - id creator = generateRandomId(); - uint64_t num_polls = 5; - std::vector poll_names; - std::vector poll_ids; - - for (uint64_t i = 0; i < num_polls; ++i) - { - id poll_name = generateRandomId(); - poll_names.push_back(poll_name); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(i)); - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - poll_ids.push_back(output.poll_id); - } - - for (uint64_t i = 0; i < num_polls; ++i) - { - auto poll_info = qutil.getPollInfo(poll_ids[i]); - EXPECT_EQ(poll_info.found, 1); - EXPECT_EQ(poll_info.poll_info.poll_name, poll_names[i]); - } -} - -TEST(QUtilTest, CreatePollsMoreThanMax_CheckActiveIds) -{ - ContractTestingQUtil qutil; - id creator = generateRandomId(); - uint64_t num_polls_per_epoch = QUTIL_MAX_NEW_POLL; // 16 polls per epoch - uint64_t num_epochs = 2; - std::vector created_poll_ids; - - for (uint64_t epoch = 0; epoch < num_epochs; ++epoch) - { - for (uint64_t i = 0; i < num_polls_per_epoch; ++i) - { - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(epoch * num_polls_per_epoch + i)); - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - created_poll_ids.push_back(output.poll_id); - } - if (epoch < num_epochs - 1) - { - qutil.endEpoch(); - qutil.beginEpoch(); - } - } - - auto current_poll_info = qutil.getCurrentPollId(); - EXPECT_EQ(current_poll_info.current_poll_id, num_polls_per_epoch * num_epochs); // Total polls created: 32 - EXPECT_EQ(current_poll_info.active_count, num_polls_per_epoch); // Only 16 active in current epoch - - std::set expected_active_ids; - for (uint64_t i = (num_epochs - 1) * num_polls_per_epoch; i < num_epochs * num_polls_per_epoch; ++i) - { - expected_active_ids.insert(i); // IDs 16 to 31 should be active - } - - std::set active_ids; - for (uint64_t i = 0; i < current_poll_info.active_count; ++i) - { - active_ids.insert(current_poll_info.active_poll_ids.get(i)); - } - EXPECT_EQ(active_ids, expected_active_ids); -} - -TEST(QUtilTest, CreatePollsMoreThanMax_CheckPollInfo) -{ - ContractTestingQUtil qutil; - id creator = generateRandomId(); - uint64_t num_polls_per_epoch = QUTIL_MAX_NEW_POLL; // 16 polls per epoch - uint64_t num_epochs = 2; - std::map poll_id_to_name; - - for (uint64_t epoch = 0; epoch < num_epochs; ++epoch) - { - for (uint64_t i = 0; i < num_polls_per_epoch; ++i) - { - id poll_name = generateRandomId(); - uint64_t poll_id = epoch * num_polls_per_epoch + i; - poll_id_to_name[poll_id] = poll_name; - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(poll_id)); - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - EXPECT_EQ(output.poll_id, poll_id); - } - if (epoch < num_epochs - 1) - { - qutil.endEpoch(); - qutil.beginEpoch(); - } - } - - // Check polls from the first epoch (IDs 0-15, should be deactivated) - for (uint64_t i = 0; i < num_polls_per_epoch; ++i) - { - auto poll_info = qutil.getPollInfo(i); - EXPECT_EQ(poll_info.found, 1); // Poll exists but is inactive - EXPECT_EQ(poll_info.poll_info.is_active, 0); - } - - // Check polls from the second epoch (IDs 16-31, should be active) - for (uint64_t i = num_polls_per_epoch; i < num_polls_per_epoch * 2; ++i) - { - auto poll_info = qutil.getPollInfo(i); - EXPECT_EQ(poll_info.found, 1); - EXPECT_EQ(poll_info.poll_info.is_active, 1); - EXPECT_EQ(poll_info.poll_info.poll_name, poll_id_to_name[i]); - } -} - -TEST(QUtilTest, CreatePolls_Vote_PassEpoch_CreateNewPolls_Vote_CheckResults) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - uint64_t min_amount = 1000; - - // Create poll 0 - id poll_name0 = generateRandomId(); - QUTIL::CreatePoll_input create_input0; - create_input0.poll_name = poll_name0; - create_input0.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input0.min_amount = min_amount; - create_input0.github_link = stringToArray("https://github.com/qubic/proposal/poll0"); - create_input0.num_assets = 0; - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output0 = qutil.createPoll(creator, create_input0, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id0 = create_output0.poll_id; - - // Create poll 1 - id poll_name1 = generateRandomId(); - QUTIL::CreatePoll_input create_input1; - create_input1.poll_name = poll_name1; - create_input1.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input1.min_amount = min_amount; - create_input1.github_link = stringToArray("https://github.com/qubic/proposal/poll1"); - create_input1.num_assets = 0; - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output1 = qutil.createPoll(creator, create_input1, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id1 = create_output1.poll_id; - - // Vote on poll 0: 2 votes for option 0, 1 for option 1 - id voter0 = generateRandomId(); - increaseEnergy(voter0, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input0; - vote_input0.poll_id = poll_id0; - vote_input0.address = voter0; - vote_input0.amount = min_amount; - vote_input0.chosen_option = 0; - qutil.vote(voter0, vote_input0, QUTIL_VOTE_FEE); - - id voter1 = generateRandomId(); - increaseEnergy(voter1, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input1; - vote_input1.poll_id = poll_id0; - vote_input1.address = voter1; - vote_input1.amount = min_amount; - vote_input1.chosen_option = 0; - qutil.vote(voter1, vote_input1, QUTIL_VOTE_FEE); - - id voter2 = generateRandomId(); - increaseEnergy(voter2, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input2; - vote_input2.poll_id = poll_id0; - vote_input2.address = voter2; - vote_input2.amount = min_amount; - vote_input2.chosen_option = 1; - qutil.vote(voter2, vote_input2, QUTIL_VOTE_FEE); - - // Vote on poll 1: 1 vote for option 0, 2 for option 1 - id voter3 = generateRandomId(); - increaseEnergy(voter3, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input3; - vote_input3.poll_id = poll_id1; - vote_input3.address = voter3; - vote_input3.amount = min_amount; - vote_input3.chosen_option = 0; - qutil.vote(voter3, vote_input3, QUTIL_VOTE_FEE); - - id voter4 = generateRandomId(); - increaseEnergy(voter4, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input4; - vote_input4.poll_id = poll_id1; - vote_input4.address = voter4; - vote_input4.amount = min_amount; - vote_input4.chosen_option = 1; - qutil.vote(voter4, vote_input4, QUTIL_VOTE_FEE); - - id voter5 = generateRandomId(); - increaseEnergy(voter5, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input5; - vote_input5.poll_id = poll_id1; - vote_input5.address = voter5; - vote_input5.amount = min_amount; - vote_input5.chosen_option = 1; - qutil.vote(voter5, vote_input5, QUTIL_VOTE_FEE); - - // Pass the epoch - qutil.endEpoch(); - - // Create poll 2 - id poll_name2 = generateRandomId(); - QUTIL::CreatePoll_input create_input2; - create_input2.poll_name = poll_name2; - create_input2.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input2.min_amount = min_amount; - create_input2.github_link = stringToArray("https://github.com/qubic/proposal/poll2"); - create_input2.num_assets = 0; - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output2 = qutil.createPoll(creator, create_input2, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id2 = create_output2.poll_id; - - // Create poll 3 - id poll_name3 = generateRandomId(); - QUTIL::CreatePoll_input create_input3; - create_input3.poll_name = poll_name3; - create_input3.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input3.min_amount = min_amount; - create_input3.github_link = stringToArray("https://github.com/qubic/proposal/poll3"); - create_input3.num_assets = 0; - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output3 = qutil.createPoll(creator, create_input3, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id3 = create_output3.poll_id; - - // Vote on poll 2: 1 vote for option 2 - id voter6 = generateRandomId(); - increaseEnergy(voter6, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input6; - vote_input6.poll_id = poll_id2; - vote_input6.address = voter6; - vote_input6.amount = min_amount; - vote_input6.chosen_option = 2; - qutil.vote(voter6, vote_input6, QUTIL_VOTE_FEE); - - // Vote on poll 3: 1 vote for option 3 - id voter7 = generateRandomId(); - increaseEnergy(voter7, min_amount + QUTIL_VOTE_FEE); - QUTIL::Vote_input vote_input7; - vote_input7.poll_id = poll_id3; - vote_input7.address = voter7; - vote_input7.amount = min_amount; - vote_input7.chosen_option = 3; - qutil.vote(voter7, vote_input7, QUTIL_VOTE_FEE); - - // Check results for old polls - auto result0 = qutil.getCurrentResult(poll_id0); - EXPECT_EQ(result0.is_active, 0); - EXPECT_EQ(result0.result.get(0), 2 * min_amount); - EXPECT_EQ(result0.result.get(1), min_amount); - EXPECT_EQ(result0.voter_count.get(0), 2); - EXPECT_EQ(result0.voter_count.get(1), 1); - - auto result1 = qutil.getCurrentResult(poll_id1); - EXPECT_EQ(result1.is_active, 0); - EXPECT_EQ(result1.result.get(0), min_amount); // One vote for option 0 - EXPECT_EQ(result1.result.get(1), 2 * min_amount); // Two votes for option 1 - EXPECT_EQ(result1.voter_count.get(0), 1); - EXPECT_EQ(result1.voter_count.get(1), 2); - - // Check results for new polls - auto result2 = qutil.getCurrentResult(poll_id2); - EXPECT_EQ(result2.is_active, 1); - EXPECT_EQ(result2.result.get(2), min_amount); - EXPECT_EQ(result2.voter_count.get(2), 1); - for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) - { - if (i != 2) - { - EXPECT_EQ(result2.result.get(i), 0); - EXPECT_EQ(result2.voter_count.get(i), 0); - } - } - - auto result3 = qutil.getCurrentResult(poll_id3); - EXPECT_EQ(result3.is_active, 1); - EXPECT_EQ(result3.result.get(3), min_amount); - EXPECT_EQ(result3.voter_count.get(3), 1); - for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) - { - if (i != 3) - { - EXPECT_EQ(result3.result.get(i), 0); - EXPECT_EQ(result3.voter_count.get(i), 0); - } - } -} - -TEST(QUtilTest, VoterListUpdateAndCompaction) { - ContractTestingQUtil qutil; - - id creator = generateRandomId(); - uint64_t min_amount = 1000; - id poll_name = generateRandomId(); - Array github_link = stringToArray("https://github.com/qubic/proposal/test"); - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = poll_type; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id0 = create_output.poll_id; - - id voterA = generateRandomId(); - id voterB = generateRandomId(); - id voterC = generateRandomId(); - id voterD = generateRandomId(); - id voterE = generateRandomId(); - id voterF = generateRandomId(); - id voterG = generateRandomId(); - - // Give each voter enough energy for voting (min_amount + fee) for two polls - increaseEnergy(voterA, min_amount + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterB, min_amount + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterC, min_amount + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterD, min_amount + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterE, min_amount + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterF, min_amount + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterG, min_amount + 2 * QUTIL_VOTE_FEE); - - int voterA_index = spectrumIndex(voterA); - int voterB_index = spectrumIndex(voterB); - int voterC_index = spectrumIndex(voterC); - int voterD_index = spectrumIndex(voterD); - int voterE_index = spectrumIndex(voterE); - int voterF_index = spectrumIndex(voterF); - int voterG_index = spectrumIndex(voterG); - - // Scenario 1: All voters valid for Poll 0 - QUTIL::Vote_input vote_inputA; - vote_inputA.poll_id = poll_id0; - vote_inputA.address = voterA; - vote_inputA.amount = min_amount; - vote_inputA.chosen_option = 0; - EXPECT_TRUE(qutil.vote(voterA, vote_inputA, QUTIL_VOTE_FEE).success); - - EXPECT_EQ(getBalance(voterA), min_amount + QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_inputB; - vote_inputB.poll_id = poll_id0; - vote_inputB.address = voterB; - vote_inputB.amount = min_amount; - vote_inputB.chosen_option = 0; - qutil.vote(voterB, vote_inputB, QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_inputC; - vote_inputC.poll_id = poll_id0; - vote_inputC.address = voterC; - vote_inputC.amount = min_amount; - vote_inputC.chosen_option = 0; - qutil.vote(voterC, vote_inputC, QUTIL_VOTE_FEE); - - auto result = qutil.getCurrentResult(poll_id0); - EXPECT_EQ(result.result.get(0), 3000); // A, B, C: 1000 each - EXPECT_EQ(result.voter_count.get(0), 3); - - auto balance_b = getBalance(voterB); - // Scenario 2: Invalidate voterB by decreasing energy below min_amount - decreaseEnergy(voterB_index, min_amount + QUTIL_VOTE_FEE - 500); // Leave 500, below min_amount - balance_b = getBalance(voterB); - - QUTIL::Vote_input vote_inputD; - vote_inputD.poll_id = poll_id0; - vote_inputD.address = voterD; - vote_inputD.amount = min_amount; - vote_inputD.chosen_option = 0; - qutil.vote(voterD, vote_inputD, QUTIL_VOTE_FEE); - - result = qutil.getCurrentResult(poll_id0); - EXPECT_EQ(result.result.get(0), 3000); // A, C, D: 1000 each, B invalid - EXPECT_EQ(result.voter_count.get(0), 3); - - // Scenario 3: Invalidate voterA and voterC - decreaseEnergy(voterA_index, min_amount + QUTIL_VOTE_FEE - 500); // Leave 500 - decreaseEnergy(voterC_index, min_amount + QUTIL_VOTE_FEE - 500); // Leave 500 - - QUTIL::Vote_input vote_inputE; - vote_inputE.poll_id = poll_id0; - vote_inputE.address = voterE; - vote_inputE.amount = min_amount; - vote_inputE.chosen_option = 0; - qutil.vote(voterE, vote_inputE, QUTIL_VOTE_FEE); - - result = qutil.getCurrentResult(poll_id0); - EXPECT_EQ(result.result.get(0), 2000); // D, E: 1000 each, others invalid - EXPECT_EQ(result.voter_count.get(0), 2); - - // Scenario 4: Single voter with new poll - id poll_name2 = generateRandomId(); - QUTIL::CreatePoll_input create_input2; - create_input2.poll_name = poll_name2; - create_input2.poll_type = poll_type; - create_input2.min_amount = min_amount; - create_input2.github_link = github_link; - create_input2.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output2 = qutil.createPoll(creator, create_input2, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id2 = create_output2.poll_id; - - id voterH = generateRandomId(); - increaseEnergy(voterH, min_amount + QUTIL_VOTE_FEE); - int voterH_index = spectrumIndex(voterH); - QUTIL::Vote_input vote_inputH; - vote_inputH.poll_id = poll_id2; - vote_inputH.address = voterH; - vote_inputH.amount = min_amount; - vote_inputH.chosen_option = 0; - qutil.vote(voterH, vote_inputH, QUTIL_VOTE_FEE); - - result = qutil.getCurrentResult(poll_id2); - EXPECT_EQ(result.result.get(0), 1000); // H: 1000 - EXPECT_EQ(result.voter_count.get(0), 1); - - // Scenario 5: Multiple polls with voter invalidation and new votes - // Create a new poll (Poll 1) - id poll_name1 = generateRandomId(); - QUTIL::CreatePoll_input create_input1; - create_input1.poll_name = poll_name1; - create_input1.poll_type = poll_type; - create_input1.min_amount = min_amount; - create_input1.github_link = github_link; - create_input1.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output1 = qutil.createPoll(creator, create_input1, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id1 = create_output1.poll_id; - - // Voters D, E voted for Poll 0, already did above. Only A, B, C need to revote again. F now votes for Poll 0 - increaseEnergy(voterA, 500 + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterB, 500 + 2 * QUTIL_VOTE_FEE); - increaseEnergy(voterC, 500 + 2 * QUTIL_VOTE_FEE); - - EXPECT_EQ(getBalance(voterA), 1200); - EXPECT_EQ(getBalance(voterB), 1200); - EXPECT_EQ(getBalance(voterC), 1200); - EXPECT_EQ(getBalance(voterD), 1100); - EXPECT_EQ(getBalance(voterE), 1100); - EXPECT_EQ(getBalance(voterF), 1200); - EXPECT_EQ(getBalance(voterG), 1200); - - vote_inputB.poll_id = poll_id0; - qutil.vote(voterB, vote_inputB, QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_inputF; - vote_inputF.poll_id = poll_id0; - vote_inputF.address = voterF; - vote_inputF.amount = min_amount; - vote_inputF.chosen_option = 0; - qutil.vote(voterF, vote_inputF, QUTIL_VOTE_FEE); - - // Add A and C voting for Poll 0 again - vote_inputA.poll_id = poll_id0; - qutil.vote(voterA, vote_inputA, QUTIL_VOTE_FEE); - vote_inputC.poll_id = poll_id0; - qutil.vote(voterC, vote_inputC, QUTIL_VOTE_FEE); - - // Voters A, B, C, D, E, F vote for Poll 1 - vote_inputA.poll_id = poll_id1; - qutil.vote(voterA, vote_inputA, QUTIL_VOTE_FEE); - vote_inputB.poll_id = poll_id1; - qutil.vote(voterB, vote_inputB, QUTIL_VOTE_FEE); - vote_inputC.poll_id = poll_id1; - qutil.vote(voterC, vote_inputC, QUTIL_VOTE_FEE); - vote_inputD.poll_id = poll_id1; - qutil.vote(voterD, vote_inputD, QUTIL_VOTE_FEE); - vote_inputE.poll_id = poll_id1; - qutil.vote(voterE, vote_inputE, QUTIL_VOTE_FEE); - vote_inputF.poll_id = poll_id1; - qutil.vote(voterF, vote_inputF, QUTIL_VOTE_FEE); - - EXPECT_EQ(getBalance(voterA), 1000); - EXPECT_EQ(getBalance(voterB), 1000); - EXPECT_EQ(getBalance(voterC), 1000); - EXPECT_EQ(getBalance(voterD), 1000); - EXPECT_EQ(getBalance(voterE), 1000); - EXPECT_EQ(getBalance(voterF), 1000); - EXPECT_EQ(getBalance(voterG), 1200); - - // Decrease energy for voters B and D below min_amount for both polls - decreaseEnergy(voterB_index, 500); - decreaseEnergy(voterD_index, 500); - - EXPECT_EQ(getBalance(voterA), 1000); - EXPECT_EQ(getBalance(voterB), 500); - EXPECT_EQ(getBalance(voterC), 1000); - EXPECT_EQ(getBalance(voterD), 500); - EXPECT_EQ(getBalance(voterE), 1000); - EXPECT_EQ(getBalance(voterF), 1000); - EXPECT_EQ(getBalance(voterG), 1200); - - // Voter G votes for both Poll 0 and Poll 1 - QUTIL::Vote_input vote_inputG; - vote_inputG.address = voterG; - vote_inputG.amount = min_amount; - vote_inputG.chosen_option = 0; - - vote_inputG.poll_id = poll_id0; - qutil.vote(voterG, vote_inputG, QUTIL_VOTE_FEE); - vote_inputG.poll_id = poll_id1; - qutil.vote(voterG, vote_inputG, QUTIL_VOTE_FEE); - - // Get and verify results for Poll 0 - result = qutil.getCurrentResult(poll_id0); - EXPECT_EQ(result.result.get(0), 5000); // A, C, E, F, G: 1000 each, B and D invalid - EXPECT_EQ(result.voter_count.get(0), 5); - - // Get and verify results for Poll 1 - result = qutil.getCurrentResult(poll_id1); - EXPECT_EQ(result.result.get(0), 5000); // A, C, E, F, G: 1000 each, B and D invalid - EXPECT_EQ(result.voter_count.get(0), 5); -} - -// Test successful Qubic poll creation -TEST(QUtilTest, CreatePoll_Success_Qubic) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - Array allowed_assets; - uint64_t num_assets = 0; - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.allowed_assets = allowed_assets; - input.num_assets = num_assets; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = output.poll_id; - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 1); - EXPECT_EQ(polls.poll_ids.get(0), poll_id); - - auto result = qutil.getCurrentResult(poll_id); - for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) { - EXPECT_EQ(result.result.get(i), 0); // No votes yet - } -} - -// Test successful Asset poll creation -TEST(QUtilTest, CreatePoll_Success_Asset) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - Array allowed_assets; - allowed_assets.set(0, generateAsset()); - uint64_t num_assets = 1; - uint64_t poll_type = QUTIL_POLL_TYPE_ASSET; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.allowed_assets = allowed_assets; - input.num_assets = num_assets; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = output.poll_id; - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 1); - EXPECT_EQ(polls.poll_ids.get(0), poll_id); -} - -// Test CreatePoll failure due to insufficient funds -TEST(QUtilTest, CreatePoll_InsufficientFunds) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE - 1); - qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE - 1); - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 0); -} - -// Test CreatePoll failure due to invalid poll type -TEST(QUtilTest, CreatePoll_InvalidPollType) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = 3; // Invalid type - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 0); -} - -// Test CreatePoll failure due to invalid num_assets for Qubic poll -TEST(QUtilTest, CreatePoll_InvalidNumAssetsQubic) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = QUTIL_POLL_TYPE_QUBIC; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 1; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 0); -} - -// Test CreatePoll failure due to invalid num_assets for Asset poll -TEST(QUtilTest, CreatePoll_InvalidNumAssetsAsset) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = QUTIL_POLL_TYPE_ASSET; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 0); -} - -// Test successful voting -TEST(QUtilTest, Vote_Success) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = poll_type; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - id voter = generateRandomId(); - increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voter; - vote_input.amount = min_amount; - vote_input.chosen_option = 0; - - auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_output.success); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(0), min_amount); - EXPECT_EQ(result.voter_count.get(0), 1); -} - -// Test Vote failure due to insufficient fee -TEST(QUtilTest, Vote_InsufficientFee) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - id voter = generateRandomId(); - increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE - 1); - - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voter; - vote_input.amount = min_amount; - vote_input.chosen_option = 0; - - auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE - 1); - EXPECT_FALSE(vote_output.success); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.voter_count.get(0), 0); -} - -// Test Vote failure due to invalid poll ID -TEST(QUtilTest, Vote_InvalidPollId) { - ContractTestingQUtil qutil; - id voter = generateRandomId(); - increaseEnergy(voter, 1000 + QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_input; - vote_input.poll_id = 999; // Non-existent poll - vote_input.address = voter; - vote_input.amount = 1000; - vote_input.chosen_option = 0; - - auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_output.success); -} - -// Test Vote failure due to insufficient balance -TEST(QUtilTest, Vote_InsufficientBalance) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - id voter = generateRandomId(); - increaseEnergy(voter, min_amount - 1 + QUTIL_VOTE_FEE); // Less than min_amount - - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voter; - vote_input.amount = min_amount; - vote_input.chosen_option = 0; - - auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_output.success); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.voter_count.get(0), 0); -} - -// Test Vote failure due to invalid option -TEST(QUtilTest, Vote_InvalidOption) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - id voter = generateRandomId(); - increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voter; - vote_input.amount = min_amount; - vote_input.chosen_option = QUTIL_MAX_OPTIONS; // 64 is invalid - - auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_output.success); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.voter_count.get(0), 0); -} - -// Test GetCurrentResult success -TEST(QUtilTest, GetCurrentResult_Success) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - id voter = generateRandomId(); - increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voter; - vote_input.amount = min_amount; - vote_input.chosen_option = 1; - - qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(1), min_amount); - EXPECT_EQ(result.voter_count.get(1), 1); -} - -// Test GetCurrentResult failure due to invalid poll ID -TEST(QUtilTest, GetCurrentResult_InvalidPollId) { - ContractTestingQUtil qutil; - auto result = qutil.getCurrentResult(999); // Non-existent poll - for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) { - EXPECT_EQ(result.result.get(i), 0); // Should return default values - } -} - -// Test GetPollsByCreator with polls -TEST(QUtilTest, GetPollsByCreator_WithPolls) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name1 = generateRandomId(); - id poll_name2 = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input input; - input.poll_type = QUTIL_POLL_TYPE_QUBIC; - input.min_amount = min_amount; - input.github_link = github_link; - input.num_assets = 0; - - input.poll_name = poll_name1; - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output1 = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - - input.poll_name = poll_name2; - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto output2 = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 2); - EXPECT_TRUE(polls.poll_ids.get(0) == output1.poll_id || polls.poll_ids.get(1) == output1.poll_id); - EXPECT_TRUE(polls.poll_ids.get(0) == output2.poll_id || polls.poll_ids.get(1) == output2.poll_id); -} - -// Test GetPollsByCreator with no polls -TEST(QUtilTest, GetPollsByCreator_NoPolls) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 0); -} - -// Scenario ID 1: Create a poll and have 10 random IDs vote -TEST(QUtilTest, ScenarioID1_CreatePoll_10Voters) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist - - QUTIL::CreatePoll_input create_input; - create_input.poll_name = poll_name; - create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; - create_input.min_amount = min_amount; - create_input.github_link = github_link; - create_input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - std::vector voters; - for (int i = 0; i < 10; ++i) { - id voter = generateRandomId(); - voters.push_back(voter); - increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voter; - vote_input.amount = min_amount; - vote_input.chosen_option = i % QUTIL_MAX_OPTIONS; // Options 0-9 - - auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_output.success); - } - - auto result = qutil.getCurrentResult(poll_id); - for (int i = 0; i < 10; ++i) { - EXPECT_EQ(result.result.get(i), min_amount); - EXPECT_EQ(result.voter_count.get(i), 1); - } - - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 1); - EXPECT_EQ(polls.poll_ids.get(0), poll_id); -} - -TEST(QUtilTest, CreatePoll_InvalidGithubLink) { - ContractTestingQUtil qutil; - id creator = generateRandomId(); - id poll_name = generateRandomId(); - uint64_t min_amount = 1000; - // Invalid GitHub link (does not start with "https://github.com/") - Array invalid_github_link = stringToArray("https://gitlab.com/invalidlink/proposal/abc"); - uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; - - QUTIL::CreatePoll_input input; - input.poll_name = poll_name; - input.poll_type = poll_type; - input.min_amount = min_amount; - input.github_link = invalid_github_link; - input.num_assets = 0; - - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - // Attempt to create the poll with invalid GitHub link - auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); - // Expect poll_id to be 0 indicating failure - EXPECT_EQ(output.poll_id, 0); - - // Verify that no poll was created for the creator - auto polls = qutil.getPollsByCreator(creator); - EXPECT_EQ(polls.count, 0); -} - -// Create an asset poll and have voters with allowed assets vote -TEST(QUtilTest, CreateAssetPoll_VotersWithAssets) -{ - ContractTestingQUtil qutil; - - id creator = generateRandomId(); - id issuer = generateRandomId(); - id voterX = generateRandomId(); - id voterY = generateRandomId(); - id voterZ = generateRandomId(); - id voterW = generateRandomId(); - - increaseEnergy(issuer, 2000000000 + 1000000 * 10); // For issuance and transfers - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - increaseEnergy(voterX, 10000000); // for votes and transfers - increaseEnergy(voterY, 10000000); - increaseEnergy(voterZ, 10000000); - increaseEnergy(voterW, 10000000); - - // Issue assets with valid names - unsigned long long assetNameA = assetNameFromString("ASSETA"); - unsigned long long assetNameB = assetNameFromString("ASSETB"); - Asset assetA = { issuer, assetNameA }; - Asset assetB = { issuer, assetNameB }; - qutil.issueAsset(issuer, assetNameA, 1000000); - qutil.issueAsset(issuer, assetNameB, 1000000); - - // Transfer assets - auto transferred_amount1 = qutil.transferAsset(issuer, voterX, assetA, 2000); - auto transferred_amount2 = qutil.transferAsset(issuer, voterX, assetB, 1500); - auto transferred_amount3 = qutil.transferAsset(issuer, voterY, assetA, 1000); - auto transferred_amount4 = qutil.transferAsset(issuer, voterZ, assetB, 1200); - - // Create asset poll - Array allowed_assets; - allowed_assets.set(0, assetA); - allowed_assets.set(1, assetB); - QUTIL::CreatePoll_input create_input; - create_input.poll_name = generateRandomId(); - create_input.poll_type = QUTIL_POLL_TYPE_ASSET; - create_input.min_amount = 1000; - create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); - create_input.allowed_assets = allowed_assets; - create_input.num_assets = 2; - - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - auto current_poll_id = qutil.getCurrentPollId(); - - // Voters vote - QUTIL::Vote_input vote_inputX; - vote_inputX.poll_id = poll_id; - vote_inputX.address = voterX; - vote_inputX.amount = 2000; // Max of A and B - vote_inputX.chosen_option = 0; - auto vote_outputX = qutil.vote(voterX, vote_inputX, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_outputX.success); - - QUTIL::Vote_input vote_inputY; - vote_inputY.poll_id = poll_id; - vote_inputY.address = voterY; - vote_inputY.amount = 1000; // Holding of A - vote_inputY.chosen_option = 1; - auto vote_outputY = qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_outputY.success); - - QUTIL::Vote_input vote_inputZ; - vote_inputZ.poll_id = poll_id; - vote_inputZ.address = voterZ; - vote_inputZ.amount = 1200; // Holding of B - vote_inputZ.chosen_option = 0; - auto vote_outputZ = qutil.vote(voterZ, vote_inputZ, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_outputZ.success); - - QUTIL::Vote_input vote_inputW; - vote_inputW.poll_id = poll_id; - vote_inputW.address = voterW; - vote_inputW.amount = 1000; // No assets - vote_inputW.chosen_option = 0; - auto vote_outputW = qutil.vote(voterW, vote_inputW, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_outputW.success); - - // Check results - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(0), 2000 + 1200); // VoterX + VoterZ - EXPECT_EQ(result.result.get(1), 1000); // VoterY - EXPECT_EQ(result.voter_count.get(0), 2); - EXPECT_EQ(result.voter_count.get(1), 1); -} - -// Voters with no allowed assets cannot vote -TEST(QUtilTest, VotersNoAllowedAssets) -{ - ContractTestingQUtil qutil; - - id creator = generateRandomId(); - id issuer = generateRandomId(); - id voterW = generateRandomId(); - - increaseEnergy(issuer, 2000000000); - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - increaseEnergy(voterW, QUTIL_VOTE_FEE); - - unsigned long long assetNameA = assetNameFromString("ASSETA"); - Asset assetA = { issuer, assetNameA }; - qutil.issueAsset(issuer, assetNameA, 1000000); - - Array allowed_assets; - allowed_assets.set(0, assetA); - QUTIL::CreatePoll_input create_input; - create_input.poll_name = generateRandomId(); - create_input.poll_type = QUTIL_POLL_TYPE_ASSET; - create_input.min_amount = 1000; - create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); - create_input.allowed_assets = allowed_assets; - create_input.num_assets = 1; - - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - QUTIL::Vote_input vote_inputW; - vote_inputW.poll_id = poll_id; - vote_inputW.address = voterW; - vote_inputW.amount = 1000; - vote_inputW.chosen_option = 0; - auto vote_outputW = qutil.vote(voterW, vote_inputW, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_outputW.success); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(0), 0); - EXPECT_EQ(result.voter_count.get(0), 0); -} - -// Voters with one allowed asset can vote -TEST(QUtilTest, VotersWithOneAllowedAsset) -{ - ContractTestingQUtil qutil; - - id creator = generateRandomId(); - id issuer = generateRandomId(); - id voterY = generateRandomId(); - - increaseEnergy(issuer, 2000000000 + 1000000); - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - increaseEnergy(voterY, QUTIL_VOTE_FEE); - - unsigned long long assetNameA = assetNameFromString("ASSETA"); - unsigned long long assetNameB = assetNameFromString("ASSETB"); - Asset assetA = { issuer, assetNameA }; - Asset assetB = { issuer, assetNameB }; - qutil.issueAsset(issuer, assetNameA, 1000000); - qutil.issueAsset(issuer, assetNameB, 1000000); - qutil.transferAsset(issuer, voterY, assetA, 1000); - - Array allowed_assets; - allowed_assets.set(0, assetA); - allowed_assets.set(1, assetB); - QUTIL::CreatePoll_input create_input; - create_input.poll_name = generateRandomId(); - create_input.poll_type = QUTIL_POLL_TYPE_ASSET; - create_input.min_amount = 1000; - create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); - create_input.allowed_assets = allowed_assets; - create_input.num_assets = 2; - - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - QUTIL::Vote_input vote_inputY; - vote_inputY.poll_id = poll_id; - vote_inputY.address = voterY; - vote_inputY.amount = 1000; - vote_inputY.chosen_option = 1; - auto vote_outputY = qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_outputY.success); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(1), 1000); - EXPECT_EQ(result.voter_count.get(1), 1); -} - -// Decrease voter shares and update voting power -TEST(QUtilTest, DecreaseShares_UpdateVotingPower) -{ - ContractTestingQUtil qutil; - - id creator = generateRandomId(); - id issuer = generateRandomId(); - id voterX = generateRandomId(); - id voterY = generateRandomId(); - - increaseEnergy(issuer, 2000000000 + 1000000 * 10); - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - increaseEnergy(voterX, 10000000); - increaseEnergy(voterY, 10000000); - - unsigned long long assetNameA = assetNameFromString("ASSETA"); - Asset assetA = { issuer, assetNameA }; - qutil.issueAsset(issuer, assetNameA, 1000000); - qutil.transferAsset(issuer, voterX, assetA, 2000); - qutil.transferAsset(issuer, voterY, assetA, 1000); - - Array allowed_assets; - allowed_assets.set(0, assetA); - QUTIL::CreatePoll_input create_input; - create_input.poll_name = generateRandomId(); - create_input.poll_type = QUTIL_POLL_TYPE_ASSET; - create_input.min_amount = 1000; - create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); - create_input.allowed_assets = allowed_assets; - create_input.num_assets = 1; - - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - // Initial votes - QUTIL::Vote_input vote_inputX; - vote_inputX.poll_id = poll_id; - vote_inputX.address = voterX; - vote_inputX.amount = 2000; - vote_inputX.chosen_option = 0; - qutil.vote(voterX, vote_inputX, QUTIL_VOTE_FEE); - - QUTIL::Vote_input vote_inputY; - vote_inputY.poll_id = poll_id; - vote_inputY.address = voterY; - vote_inputY.amount = 1000; - vote_inputY.chosen_option = 1; - qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); - - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(0), 2000); - EXPECT_EQ(result.result.get(1), 1000); - EXPECT_EQ(result.voter_count.get(0), 1); - EXPECT_EQ(result.voter_count.get(1), 1); - - // Decrease voterX's shares below min_amount - qutil.transferAsset(voterX, issuer, assetA, 1900); // Leaves 100 - - // VoterY votes again to trigger update - qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); - - // Check updated results - result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(0), 0); // VoterX removed - EXPECT_EQ(result.result.get(1), 1000); // VoterY remains - EXPECT_EQ(result.voter_count.get(0), 0); - EXPECT_EQ(result.voter_count.get(1), 1); - - // VoterX tries to vote again with insufficient shares - auto vote_outputX = qutil.vote(voterX, vote_inputX, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_outputX.success); -} - -TEST(QUtilTest, MultipleVoters_ShareTransfers_EligibilityTest) -{ - ContractTestingQUtil qutil; - - id creator = generateRandomId(); - id issuer = generateRandomId(); - std::vector voters(10); - for (auto& v : voters) - { - v = generateRandomId(); - } - - increaseEnergy(issuer, 4000000000); // for issuance and transfers - increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); - for (const auto& v : voters) - { - increaseEnergy(v, 10000000); // for votes and transfers - } - - // Issue 3 asset - unsigned long long assetNameA = assetNameFromString("ASSETA"); - unsigned long long assetNameB = assetNameFromString("ASSETB"); - unsigned long long assetNameC = assetNameFromString("ASSETC"); - Asset assetA = { issuer, assetNameA }; - Asset assetB = { issuer, assetNameB }; - Asset assetC = { issuer, assetNameC }; - qutil.issueAsset(issuer, assetNameA, 1000000); - qutil.issueAsset(issuer, assetNameB, 1000000); - qutil.issueAsset(issuer, assetNameC, 1000000); - - // Distribute assets - // Voters 0-2: 2000 A, 500 B, 500 C - for (int i = 0; i < 3; i++) - { - qutil.transferAsset(issuer, voters[i], assetA, 2000); - qutil.transferAsset(issuer, voters[i], assetB, 500); - qutil.transferAsset(issuer, voters[i], assetC, 500); - } - // Voters 3-5: 500 A, 2000 B, 500 C - for (int i = 3; i < 6; i++) - { - qutil.transferAsset(issuer, voters[i], assetA, 500); - qutil.transferAsset(issuer, voters[i], assetB, 2000); - qutil.transferAsset(issuer, voters[i], assetC, 500); - } - // Voters 6-9: 500 A, 500 B, 2000 C - for (int i = 6; i < 10; i++) - { - qutil.transferAsset(issuer, voters[i], assetA, 500); - qutil.transferAsset(issuer, voters[i], assetB, 500); - qutil.transferAsset(issuer, voters[i], assetC, 2000); - } - - // Create asset poll - Array allowed_assets; - allowed_assets.set(0, assetA); - allowed_assets.set(1, assetB); - allowed_assets.set(2, assetC); - QUTIL::CreatePoll_input create_input; - create_input.poll_name = generateRandomId(); - create_input.poll_type = QUTIL_POLL_TYPE_ASSET; - create_input.min_amount = 1000; - create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); - create_input.allowed_assets = allowed_assets; - create_input.num_assets = 3; - auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); - uint64_t poll_id = create_output.poll_id; - - for (int i = 0; i < 10; i++) - { - uint64_t option = (i < 3) ? 0 : (i < 6 ? 1 : 2); - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voters[i]; - vote_input.amount = 2000; // Max holding - vote_input.chosen_option = option; - auto vote_output = qutil.vote(voters[i], vote_input, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_output.success); - } - - // Check initial results - auto result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.is_active, 1); - EXPECT_EQ(result.result.get(0), 3 * 2000); // Voters 0-2 - EXPECT_EQ(result.result.get(1), 3 * 2000); // Voters 3-5 - EXPECT_EQ(result.result.get(2), 4 * 2000); // Voters 6-9 - EXPECT_EQ(result.voter_count.get(0), 3); - EXPECT_EQ(result.voter_count.get(1), 3); - EXPECT_EQ(result.voter_count.get(2), 4); - - // Make 3 voters ineligible - // Voter 0: All assets to 999 - qutil.transferAsset(voters[0], issuer, assetA, 1001); // A: 2000 - 1001 = 999 - qutil.transferAsset(issuer, voters[0], assetB, 499); // B: 500 + 499 = 999 - qutil.transferAsset(issuer, voters[0], assetC, 499); // C: 500 + 499 = 999 - // Voter 3: Reduce B to 999 - qutil.transferAsset(voters[3], issuer, assetB, 1001); // B: 2000 - 1001 = 999 - // Voter 6: Reduce C to 999 - qutil.transferAsset(voters[6], issuer, assetC, 1001); // C: 2000 - 1001 = 999 - - // Trigger voter list to update - QUTIL::Vote_input vote_input1; - vote_input1.poll_id = poll_id; - vote_input1.address = voters[1]; - vote_input1.amount = 2000; - vote_input1.chosen_option = 0; - auto vote_output1 = qutil.vote(voters[1], vote_input1, QUTIL_VOTE_FEE); - EXPECT_TRUE(vote_output1.success); - - // Check updated results - result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.result.get(0), 2 * 2000); // Voters 1,2 - EXPECT_EQ(result.result.get(1), 2 * 2000); // Voters 4,5 - EXPECT_EQ(result.result.get(2), 3 * 2000); // Voters 7-9 - EXPECT_EQ(result.voter_count.get(0), 2); - EXPECT_EQ(result.voter_count.get(1), 2); - EXPECT_EQ(result.voter_count.get(2), 3); - - // Verify ineligible voters cannot vote - for (int i : {0, 3, 6}) - { - QUTIL::Vote_input vote_input; - vote_input.poll_id = poll_id; - vote_input.address = voters[i]; - vote_input.amount = 2000; - vote_input.chosen_option = (i < 3) ? 0 : (i < 6 ? 1 : 2); - auto vote_output = qutil.vote(voters[i], vote_input, QUTIL_VOTE_FEE); - EXPECT_FALSE(vote_output.success); - } - - qutil.endEpoch(); - qutil.beginEpoch(); - - // Check updated results - result = qutil.getCurrentResult(poll_id); - EXPECT_EQ(result.is_active, 0); - EXPECT_EQ(result.result.get(0), 2 * 2000); // Voters 1,2 - EXPECT_EQ(result.result.get(1), 2 * 2000); // Voters 4,5 - EXPECT_EQ(result.result.get(2), 3 * 2000); // Voters 7-9 - EXPECT_EQ(result.voter_count.get(0), 2); - EXPECT_EQ(result.voter_count.get(1), 2); - EXPECT_EQ(result.voter_count.get(2), 3); -} - -TEST(QUtilTest, DistributeQuToShareholders) -{ - ContractTestingQUtil qutil; - - id distributor = generateRandomId(); - id issuer = generateRandomId(); - std::vector shareholder(10); - for (auto& v : shareholder) - { - v = generateRandomId(); - } - - increaseEnergy(issuer, 4000000000); // for issuance and transfers - increaseEnergy(distributor, 10000000000); - - // Issue 3 asset - unsigned long long assetNameA = assetNameFromString("ASSETA"); - unsigned long long assetNameB = assetNameFromString("ASSETB"); - unsigned long long assetNameC = assetNameFromString("ASSETC"); - Asset assetA = { issuer, assetNameA }; - Asset assetB = { issuer, assetNameB }; - Asset assetC = { issuer, assetNameC }; - qutil.issueAsset(issuer, assetNameA, 10); - qutil.issueAsset(issuer, assetNameB, 10000); - qutil.issueAsset(issuer, assetNameC, 10000000); - - // Distribute assets - // shareholder 0-2: 1 A, 500 B, 500 C - for (int i = 0; i < 3; i++) - { - qutil.transferAsset(issuer, shareholder[i], assetA, 1); - qutil.transferAsset(issuer, shareholder[i], assetB, 500); - qutil.transferAsset(issuer, shareholder[i], assetC, 600); - } - // shareholder 3-5: 0 A, 2000 B, 500 C - for (int i = 3; i < 6; i++) - { - qutil.transferAsset(issuer, shareholder[i], assetB, 2000); - qutil.transferAsset(issuer, shareholder[i], assetC, 500); - } - // shareholder 6-9: 1 A, 500 B, 0 C - for (int i = 6; i < 10; i++) - { - qutil.transferAsset(issuer, shareholder[i], assetA, 1); - qutil.transferAsset(issuer, shareholder[i], assetB, 500); - } - - QUTIL::DistributeQuToShareholders_output output; - sint64 distributorBalanceBefore, shareholderBalanceBefore; - - // Error case 1: asset without shareholders - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, { distributor, assetNameA }, 10000000); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore); - EXPECT_EQ(output.shareholders, 0); - EXPECT_EQ(output.totalShares, 0); - EXPECT_EQ(output.amountPerShare, 0); - EXPECT_EQ(output.fees, 0); - - // Error case 2: amount too low to pay fee - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetA, 1); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore); - EXPECT_EQ(output.shareholders, 8); - EXPECT_EQ(output.totalShares, 10); - EXPECT_LE(output.amountPerShare, 0); - EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Error case 3: amount too low to pay 1 QU per share - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetA, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER + 9); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore); - EXPECT_EQ(output.shareholders, 8); - EXPECT_EQ(output.totalShares, 10); - EXPECT_EQ(output.amountPerShare, 0); - EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Success case with assetA + exactly calculated amount - sint64 amountPerShare = 50; - sint64 totalAmount = 10 * amountPerShare + 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetA, totalAmount); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + amountPerShare); - EXPECT_EQ(output.shareholders, 8); - EXPECT_EQ(output.totalShares, 10); - EXPECT_EQ(output.amountPerShare, amountPerShare); - EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Success case with assetA + amount with some QUs that cannot be evenly distributed and are refundet - amountPerShare = 100; - totalAmount = 10 * amountPerShare + 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetA, totalAmount + 7); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + amountPerShare); - EXPECT_EQ(output.shareholders, 8); - EXPECT_EQ(output.totalShares, 10); - EXPECT_EQ(output.amountPerShare, amountPerShare); - EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Success case with assetB + exactly calculated amount - amountPerShare = 1000; - totalAmount = 10000 * amountPerShare + 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetB, totalAmount); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 500 * amountPerShare); - EXPECT_EQ(output.shareholders, 11); - EXPECT_EQ(output.totalShares, 10000); - EXPECT_EQ(output.amountPerShare, amountPerShare); - EXPECT_EQ(output.fees, 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Success case with assetB + amount with some QUs that cannot be evenly distributed and are refundet - amountPerShare = 42; - totalAmount = 10000 * amountPerShare + 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetB, totalAmount + 9999); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 500 * amountPerShare); - EXPECT_EQ(output.shareholders, 11); - EXPECT_EQ(output.totalShares, 10000); - EXPECT_EQ(output.amountPerShare, amountPerShare); - EXPECT_EQ(output.fees, 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Success case with assetC + exactly calculated amount (fee is minimal) - amountPerShare = 123; - totalAmount = 10000000 * amountPerShare + 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetC, totalAmount); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 600 * amountPerShare); - EXPECT_EQ(output.shareholders, 7); - EXPECT_EQ(output.totalShares, 10000000); - EXPECT_EQ(output.amountPerShare, amountPerShare); - EXPECT_EQ(output.fees, 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); - - // Success case with assetC + non-minimal fee (fee payed too much is donation for running QUTIL -> burned with fee) - amountPerShare = 654; - totalAmount = 10000000 * amountPerShare + 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; - distributorBalanceBefore = getBalance(distributor); - shareholderBalanceBefore = getBalance(shareholder[0]); - output = qutil.distributeQuToShareholders(distributor, assetC, totalAmount + 123456); - EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); - EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 600 * amountPerShare); - EXPECT_EQ(output.shareholders, 7); - EXPECT_EQ(output.totalShares, 10000000); - EXPECT_EQ(output.amountPerShare, amountPerShare); - EXPECT_EQ(output.fees, 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); -} diff --git a/test/contract_qvault.cpp b/test/contract_qvault.cpp deleted file mode 100644 index d73f8e150..000000000 --- a/test/contract_qvault.cpp +++ /dev/null @@ -1,874 +0,0 @@ -#define NO_UEFI - -#include -#include - -#include "contract_testing.h" - -static std::mt19937_64 rand64; -static constexpr uint64 QVAULT_QCAP_MAX_HOLDERS = 131072; -static constexpr uint64 QVAULT_ISSUE_ASSET_FEE = 1000000000ull; -static constexpr uint64 QVAULT_TOKEN_TRANSFER_FEE = 1000000ull; -static constexpr uint32 QVAULT_SMALL_AMOUNT_QCAP_TRANSFER = 1000; -static constexpr uint32 QVAULT_BIG_AMOUNT_QCAP_TRANSFER = 1000000; -static constexpr uint64 QVAULT_MAX_REVENUE = 1000000000000ull; -static constexpr uint64 QVAULT_MIN_REVENUE = 100000000000ull; -static const id QVAULT_CONTRACT_ID(QVAULT_CONTRACT_INDEX, 0, 0, 0); -const id QVAULT_QCAP_ISSUER = ID(_Q, _C, _A, _P, _W, _M, _Y, _R, _S, _H, _L, _B, _J, _H, _S, _T, _T, _Z, _Q, _V, _C, _I, _B, _A, _R, _V, _O, _A, _S, _K, _D, _E, _N, _A, _S, _A, _K, _N, _O, _B, _R, _G, _P, _F, _W, _W, _K, _R, _C, _U, _V, _U, _A, _X, _Y, _E); -const id QVAULT_authAddress1 = ID(_T, _K, _U, _W, _W, _S, _N, _B, _A, _E, _G, _W, _J, _H, _Q, _J, _D, _F, _L, _G, _Q, _H, _J, _J, _C, _J, _B, _A, _X, _B, _S, _Q, _M, _Q, _A, _Z, _J, _J, _D, _Y, _X, _E, _P, _B, _V, _B, _B, _L, _I, _Q, _A, _N, _J, _T, _I, _D); -const id QVAULT_authAddress2 = ID(_F, _X, _J, _F, _B, _T, _J, _M, _Y, _F, _J, _H, _P, _B, _X, _C, _D, _Q, _T, _L, _Y, _U, _K, _G, _M, _H, _B, _B, _Z, _A, _A, _F, _T, _I, _C, _W, _U, _K, _R, _B, _M, _E, _K, _Y, _N, _U, _P, _M, _R, _M, _B, _D, _N, _D, _R, _G); -const id QVAULT_authAddress3 = ID(_K, _E, _F, _D, _Z, _T, _Y, _L, _F, _E, _R, _A, _H, _D, _V, _L, _N, _Q, _O, _R, _D, _H, _F, _Q, _I, _B, _S, _B, _Z, _C, _W, _S, _Z, _X, _Z, _F, _F, _A, _N, _O, _T, _F, _A, _H, _W, _M, _O, _V, _G, _T, _R, _Q, _J, _P, _X, _D); -const id QVAULT_reinvestingAddress = ID(_R, _U, _U, _Y, _R, _V, _N, _K, _J, _X, _M, _L, _R, _B, _B, _I, _R, _I, _P, _D, _I, _B, _M, _H, _D, _H, _U, _A, _Z, _B, _Q, _K, _N, _B, _J, _T, _R, _D, _S, _P, _G, _C, _L, _Z, _C, _Q, _W, _A, _K, _C, _F, _Q, _J, _K, _K, _E); -const id QVAULT_adminAddress = ID(_H, _E, _C, _G, _U, _G, _H, _C, _J, _K, _Q, _O, _S, _D, _T, _M, _E, _H, _Q, _Y, _W, _D, _D, _T, _L, _F, _D, _A, _S, _Z, _K, _M, _G, _J, _L, _S, _R, _C, _S, _T, _H, _H, _A, _P, _P, _E, _D, _L, _G, _B, _L, _X, _J, _M, _N, _D); -const id QVAULT_initialBannedAddress1 = ID(_K, _E, _F, _D, _Z, _T, _Y, _L, _F, _E, _R, _A, _H, _D, _V, _L, _N, _Q, _O, _R, _D, _H, _F, _Q, _I, _B, _S, _B, _Z, _C, _W, _S, _Z, _X, _Z, _F, _F, _A, _N, _O, _T, _F, _A, _H, _W, _M, _O, _V, _G, _T, _R, _Q, _J, _P, _X, _D); -const id QVAULT_initialBannedAddress2 = ID(_E, _S, _C, _R, _O, _W, _B, _O, _T, _F, _T, _F, _I, _C, _I, _F, _P, _U, _X, _O, _J, _K, _G, _Q, _P, _Y, _X, _C, _A, _B, _L, _Z, _V, _M, _M, _U, _C, _M, _J, _F, _S, _G, _S, _A, _I, _A, _T, _Y, _I, _N, _V, _T, _Y, _G, _O, _A); - -static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) -{ - if(minValue > maxValue) - { - return 0; - } - return minValue + rand64() % (maxValue - minValue); -} - -static id getUser(unsigned long long i) -{ - return id(i, i / 2 + 4, i + 10, i * 3 + 8); -} - -static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) -{ - unsigned long long userCount = random(0, maxNum); - std::vector users; - users.reserve(userCount); - for (unsigned int i = 0; i < userCount; ++i) - { - unsigned long long userIdx = random(0, totalUsers - 1); - users.push_back(getUser(userIdx)); - } - return users; -} - -class QVAULTChecker : public QVAULT -{ -public: - void endEpochChecker(uint64 revenue, const std::vector& QCAPHolders) - { - uint64 paymentForShareholders = QPI::div(revenue * shareholderDividend, 1000ULL); - uint64 paymentForQCAPHolders = QPI::div(revenue * QCAPHolderPermille, 1000ULL); - uint64 paymentForReinvest = QPI::div(revenue * reinvestingPermille, 1000ULL); - uint64 amountOfBurn = QPI::div(revenue * burnPermille, 1000ULL); - uint64 paymentForDevelopment = revenue - paymentForShareholders - paymentForQCAPHolders - paymentForReinvest - amountOfBurn; - - if(paymentForReinvest > QVAULT_MAX_REINVEST_AMOUNT) - { - paymentForQCAPHolders += paymentForReinvest - QVAULT_MAX_REINVEST_AMOUNT; - paymentForReinvest = QVAULT_MAX_REINVEST_AMOUNT; - } - - uint64 QCAPCirculatedSupply = QVAULT_QCAP_MAX_SUPPLY; - - for(uint64 i = 0 ; i < QCAPHolders.size(); i++) - { - for(uint64 j = 0 ; j < numberOfBannedAddress; j++) - { - if(QCAPHolders[i] == bannedAddress.get(j)) - { - QCAPCirculatedSupply -= numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, QCAPHolders[i], QCAPHolders[i], QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); - break; - } - } - } - - QCAPCirculatedSupply -= numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, QVAULT_initialBannedAddress1, QVAULT_initialBannedAddress1, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); - QCAPCirculatedSupply -= numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, QVAULT_initialBannedAddress2, QVAULT_initialBannedAddress2, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); - /* - This for loop will check the revenue distributed to QCAPHolders. - */ - for (const auto& user : QCAPHolders) - { - uint64 j = 0; - for(j = 0 ; j < numberOfBannedAddress; j++) - { - if(user == bannedAddress.get(j)) - { - break; - } - } - if(j != numberOfBannedAddress) - { - continue; - } - EXPECT_EQ(QPI::div(paymentForQCAPHolders, QCAPCirculatedSupply) * numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, user, user, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), getBalance(user) - 1); - } - if(paymentForReinvest > QVAULT_MAX_REINVEST_AMOUNT) - { - EXPECT_EQ(QVAULT_MAX_REINVEST_AMOUNT, getBalance(reinvestingAddress)); - } - else - { - EXPECT_EQ(paymentForReinvest, getBalance(reinvestingAddress)); - } - EXPECT_EQ(paymentForDevelopment, getBalance(adminAddress)); - } - - void balanceChecker(const id& user) - { - EXPECT_EQ(getBalance(user), 1); - } - - void submitAuthAddressChecker() - { - EXPECT_EQ(NULL_ID, newAuthAddress1); - EXPECT_EQ(NULL_ID, newAuthAddress2); - EXPECT_EQ(NULL_ID, newAuthAddress3); - } - - void submitAuthAddressWithExactAuthId(const id& newAuthAddress) - { - EXPECT_EQ(newAuthAddress1, newAuthAddress); - EXPECT_EQ(newAuthAddress2, newAuthAddress); - EXPECT_EQ(newAuthAddress3, newAuthAddress); - } - - void changeAuthAddressChecker(uint32 numberOfAuth, const id& newAuthAddress) - { - if(numberOfAuth == 1) - { - EXPECT_EQ(authAddress1, newAuthAddress); - } - else if(numberOfAuth == 2) - { - EXPECT_EQ(authAddress2, newAuthAddress); - } - else - { - EXPECT_EQ(authAddress3, newAuthAddress); - } - } - - void submitDistributionPermilleChecker(uint32 newQCAPHolderPt, uint32 newReinvestingPt, uint32 newDevPt) - { - EXPECT_EQ(newQCAPHolderPt, newQCAPHolderPermille1); - EXPECT_EQ(newQCAPHolderPt, newQCAPHolderPermille2); - EXPECT_EQ(newQCAPHolderPt, newQCAPHolderPermille3); - - EXPECT_EQ(newReinvestingPt, newReinvestingPermille1); - EXPECT_EQ(newReinvestingPt, newReinvestingPermille2); - EXPECT_EQ(newReinvestingPt, newReinvestingPermille3); - - EXPECT_EQ(newDevPt, newDevPermille1); - EXPECT_EQ(newDevPt, newDevPermille2); - EXPECT_EQ(newDevPt, newDevPermille3); - } - - void changeDistributionPermilleChecker(uint32 newQCAPHolderPt, uint32 newReinvestingPt, uint32 newDevPt) - { - EXPECT_EQ(newQCAPHolderPt, QCAPHolderPermille); - EXPECT_EQ(newReinvestingPt, reinvestingPermille); - EXPECT_EQ(newDevPt, devPermille); - } - - void submitReinvestingAddressChecker(const id& newReinvestingAddress) - { - EXPECT_EQ(newReinvestingAddress1, newReinvestingAddress); - EXPECT_EQ(newReinvestingAddress2, newReinvestingAddress); - EXPECT_EQ(newReinvestingAddress3, newReinvestingAddress); - } - - void changeReinvestingAddressChecker(const id& newReinvestingAddress) - { - EXPECT_EQ(reinvestingAddress, newReinvestingAddress); - } - - void submitAdminAddressChecker(const id& newAdminAddress) - { - EXPECT_EQ(newAdminAddress1, newAdminAddress); - EXPECT_EQ(newAdminAddress2, newAdminAddress); - EXPECT_EQ(newAdminAddress3, newAdminAddress); - } - - void changeAdminAddressChecker(const id& newAdminAddress) - { - EXPECT_EQ(adminAddress, newAdminAddress); - } - - void submitBannedAddressChecker(const id& newBannedAddress) - { - EXPECT_EQ(bannedAddress1, newBannedAddress); - EXPECT_EQ(bannedAddress2, newBannedAddress); - EXPECT_EQ(bannedAddress3, newBannedAddress); - } - - void saveBannedAddressChecker(const id& newBannedAddress) - { - EXPECT_EQ(bannedAddress.get(numberOfBannedAddress - 1), newBannedAddress); - } - - void submitUnbannedAddressChecker(const id& newUnbannedAddress) - { - EXPECT_EQ(unbannedAddress1, newUnbannedAddress); - EXPECT_EQ(unbannedAddress2, newUnbannedAddress); - EXPECT_EQ(unbannedAddress3, newUnbannedAddress); - } - - void saveUnbannedAddressChecker(const id& unbannedAddress) - { - for(uint32 i = 0 ; i < numberOfBannedAddress; i++) - { - EXPECT_NE(unbannedAddress, bannedAddress.get(i)); - } - } - - void getDataChecker(const getData_output& output) - { - EXPECT_EQ(output.authAddress1, authAddress1); - EXPECT_EQ(output.authAddress2, authAddress2); - EXPECT_EQ(output.authAddress3, authAddress3); - EXPECT_EQ(output.reinvestingAddress, reinvestingAddress); - EXPECT_EQ(output.shareholderDividend, shareholderDividend); - EXPECT_EQ(output.devPermille, devPermille); - EXPECT_EQ(output.QCAPHolderPermille, QCAPHolderPermille); - EXPECT_EQ(output.reinvestingPermille, reinvestingPermille); - EXPECT_EQ(output.adminAddress, adminAddress); - EXPECT_EQ(output.newAuthAddress1, newAuthAddress1); - EXPECT_EQ(output.newAuthAddress2, newAuthAddress2); - EXPECT_EQ(output.newAuthAddress3, newAuthAddress3); - EXPECT_EQ(output.newAdminAddress1, newAdminAddress1); - EXPECT_EQ(output.newAdminAddress2, newAdminAddress2); - EXPECT_EQ(output.newAdminAddress3, newAdminAddress3); - EXPECT_EQ(output.newReinvestingAddress1, newReinvestingAddress1); - EXPECT_EQ(output.newReinvestingAddress2, newReinvestingAddress2); - EXPECT_EQ(output.newReinvestingAddress3, newReinvestingAddress3); - EXPECT_EQ(output.numberOfBannedAddress, numberOfBannedAddress); - EXPECT_EQ(output.bannedAddress1, bannedAddress1); - EXPECT_EQ(output.bannedAddress2, bannedAddress2); - EXPECT_EQ(output.bannedAddress3, bannedAddress3); - EXPECT_EQ(output.unbannedAddress1, unbannedAddress1); - EXPECT_EQ(output.unbannedAddress2, unbannedAddress2); - EXPECT_EQ(output.unbannedAddress3, unbannedAddress3); - } -}; - -class ContractTestingQvault : protected ContractTesting -{ -public: - ContractTestingQvault() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QVAULT); - callSystemProcedure(QVAULT_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - QVAULTChecker* getState() - { - return (QVAULTChecker*)contractStates[QVAULT_CONTRACT_INDEX]; - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(QVAULT_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - QVAULT::getData_output getData() const - { - QVAULT::getData_input input; - QVAULT::getData_output output; - - callFunction(QVAULT_CONTRACT_INDEX, 1, input, output); - return output; - } - - void submitAuthAddress(const id& authAddress, const id& newAuthAddress) - { - QVAULT::submitAuthAddress_input input; - QVAULT::submitAuthAddress_output output; - - input.newAddress = newAuthAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 1, input, output, authAddress, 0); - } - - void changeAuthAddress(const id& authAddress, uint32 numberOfChangedAddress) - { - QVAULT::changeAuthAddress_input input{numberOfChangedAddress}; - QVAULT::changeAuthAddress_output output; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 2, input, output, authAddress, 0); - } - - void submitDistributionPermille(const id& authAddress, uint32 newQCAPHolderPermille, uint32 newReinvestingPermille, uint32 newDevPermille) - { - QVAULT::submitDistributionPermille_input input; - QVAULT::submitDistributionPermille_output output; - - input.newDevPermille = newDevPermille; - input.newQCAPHolderPermille = newQCAPHolderPermille; - input.newReinvestingPermille = newReinvestingPermille; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 3, input, output, authAddress, 0); - } - - void changeDistributionPermille(const id& authAddress, uint32 newQCAPHolderPermille, uint32 newReinvestingPermille, uint32 newDevPermille) - { - QVAULT::changeDistributionPermille_input input; - QVAULT::changeDistributionPermille_output output; - - input.newDevPermille = newDevPermille; - input.newQCAPHolderPermille = newQCAPHolderPermille; - input.newReinvestingPermille = newReinvestingPermille; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 4, input, output, authAddress, 0); - } - - void submitReinvestingAddress(const id& authAddress, const id& newReinvestingAddress) - { - QVAULT::submitReinvestingAddress_input input; - QVAULT::submitReinvestingAddress_output output; - - input.newAddress = newReinvestingAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 5, input, output, authAddress, 0); - } - - void changeReinvestingAddress(const id& authAddress, const id& newReinvestingAddress) - { - QVAULT::changeReinvestingAddress_input input; - QVAULT::changeReinvestingAddress_output output; - - input.newAddress = newReinvestingAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 6, input, output, authAddress, 0); - } - - void submitAdminAddress(const id& authAddress, const id& newAdminAddress) - { - QVAULT::submitAdminAddress_input input; - QVAULT::submitAdminAddress_output output; - - input.newAddress = newAdminAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 7, input, output, authAddress, 0); - } - - void changeAdminAddress(const id& authAddress, const id& newAdminAddress) - { - QVAULT::changeAdminAddress_input input; - QVAULT::changeAdminAddress_output output; - - input.newAddress = newAdminAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 8, input, output, authAddress, 0); - } - - void submitBannedAddress(const id& authAddress, const id& bannedAddress) - { - QVAULT::submitBannedAddress_input input; - QVAULT::submitBannedAddress_output output; - - input.bannedAddress = bannedAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 9, input, output, authAddress, 0); - } - - void saveBannedAddress(const id& authAddress, const id& bannedAddress) - { - QVAULT::saveBannedAddress_input input; - QVAULT::saveBannedAddress_output output; - - input.bannedAddress = bannedAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 10, input, output, authAddress, 0); - } - - void submitUnbannedAddress(const id& authAddress, const id& unbannedAddress) - { - QVAULT::submitUnbannedAddress_input input; - QVAULT::submitUnbannedAddress_output output; - - input.unbannedAddress = unbannedAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 11, input, output, authAddress, 0); - } - - void saveUnbannedAddress(const id& authAddress, const id& unbannedAddress) - { - QVAULT::unblockBannedAddress_input input; - QVAULT::unblockBannedAddress_output output; - - input.unbannedAddress = unbannedAddress; - - invokeUserProcedure(QVAULT_CONTRACT_INDEX, 12, input, output, authAddress, 0); - } - - sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) - { - QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QVAULT_ISSUE_ASSET_FEE); - return output.issuedNumberOfShares; - } - - sint64 TransferShareOwnershipAndPossession(const id& issuer, uint64 assetName, sint64 numberOfShares, id newOwnerAndPossesor) - { - QX::TransferShareOwnershipAndPossession_input input; - QX::TransferShareOwnershipAndPossession_output output; - - input.assetName = assetName; - input.issuer = issuer; - input.newOwnerAndPossessor = newOwnerAndPossesor; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, issuer, QVAULT_TOKEN_TRANSFER_FEE); - - return output.transferredNumberOfShares; - } -}; - -TEST(ContractQvault, END_EPOCH) -{ - ContractTestingQvault qvault; - - id issuer = QVAULT_QCAP_ISSUER; - uint64 assetName = assetNameFromString("QCAP"); - sint64 numberOfShares = QVAULT_QCAP_MAX_SUPPLY; - - - increaseEnergy(issuer, QVAULT_ISSUE_ASSET_FEE); - EXPECT_EQ(qvault.issueAsset(issuer, assetName, numberOfShares, 0, 0), numberOfShares); - - uint64 currentAmount = QVAULT_QCAP_MAX_SUPPLY; - uint64 numberOfHolder = 0; - bool flag = 0; - auto QCAPHolders = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - std::map transferChecker; - - /* - sending the QCAP token to bannedAddresses - */ - increaseEnergy(issuer, QVAULT_TOKEN_TRANSFER_FEE); - increaseEnergy(QVAULT_initialBannedAddress1, 1); - currentAmount -= qvault.TransferShareOwnershipAndPossession(issuer, assetName, random(0, QVAULT_BIG_AMOUNT_QCAP_TRANSFER), QVAULT_initialBannedAddress1); - - increaseEnergy(issuer, QVAULT_TOKEN_TRANSFER_FEE); - increaseEnergy(QVAULT_initialBannedAddress2, 1); - currentAmount -= qvault.TransferShareOwnershipAndPossession(issuer, assetName, random(0, QVAULT_BIG_AMOUNT_QCAP_TRANSFER), QVAULT_initialBannedAddress2); - /* - this while statement will distribute the QCAP token to holders. - */ - while(1) - { - uint64 amountOfQCAPTransfer; - if(flag) - { - amountOfQCAPTransfer = random(0, QVAULT_BIG_AMOUNT_QCAP_TRANSFER); - } - else - { - amountOfQCAPTransfer = random(0, QVAULT_SMALL_AMOUNT_QCAP_TRANSFER); - } - if(currentAmount < amountOfQCAPTransfer || numberOfHolder == QCAPHolders.size()) - { - break; - } - - if(transferChecker[QCAPHolders[numberOfHolder]]) - { - QCAPHolders.erase(QCAPHolders.begin() + numberOfHolder); - continue; - } - transferChecker[QCAPHolders[numberOfHolder]] = 1; - currentAmount -= amountOfQCAPTransfer; - increaseEnergy(issuer, QVAULT_TOKEN_TRANSFER_FEE); - increaseEnergy(QCAPHolders[numberOfHolder], 1); - qvault.getState()->balanceChecker(QCAPHolders[numberOfHolder]); - EXPECT_EQ(qvault.TransferShareOwnershipAndPossession(issuer, assetName, amountOfQCAPTransfer, QCAPHolders[numberOfHolder]), amountOfQCAPTransfer); - numberOfHolder++; - } - - uint64 revenue = random(QVAULT_MIN_REVENUE, QVAULT_MAX_REVENUE); - increaseEnergy(QVAULT_CONTRACT_ID, revenue); - qvault.endEpoch(); - qvault.getState()->endEpochChecker(revenue, QCAPHolders); -} - -TEST(ContractQvault, submitAuthAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - for (const auto& user : randomAddresses) - { - // make sure that user exists in spectrum - increaseEnergy(user, 1); - - // checking to change the auth address using the non-authAddress - qvault.submitAuthAddress(user, user); - qvault.getState()->submitAuthAddressChecker(); - } - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the auth address using the exact authAddresss - qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.getState()->submitAuthAddressWithExactAuthId(randomAddresses[0]); -} - -TEST(ContractQvault, changeAuthAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the authAddress3 with exact process - qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.changeAuthAddress(QVAULT_authAddress1, 3); - qvault.getState()->changeAuthAddressChecker(3, randomAddresses[0]); - - qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[1]); - qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.changeAuthAddress(QVAULT_authAddress2, 3); - qvault.getState()->changeAuthAddressChecker(3, randomAddresses[1]); - - qvault.submitAuthAddress(QVAULT_authAddress1, QVAULT_authAddress3); - qvault.submitAuthAddress(QVAULT_authAddress2, QVAULT_authAddress3); - qvault.changeAuthAddress(QVAULT_authAddress2, 3); - qvault.getState()->changeAuthAddressChecker(3, QVAULT_authAddress3); - - // checking to change the authAddress2 with exact process - qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.changeAuthAddress(QVAULT_authAddress1, 2); - qvault.getState()->changeAuthAddressChecker(2, randomAddresses[0]); - - qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[1]); - qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[1]); - qvault.changeAuthAddress(QVAULT_authAddress3, 2); - qvault.getState()->changeAuthAddressChecker(2, randomAddresses[1]); - - qvault.submitAuthAddress(QVAULT_authAddress1, QVAULT_authAddress2); - qvault.submitAuthAddress(QVAULT_authAddress3, QVAULT_authAddress2); - qvault.changeAuthAddress(QVAULT_authAddress3, 2); - qvault.getState()->changeAuthAddressChecker(2, QVAULT_authAddress2); - - // checking to change the authAddress1 with exact process - qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.changeAuthAddress(QVAULT_authAddress2, 1); - qvault.getState()->changeAuthAddressChecker(1, randomAddresses[0]); - - qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[1]); - qvault.changeAuthAddress(QVAULT_authAddress3, 1); - qvault.getState()->changeAuthAddressChecker(1, randomAddresses[1]); - - qvault.submitAuthAddress(QVAULT_authAddress2, QVAULT_authAddress1); - qvault.submitAuthAddress(QVAULT_authAddress3, QVAULT_authAddress1); - qvault.changeAuthAddress(QVAULT_authAddress3, 1); - qvault.getState()->changeAuthAddressChecker(1, QVAULT_authAddress1); -} - -TEST(ContractQvault, submitDistributionPermille) -{ - ContractTestingQvault qvault; - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the Permille - qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); - qvault.getState()->submitDistributionPermilleChecker(500, 400, 70); -} - -TEST(ContractQvault, changeDistributionPermille) -{ - ContractTestingQvault qvault; - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the Permille - qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); - qvault.changeDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.getState()->changeDistributionPermilleChecker(500, 400, 70); - - qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); - qvault.changeDistributionPermille(QVAULT_authAddress2, 500, 400, 70); - qvault.getState()->changeDistributionPermilleChecker(500, 400, 70); - - qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); - qvault.changeDistributionPermille(QVAULT_authAddress3, 500, 400, 70); - qvault.getState()->changeDistributionPermilleChecker(500, 400, 70); -} - -TEST(ContractQvault, submitReinvestingAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the reinvestingAddress - qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.getState()->submitReinvestingAddressChecker(randomAddresses[0]); -} - -TEST(ContractQvault, changeReinvestingAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the reinvestingAddress - qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.changeReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.getState()->changeReinvestingAddressChecker(randomAddresses[0]); - - qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[1]); - qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[1]); - qvault.changeReinvestingAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.getState()->changeReinvestingAddressChecker(randomAddresses[1]); - - qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[2]); - qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[2]); - qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[2]); - qvault.changeReinvestingAddress(QVAULT_authAddress3, randomAddresses[2]); - qvault.getState()->changeReinvestingAddressChecker(randomAddresses[2]); -} - -TEST(ContractQvault, submitAdminAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the adminAddress - qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.getState()->submitAdminAddressChecker(randomAddresses[0]); -} - -TEST(ContractQvault, changeAdminAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to change the adminAddress - qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.changeAdminAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.getState()->changeAdminAddressChecker(randomAddresses[0]); - - qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[1]); - qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[1]); - qvault.changeAdminAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.getState()->changeAdminAddressChecker(randomAddresses[1]); - - qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[2]); - qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[2]); - qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[2]); - qvault.changeAdminAddress(QVAULT_authAddress3, randomAddresses[2]); - qvault.getState()->changeAdminAddressChecker(randomAddresses[2]); -} - -TEST(ContractQvault, submitBannedAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to submit the bannedAddress - qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.getState()->submitBannedAddressChecker(randomAddresses[0]); -} - -TEST(ContractQvault, saveBannedAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to save the bannedAddress - qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.saveBannedAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.getState()->saveBannedAddressChecker(randomAddresses[0]); - - qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[1]); - qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[1]); - qvault.saveBannedAddress(QVAULT_authAddress2, randomAddresses[1]); - qvault.getState()->saveBannedAddressChecker(randomAddresses[1]); - - qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[2]); - qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[2]); - qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[2]); - qvault.saveBannedAddress(QVAULT_authAddress3, randomAddresses[2]); - qvault.getState()->saveBannedAddressChecker(randomAddresses[2]); -} - -TEST(ContractQvault, submitUnbannedAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - // checking to submit unbannedAddress - qvault.submitUnbannedAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitUnbannedAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitUnbannedAddress(QVAULT_authAddress3, randomAddresses[0]); - qvault.getState()->submitUnbannedAddressChecker(randomAddresses[0]); -} - -TEST(ContractQvault, unblockBannedAddress) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - for(uint32 i = 0 ; i < 10; i++) - { - qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[i]); - qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[i]); - qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[i]); - qvault.saveBannedAddress(QVAULT_authAddress1, randomAddresses[i]); - } - - // checking to unblock the bannedAddress - for(uint32 i = 0 ; i < 10; i++) - { - qvault.submitUnbannedAddress(QVAULT_authAddress1, randomAddresses[i]); - qvault.submitUnbannedAddress(QVAULT_authAddress2, randomAddresses[i]); - qvault.submitUnbannedAddress(QVAULT_authAddress3, randomAddresses[i]); - qvault.saveUnbannedAddress(QVAULT_authAddress1, randomAddresses[i]); - qvault.getState()->saveUnbannedAddressChecker(randomAddresses[i]); - } -} - -TEST(ContractQvault, getData) -{ - ContractTestingQvault qvault; - - auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); - - // make sure that user exists in spectrum - increaseEnergy(QVAULT_authAddress1, 1); - increaseEnergy(QVAULT_authAddress2, 1); - increaseEnergy(QVAULT_authAddress3, 1); - - qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); - - qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); - qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); - - qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[0]); - - qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[0]); - - qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[0]); - - qvault.submitUnbannedAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.submitUnbannedAddress(QVAULT_authAddress2, randomAddresses[0]); - qvault.submitUnbannedAddress(QVAULT_authAddress3, randomAddresses[0]); - - auto output = qvault.getData(); - qvault.getState()->getDataChecker(output); - - qvault.changeAuthAddress(QVAULT_authAddress1, 3); - qvault.changeDistributionPermille(QVAULT_authAddress1, 500, 400, 70); - qvault.changeReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.changeAdminAddress(QVAULT_authAddress1, randomAddresses[0]); - qvault.saveBannedAddress(QVAULT_authAddress1, randomAddresses[0]); - - output = qvault.getData(); - qvault.getState()->getDataChecker(output); -} \ No newline at end of file diff --git a/test/contract_qx.cpp b/test/contract_qx.cpp deleted file mode 100644 index d1fe0e827..000000000 --- a/test/contract_qx.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -#define PRINT_DETAILS 0 - -static constexpr uint64 QX_ISSUE_ASSET_FEE = 1000000000ull; - -std::string assetNameFromInt64(uint64 assetName); - -class QxChecker : public QX -{ -public: - struct Order - { - id issuer; - uint64 assetName; - id entity; - sint64 price; - sint64 numberOfShares; - - bool operator<(const Order& other) const - { - return memcmp(this, &other, sizeof(other)) < 0; - } - }; - - void checkCollectionConsistency() - { - EXPECT_EQ(_entityOrders.population(), _assetOrders.population()); - - std::set entityOrders; - std::map entityCounter; - for (uint64 i = 0; i < _entityOrders.capacity(); ++i) - { - QX::_EntityOrder order = _entityOrders.element(i); - if (!order.numberOfShares) - continue; - Order o; - o.issuer = order.issuer; - o.assetName = order.assetName; - o.entity = _entityOrders.pov(i); - o.price = _entityOrders.priority(i); - o.numberOfShares = order.numberOfShares; - entityOrders.insert(o); - - ++entityCounter[o.entity]; - } - - for (const auto& p : entityCounter) - { - EXPECT_EQ(_entityOrders.population(p.first), p.second); - } - - std::set assetOrders; - for (uint64 i = 0; i < _assetOrders.capacity(); ++i) - { - QX::_AssetOrder order = _assetOrders.element(i); - if (!order.numberOfShares) - continue; - Order o; - id pov = _assetOrders.pov(i); - o.issuer = id(pov.u64._0, pov.u64._1, pov.u64._2, 0); - o.assetName = pov.u64._3; - o.entity = order.entity; - o.price = _assetOrders.priority(i); - o.numberOfShares = order.numberOfShares; - assetOrders.insert(o); - } - - EXPECT_EQ(entityOrders.size(), assetOrders.size()); - auto it1 = entityOrders.begin(), it2 = assetOrders.begin(); - while (it1 != entityOrders.end()) - { - // issuer cannot be fully obtained from assetOrder (4th element missing) - EXPECT_EQ(it1->issuer.u64._0, it2->issuer.u64._0); - EXPECT_EQ(it1->issuer.u64._1, it2->issuer.u64._1); - EXPECT_EQ(it1->issuer.u64._2, it2->issuer.u64._2); - EXPECT_EQ(it1->assetName, it2->assetName); - EXPECT_EQ(it1->entity, it2->entity); - EXPECT_EQ(it1->price, it2->price); - EXPECT_EQ(it1->numberOfShares, it2->numberOfShares); - ++it1; ++it2; - } - } - - void cleanupCollections() - { - constexpr bool forceCleanup = false; - const auto population = _entityOrders.population(); - checkCollectionConsistency(); - if (forceCleanup) - { - _entityOrders.cleanup(); - _assetOrders.cleanup(); - } - else - { - _entityOrders.cleanupIfNeeded(30); - _assetOrders.cleanupIfNeeded(30); - } - checkCollectionConsistency(); - EXPECT_EQ(population, _entityOrders.population()); - } -}; - -class ContractTestingQx : protected ContractTesting -{ -public: - ContractTestingQx() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - } - - QxChecker* getState() - { - return (QxChecker*)contractStates[QX_CONTRACT_INDEX]; - } - - bool loadState(const CHAR16* filename) - { - return load(filename, sizeof(QX), contractStates[QX_CONTRACT_INDEX]) == sizeof(QX); - } - - // TODO: add other functions - - QX::AssetBidOrders_output assetBidOrders(const id& issuer, uint64 assetName, uint64 offset) - { - QX::AssetBidOrders_input input{ issuer, assetName, offset }; - QX::AssetBidOrders_output output; - callFunction(QX_CONTRACT_INDEX, 3, input, output); - return output; - } - - QX::EntityBidOrders_output entityBidOrders(const id& entity, uint64 offset) - { - QX::EntityBidOrders_input input{ entity, offset }; - QX::EntityBidOrders_output output; - callFunction(QX_CONTRACT_INDEX, 5, input, output); - return output; - } - - sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) - { - QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QX_ISSUE_ASSET_FEE); - return output.issuedNumberOfShares; - } - - // TODO: add other procedures - - void endTick(bool expectSuccess = true) - { - callSystemProcedure(QX_CONTRACT_INDEX, END_TICK, expectSuccess); - } -}; - - -TEST(ContractQx, IssueAsset) -{ - ContractTestingQx qx; - - id issuer(1, 2, 3, 4); - uint64 assetName = assetNameFromString("QUTIL"); - sint64 numberOfShares = 1000000; - - increaseEnergy(issuer, QX_ISSUE_ASSET_FEE); - EXPECT_EQ(qx.issueAsset(issuer, assetName, numberOfShares, 0, 0), numberOfShares); - - EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), numberOfShares); -} - -TEST(ContractQx, BugEntityBidOrders) -{ - ContractTestingQx qx; - m256i issuer = m256i::zero(); - uint64 assetName = assetNameFromString("QUTIL"); - auto entityIdentity = (const unsigned char*)"EEWCBEZNLEITWFWVEOFBLKHVXTAARMIGJNXICDIRIFDBUDGFXEYABULCFXAN"; - m256i entityPubkey; - getPublicKeyFromIdentity(entityIdentity, entityPubkey.m256i_u8); - - if (!qx.loadState(L"contract0001.128")) - { - std::cout << "Skipping test due to missing file!" << std::endl; - return; - } - - qx.getState()->checkCollectionConsistency(); - - auto entityBidOrders = qx.entityBidOrders(entityPubkey, 0); - int entityBidOrdersCount = 0; - for (auto i = 0ull; i < entityBidOrders.orders.capacity(); ++i) - { - const auto& order = entityBidOrders.orders.get(i); - if (!order.price) - break; - - if (order.issuer == issuer && order.assetName == assetName) - ++entityBidOrdersCount; - -#if PRINT_DETAILS - std::cout - << "entity " << entityPubkey - << ", issuer " << order.issuer - << ", assetName " << assetNameFromInt64(order.assetName) - << ", price " << order.price - << ", shares " << order.numberOfShares << std::endl; -#endif - } - - auto assertBidOrders = qx.assetBidOrders(issuer, assetName, 0); - int assertBidOrdersCount = 0; - for (auto i = 0ull; i < assertBidOrders.orders.capacity(); ++i) - { - const auto& order = assertBidOrders.orders.get(i); - if (!order.price) - break; - - if (order.entity == entityPubkey) - ++assertBidOrdersCount; - -#if PRINT_DETAILS - std::cout - << "entity " << order.entity - << ", issuer " << issuer - << ", assetName " << assetNameFromInt64(assetName) - << ", price " << order.price - << ", shares " << order.numberOfShares << std::endl; -#endif - } - - EXPECT_EQ(assertBidOrdersCount, entityBidOrdersCount); -} - -TEST(ContractQx, CleanupCollections) -{ - ContractTestingQx qx; - if (qx.loadState(L"contract0001.163")) - { - std::cout << "QX state file:" << std::endl; - QxChecker* state = qx.getState(); - qx.endTick(); - state->cleanupCollections(); - } - else - { - std::cout << "QX state file not found. Skipping file test..." << std::endl; - } -} diff --git a/test/contract_rl.cpp b/test/contract_rl.cpp deleted file mode 100644 index 55e0afbac..000000000 --- a/test/contract_rl.cpp +++ /dev/null @@ -1,1283 +0,0 @@ -// File: test/contract_rl.cpp -#define NO_UEFI - -#include "contract_testing.h" - -constexpr uint16 PROCEDURE_INDEX_BUY_TICKET = 1; -constexpr uint16 PROCEDURE_INDEX_SET_PRICE = 2; -constexpr uint16 PROCEDURE_INDEX_SET_SCHEDULE = 3; -constexpr uint16 FUNCTION_INDEX_GET_FEES = 1; -constexpr uint16 FUNCTION_INDEX_GET_PLAYERS = 2; -constexpr uint16 FUNCTION_INDEX_GET_WINNERS = 3; -constexpr uint16 FUNCTION_INDEX_GET_TICKET_PRICE = 4; -constexpr uint16 FUNCTION_INDEX_GET_MAX_NUM_PLAYERS = 5; -constexpr uint16 FUNCTION_INDEX_GET_STATE = 6; -constexpr uint16 FUNCTION_INDEX_GET_BALANCE = 7; -constexpr uint16 FUNCTION_INDEX_GET_NEXT_EPOCH_DATA = 8; -constexpr uint16 FUNCTION_INDEX_GET_DRAW_HOUR = 9; -constexpr uint16 FUNCTION_INDEX_GET_SCHEDULE = 10; -constexpr uint8 STATE_SELLING = static_cast(RL::EState::SELLING); -constexpr uint8 STATE_LOCKED = 0u; - -constexpr uint8 RL_ANY_DAY_DRAW_SCHEDULE = 0xFF; // 0xFF sets bits 0..6 (WED..TUE); bit 7 is unused/ignored by logic - -static uint32 makeDateStamp(uint16 year, uint8 month, uint8 day) -{ - const uint8 shortYear = static_cast(year - 2000); - return static_cast(shortYear << 9 | month << 5 | day); -} - -inline bool operator==(uint8 left, RL::EReturnCode right) -{ - return left == RL::toReturnCode(right); -} -inline bool operator==(RL::EReturnCode left, uint8 right) -{ - return right == left; -} -inline bool operator!=(uint8 left, RL::EReturnCode right) -{ - return !(left == right); -} -inline bool operator!=(RL::EReturnCode left, uint8 right) -{ - return !(right == left); -} - -// Equality operator for comparing WinnerInfo objects -// Compares all fields (address, revenue, epoch, tick, dayOfWeek) -bool operator==(const RL::WinnerInfo& left, const RL::WinnerInfo& right) -{ - return left.winnerAddress == right.winnerAddress && left.revenue == right.revenue && left.epoch == right.epoch && left.tick == right.tick && - left.dayOfWeek == right.dayOfWeek; -} - -// Test helper that exposes internal state assertions and utilities -class RLChecker : public RL -{ -public: - void checkFees(const GetFees_output& fees) - { - EXPECT_EQ(fees.returnCode, EReturnCode::SUCCESS); - - EXPECT_EQ(fees.distributionFeePercent, distributionFeePercent); - EXPECT_EQ(fees.teamFeePercent, teamFeePercent); - EXPECT_EQ(fees.winnerFeePercent, winnerFeePercent); - EXPECT_EQ(fees.burnPercent, burnPercent); - } - - void checkPlayers(const GetPlayers_output& output) const - { - EXPECT_EQ(output.returnCode, EReturnCode::SUCCESS); - EXPECT_EQ(output.players.capacity(), players.capacity()); - EXPECT_EQ(output.playerCounter, playerCounter); - - for (uint64 i = 0; i < playerCounter; ++i) - { - EXPECT_EQ(output.players.get(i), players.get(i)); - } - } - - void checkWinners(const GetWinners_output& output) const - { - EXPECT_EQ(output.returnCode, EReturnCode::SUCCESS); - EXPECT_EQ(output.winners.capacity(), winners.capacity()); - - const uint64 expectedCount = mod(winnersCounter, winners.capacity()); - EXPECT_EQ(output.winnersCounter, expectedCount); - - for (uint64 i = 0; i < expectedCount; ++i) - { - EXPECT_EQ(output.winners.get(i), winners.get(i)); - } - } - - void randomlyAddPlayers(uint64 maxNewPlayers) - { - playerCounter = mod(maxNewPlayers, players.capacity()); - for (uint64 i = 0; i < playerCounter; ++i) - { - players.set(i, id::randomValue()); - } - } - - void randomlyAddWinners(uint64 maxNewWinners) - { - const uint64 newWinnerCount = mod(maxNewWinners, winners.capacity()); - - winnersCounter = 0; - WinnerInfo wi; - - for (uint64 i = 0; i < newWinnerCount; ++i) - { - wi.epoch = 1; - wi.tick = 1; - wi.revenue = 1000000; - wi.winnerAddress = id::randomValue(); - winners.set(winnersCounter++, wi); - } - } - - void setScheduleMask(uint8 newMask) { schedule = newMask; } - - uint64 getPlayerCounter() const { return playerCounter; } - - uint64 getTicketPrice() const { return ticketPrice; } - - uint32 getLastDrawDateStamp() const { return lastDrawDateStamp; } - const id& team() const { return teamAddress; } -}; - -class ContractTestingRL : protected ContractTesting -{ -public: - ContractTestingRL() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(QX); - system.epoch = contractDescriptions[QX_CONTRACT_INDEX].constructionEpoch; - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(RL); - system.epoch = contractDescriptions[RL_CONTRACT_INDEX].constructionEpoch; - callSystemProcedure(RL_CONTRACT_INDEX, INITIALIZE); - } - - // Access internal contract state for assertions - RLChecker* state() { return reinterpret_cast(contractStates[RL_CONTRACT_INDEX]); } - - RL::GetFees_output getFees() - { - RL::GetFees_input input; - RL::GetFees_output output; - - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_FEES, input, output); - return output; - } - - RL::GetPlayers_output getPlayers() - { - RL::GetPlayers_input input; - RL::GetPlayers_output output; - - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_PLAYERS, input, output); - return output; - } - - RL::GetWinners_output getWinners() - { - RL::GetWinners_input input; - RL::GetWinners_output output; - - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_WINNERS, input, output); - return output; - } - - // Wrapper for public function RL::GetTicketPrice - RL::GetTicketPrice_output getTicketPrice() - { - RL::GetTicketPrice_input input; - RL::GetTicketPrice_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_TICKET_PRICE, input, output); - return output; - } - - // Wrapper for public function RL::GetMaxNumberOfPlayers - RL::GetMaxNumberOfPlayers_output getMaxNumberOfPlayers() - { - RL::GetMaxNumberOfPlayers_input input; - RL::GetMaxNumberOfPlayers_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_MAX_NUM_PLAYERS, input, output); - return output; - } - - // Wrapper for public function RL::GetState - RL::GetState_output getStateInfo() - { - RL::GetState_input input; - RL::GetState_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_STATE, input, output); - return output; - } - - // Wrapper for public function RL::GetBalance - // Returns current contract on-chain balance (incoming - outgoing) - RL::GetBalance_output getBalanceInfo() - { - RL::GetBalance_input input; - RL::GetBalance_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_BALANCE, input, output); - return output; - } - - // Wrapper for public function RL::GetNextEpochData - RL::GetNextEpochData_output getNextEpochData() - { - RL::GetNextEpochData_input input; - RL::GetNextEpochData_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_NEXT_EPOCH_DATA, input, output); - return output; - } - - // Wrapper for public function RL::GetDrawHour - RL::GetDrawHour_output getDrawHour() - { - RL::GetDrawHour_input input; - RL::GetDrawHour_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_DRAW_HOUR, input, output); - return output; - } - - // Wrapper for public function RL::GetSchedule - RL::GetSchedule_output getSchedule() - { - RL::GetSchedule_input input; - RL::GetSchedule_output output; - callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_SCHEDULE, input, output); - return output; - } - - RL::BuyTicket_output buyTicket(const id& user, sint64 reward) - { - RL::BuyTicket_input input; - RL::BuyTicket_output output; - if (!invokeUserProcedure(RL_CONTRACT_INDEX, PROCEDURE_INDEX_BUY_TICKET, input, output, user, reward)) - { - output.returnCode = RL::toReturnCode(RL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - // Added: wrapper for SetPrice procedure - RL::SetPrice_output setPrice(const id& invocator, uint64 newPrice) - { - RL::SetPrice_input input; - input.newPrice = newPrice; - RL::SetPrice_output output; - if (!invokeUserProcedure(RL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_PRICE, input, output, invocator, 0)) - { - output.returnCode = RL::toReturnCode(RL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - // Added: wrapper for SetSchedule procedure - RL::SetSchedule_output setSchedule(const id& invocator, uint8 newSchedule) - { - RL::SetSchedule_input input; - input.newSchedule = newSchedule; - RL::SetSchedule_output output; - if (!invokeUserProcedure(RL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_SCHEDULE, input, output, invocator, 0)) - { - output.returnCode = RL::toReturnCode(RL::EReturnCode::UNKNOWN_ERROR); - } - return output; - } - - void BeginEpoch() { callSystemProcedure(RL_CONTRACT_INDEX, BEGIN_EPOCH); } - - void EndEpoch() { callSystemProcedure(RL_CONTRACT_INDEX, END_EPOCH); } - - void BeginTick() { callSystemProcedure(RL_CONTRACT_INDEX, BEGIN_TICK); } - - // Returns the SELF contract account address - id rlSelf() const { return id(RL_CONTRACT_INDEX, 0, 0, 0); } - - // Computes remaining contract balance after winner/team/distribution/burn payouts - // Distribution is floored to a multiple of NUMBER_OF_COMPUTORS - uint64 expectedRemainingAfterPayout(uint64 before, const RL::GetFees_output& fees) - { - const uint64 burn = (before * fees.burnPercent) / 100; - const uint64 distribPer = ((before * fees.distributionFeePercent) / 100) / NUMBER_OF_COMPUTORS; - const uint64 distrib = distribPer * NUMBER_OF_COMPUTORS; // floor to a multiple - const uint64 team = (before * fees.teamFeePercent) / 100; - const uint64 winner = (before * fees.winnerFeePercent) / 100; - return before - burn - distrib - team - winner; - } - - // Fund user and buy a ticket, asserting success - void increaseAndBuy(ContractTestingRL& ctl, const id& user, uint64 ticketPrice) - { - increaseEnergy(user, ticketPrice * 2); - const RL::BuyTicket_output out = ctl.buyTicket(user, ticketPrice); - EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); - } - - // Assert contract account balance equals the value returned by RL::GetBalance - void expectContractBalanceEqualsGetBalance(ContractTestingRL& ctl, const id& contractAddress) - { - const RL::GetBalance_output out = ctl.getBalanceInfo(); - EXPECT_EQ(out.balance, getBalance(contractAddress)); - } - - // New: set full date and hour (UTC), then sync QPI time - void setDateTime(uint16 year, uint8 month, uint8 day, uint8 hour) - { - updateTime(); - utcTime.Year = year; - utcTime.Month = month; - utcTime.Day = day; - utcTime.Hour = hour; - utcTime.Minute = 0; - utcTime.Second = 0; - utcTime.Nanosecond = 0; - updateQpiTime(); - } - - // New: advance to the next tick boundary where tick % RL_TICK_UPDATE_PERIOD == 0 and run BEGIN_TICK once - void forceBeginTick() - { - system.tick = system.tick + (RL_TICK_UPDATE_PERIOD - mod(system.tick, static_cast(RL_TICK_UPDATE_PERIOD))); - - BeginTick(); - } - - // New: helper to advance one calendar day and perform a scheduled draw at 12:00 UTC - void advanceOneDayAndDraw() - { - // Use a safe base month to avoid invalid dates: January 2025 - static uint16 y = 2025; - static uint8 m = 1; - static uint8 d = 10; // start from 10th - // advance one day within January bounds - d = static_cast(d + 1); - if (d > 31) - { - d = 1; // wrap within month for simplicity in tests - } - setDateTime(y, m, d, 12); - forceBeginTick(); - } - - // Force schedule mask directly in state (bypasses external call, suitable for tests) - void forceSchedule(uint8 scheduleMask) - { - state()->setScheduleMask(scheduleMask); - // NOTE: we do not call SetSchedule here to avoid epoch transitions in tests. - } - - void beginEpochWithDate(uint16 year, uint8 month, uint8 day, uint8 hour = static_cast(RL_DEFAULT_DRAW_HOUR + 1)) - { - setDateTime(year, month, day, hour); - BeginEpoch(); - } - - void beginEpochWithValidTime() { beginEpochWithDate(2025, 1, 20); } -}; - -TEST(ContractRandomLottery, SetPriceAndScheduleApplyNextEpoch) -{ - ContractTestingRL ctl; - ctl.beginEpochWithValidTime(); - - // Default epoch configuration: draws 3 times per week at the default price - const uint64 oldPrice = ctl.state()->getTicketPrice(); - EXPECT_EQ(ctl.getSchedule().schedule, RL_DEFAULT_SCHEDULE); - - // Queue a new price (5,000,000) and limit draws to only Wednesday - constexpr uint64 newPrice = 5000000; - constexpr uint8 wednesdayOnly = static_cast(1 << WEDNESDAY); - increaseEnergy(ctl.state()->team(), 3); - EXPECT_EQ(ctl.setPrice(ctl.state()->team(), newPrice).returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.setSchedule(ctl.state()->team(), wednesdayOnly).returnCode, RL::EReturnCode::SUCCESS); - - const RL::NextEpochData nextDataBefore = ctl.getNextEpochData().nextEpochData; - EXPECT_EQ(nextDataBefore.newPrice, newPrice); - EXPECT_EQ(nextDataBefore.schedule, wednesdayOnly); - - // Until END_EPOCH the old settings remain active - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); - EXPECT_EQ(ctl.getSchedule().schedule, RL_DEFAULT_SCHEDULE); - - // Transition closes the epoch and applies both pending changes - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); - EXPECT_EQ(ctl.getSchedule().schedule, wednesdayOnly); - - const RL::NextEpochData nextDataAfter = ctl.getNextEpochData().nextEpochData; - EXPECT_EQ(nextDataAfter.newPrice, 0u); - EXPECT_EQ(nextDataAfter.schedule, 0u); - - // In the next epoch tickets must sell at the updated price - ctl.beginEpochWithDate(2025, 1, 15); // Wednesday - const id buyer = id::randomValue(); - increaseEnergy(buyer, newPrice * 2); - const uint64 balBefore = getBalance(buyer); - const uint64 playersBefore = ctl.state()->getPlayerCounter(); - const RL::BuyTicket_output buyOut = ctl.buyTicket(buyer, newPrice); - EXPECT_EQ(buyOut.returnCode, RL::EReturnCode::SUCCESS); - const uint64 playersAfterFirstBuy = playersBefore + 1; - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterFirstBuy); - EXPECT_EQ(getBalance(buyer), balBefore - newPrice); - - // Second user also buys a ticket at the new price - const id secondBuyer = id::randomValue(); - increaseEnergy(secondBuyer, newPrice * 2); - const uint64 secondBalBefore = getBalance(secondBuyer); - const RL::BuyTicket_output secondBuyOut = ctl.buyTicket(secondBuyer, newPrice); - EXPECT_EQ(secondBuyOut.returnCode, RL::EReturnCode::SUCCESS); - const uint64 playersAfterBuy = playersAfterFirstBuy + 1; - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterBuy); - EXPECT_EQ(getBalance(secondBuyer), secondBalBefore - newPrice); - - // Draws should only trigger on Wednesdays now: starting on Wednesday means the draw - // is deferred until the next Wednesday in the schedule. - const uint64 winnersBefore = ctl.getWinners().winnersCounter; - ctl.setDateTime(2025, 1, 15, RL_DEFAULT_DRAW_HOUR + 1); // current Wednesday - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterBuy); - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore); - - // No draw on non-scheduled days between Wednesdays - ctl.setDateTime(2025, 1, 21, RL_DEFAULT_DRAW_HOUR + 1); // Tuesday next week - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterBuy); - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore); - - // Next Wednesday processes the draw - ctl.setDateTime(2025, 1, 22, RL_DEFAULT_DRAW_HOUR + 1); // next Wednesday - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore + 1); - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_LOCKED); - - // After the draw and before the next epoch begins, ticket purchases are blocked - const id lockedBuyer = id::randomValue(); - increaseEnergy(lockedBuyer, newPrice); - const RL::BuyTicket_output lockedOut = ctl.buyTicket(lockedBuyer, newPrice); - EXPECT_EQ(lockedOut.returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); -} - -TEST(ContractRandomLottery, DefaultInitTimeGuardSkipsPlaceholderDate) -{ - ContractTestingRL ctl; - - const uint64 ticketPrice = ctl.state()->getTicketPrice(); - - // Allow draws every day so weekday logic does not block BEGIN_TICK - ctl.forceSchedule(RL_ANY_DAY_DRAW_SCHEDULE); - - // Simulate the placeholder 2022-04-13 QPI date during initialization - ctl.setDateTime(2022, 4, 13, RL_DEFAULT_DRAW_HOUR + 1); - ctl.BeginEpoch(); - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_LOCKED); - - // Selling is blocked until a valid date arrives - const id blockedBuyer = id::randomValue(); - increaseEnergy(blockedBuyer, ticketPrice); - const RL::BuyTicket_output denied = ctl.buyTicket(blockedBuyer, ticketPrice); - EXPECT_EQ(denied.returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - - const uint64 winnersBefore = ctl.getWinners().winnersCounter; - - // BEGIN_TICK should detect the placeholder date and skip processing, but remember the sentinel day - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getLastDrawDateStamp(), RL_DEFAULT_INIT_TIME); - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_LOCKED); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore); - - // First valid day re-opens selling but still skips the draw - ctl.setDateTime(2025, 1, 10, RL_DEFAULT_DRAW_HOUR + 1); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); - EXPECT_NE(ctl.state()->getLastDrawDateStamp(), RL_DEFAULT_INIT_TIME); - - const id playerA = id::randomValue(); - const id playerB = id::randomValue(); - ctl.increaseAndBuy(ctl, playerA, ticketPrice); - ctl.increaseAndBuy(ctl, playerB, ticketPrice); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 2u); - - // The immediate next valid day should run the actual draw - ctl.setDateTime(2025, 1, 11, RL_DEFAULT_DRAW_HOUR + 1); - ctl.forceBeginTick(); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore + 1); - EXPECT_NE(ctl.state()->getLastDrawDateStamp(), RL_DEFAULT_INIT_TIME); -} - -TEST(ContractRandomLottery, SellingUnlocksWhenTimeSetBeforeScheduledDay) -{ - ContractTestingRL ctl; - - const uint64 ticketPrice = ctl.state()->getTicketPrice(); - - ctl.setDateTime(2022, 4, 13, RL_DEFAULT_DRAW_HOUR + 1); - ctl.BeginEpoch(); - - const id deniedBuyer = id::randomValue(); - increaseEnergy(deniedBuyer, ticketPrice); - EXPECT_EQ(ctl.buyTicket(deniedBuyer, ticketPrice).returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); - - ctl.setDateTime(2025, 1, 14, RL_DEFAULT_DRAW_HOUR + 2); // Tuesday, not scheduled by default - ctl.forceBeginTick(); - - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); - EXPECT_EQ(ctl.state()->getLastDrawDateStamp(), 0u); - - const id allowedBuyer = id::randomValue(); - increaseEnergy(allowedBuyer, ticketPrice); - const RL::BuyTicket_output allowed = ctl.buyTicket(allowedBuyer, ticketPrice); - EXPECT_EQ(allowed.returnCode, RL::EReturnCode::SUCCESS); -} - -TEST(ContractRandomLottery, SellingUnlocksWhenTimeSetOnDrawDay) -{ - ContractTestingRL ctl; - - const uint64 ticketPrice = ctl.state()->getTicketPrice(); - - ctl.setDateTime(2022, 4, 13, RL_DEFAULT_DRAW_HOUR + 1); - ctl.BeginEpoch(); - - const id deniedBuyer = id::randomValue(); - increaseEnergy(deniedBuyer, ticketPrice); - EXPECT_EQ(ctl.buyTicket(deniedBuyer, ticketPrice).returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); - - ctl.setDateTime(2025, 1, 15, RL_DEFAULT_DRAW_HOUR + 2); // Wednesday draw day - ctl.forceBeginTick(); - - const uint32 expectedStamp = makeDateStamp(2025, 1, 15); - EXPECT_EQ(ctl.state()->getLastDrawDateStamp(), expectedStamp); - EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); - - const id allowedBuyer = id::randomValue(); - increaseEnergy(allowedBuyer, ticketPrice); - const RL::BuyTicket_output allowed = ctl.buyTicket(allowedBuyer, ticketPrice); - EXPECT_EQ(allowed.returnCode, RL::EReturnCode::SUCCESS); -} - -TEST(ContractRandomLottery, PostIncomingTransfer) -{ - ContractTestingRL ctl; - static constexpr uint64 transferAmount = 123456789; - - const id sender = id::randomValue(); - increaseEnergy(sender, transferAmount); - EXPECT_EQ(getBalance(sender), transferAmount); - - const id contractAddress = ctl.rlSelf(); - EXPECT_EQ(getBalance(contractAddress), 0); - - notifyContractOfIncomingTransfer(sender, contractAddress, transferAmount, QPI::TransferType::standardTransaction); - - EXPECT_EQ(getBalance(sender), transferAmount); - EXPECT_EQ(getBalance(contractAddress), 0); -} - -TEST(ContractRandomLottery, GetFees) -{ - ContractTestingRL ctl; - RL::GetFees_output output = ctl.getFees(); - ctl.state()->checkFees(output); -} - -TEST(ContractRandomLottery, GetPlayers) -{ - ContractTestingRL ctl; - - // Initially empty - RL::GetPlayers_output output = ctl.getPlayers(); - ctl.state()->checkPlayers(output); - - // Add random players directly to state (test helper) - constexpr uint64 maxPlayersToAdd = 10; - ctl.state()->randomlyAddPlayers(maxPlayersToAdd); - output = ctl.getPlayers(); - ctl.state()->checkPlayers(output); -} - -TEST(ContractRandomLottery, GetWinners) -{ - ContractTestingRL ctl; - - // Populate winners history artificially - constexpr uint64 maxNewWinners = 10; - ctl.state()->randomlyAddWinners(maxNewWinners); - RL::GetWinners_output winnersOutput = ctl.getWinners(); - ctl.state()->checkWinners(winnersOutput); -} - -TEST(ContractRandomLottery, BuyTicket) -{ - ContractTestingRL ctl; - - const uint64 ticketPrice = ctl.state()->getTicketPrice(); - - // 1. Attempt when state is LOCKED (should fail and refund invocation reward) - { - const id userLocked = id::randomValue(); - increaseEnergy(userLocked, ticketPrice * 2); - RL::BuyTicket_output out = ctl.buyTicket(userLocked, ticketPrice); - EXPECT_EQ(out.returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0); - } - - // Switch to SELLING to allow purchases - ctl.beginEpochWithValidTime(); - - // 2. Loop over several users and test invalid price, success, duplicate - constexpr uint64 userCount = 5; - uint64 expectedPlayers = 0; - - for (uint64 i = 0; i < userCount; ++i) - { - const id user = id::randomValue(); - increaseEnergy(user, ticketPrice * 5); - - // (a) Invalid price (wrong reward sent) — player not added - { - // < ticketPrice - RL::BuyTicket_output outInvalid = ctl.buyTicket(user, ticketPrice - 1); - EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::TICKET_INVALID_PRICE); - EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); - - // == 0 - outInvalid = ctl.buyTicket(user, 0); - EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::TICKET_INVALID_PRICE); - EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); - - // < 0 - outInvalid = ctl.buyTicket(user, -1LL * ticketPrice); - EXPECT_NE(outInvalid.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); - } - - // (b) Valid purchase — player added - { - const RL::BuyTicket_output outOk = ctl.buyTicket(user, ticketPrice); - EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); - ++expectedPlayers; - EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); - } - - // (c) Duplicate purchase — allowed, increases count - { - const RL::BuyTicket_output outDup = ctl.buyTicket(user, ticketPrice); - EXPECT_EQ(outDup.returnCode, RL::EReturnCode::SUCCESS); - ++expectedPlayers; - EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); - } - } - - // 3. Sanity check: number of tickets equals twice the number of users (due to duplicate buys) - EXPECT_EQ(ctl.state()->getPlayerCounter(), userCount * 2); -} - -// Updated: payout is triggered by BEGIN_TICK with schedule/time gating, not by END_EPOCH -TEST(ContractRandomLottery, DrawAndPayout_BeginTick) -{ - ContractTestingRL ctl; - - const id contractAddress = ctl.rlSelf(); - const uint64 ticketPrice = ctl.state()->getTicketPrice(); - - // Current fee configuration (set in INITIALIZE) - const RL::GetFees_output fees = ctl.getFees(); - const uint8 teamPercent = fees.teamFeePercent; // Team commission percent - const uint8 winnerPercent = fees.winnerFeePercent; // Winner payout percent - - // Ensure schedule allows draw any day - ctl.forceSchedule(RL_ANY_DAY_DRAW_SCHEDULE); - - // --- Scenario 1: No players (nothing to payout, no winner recorded) --- - { - ctl.beginEpochWithValidTime(); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - - RL::GetWinners_output before = ctl.getWinners(); - const uint64 winnersBefore = before.winnersCounter; - - // Need to move to a new day and call BEGIN_TICK to allow draw - ctl.advanceOneDayAndDraw(); - - RL::GetWinners_output after = ctl.getWinners(); - EXPECT_EQ(after.winnersCounter, winnersBefore); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - } - - // --- Scenario 2: Exactly one player (ticket refunded, no winner recorded) --- - { - ctl.beginEpochWithValidTime(); - - const id solo = id::randomValue(); - increaseEnergy(solo, ticketPrice); - const uint64 balanceBefore = getBalance(solo); - - const RL::BuyTicket_output out = ctl.buyTicket(solo, ticketPrice); - EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 1u); - EXPECT_EQ(getBalance(solo), balanceBefore - ticketPrice); - - const uint64 winnersBeforeCount = ctl.getWinners().winnersCounter; - - ctl.advanceOneDayAndDraw(); - - // Refund happened - EXPECT_EQ(getBalance(solo), balanceBefore); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - - const RL::GetWinners_output winners = ctl.getWinners(); - // No new winners appended - EXPECT_EQ(winners.winnersCounter, winnersBeforeCount); - } - - // --- Scenario 2b: Multiple tickets from the same player are treated as single participant --- - { - ctl.beginEpochWithValidTime(); - - const id solo = id::randomValue(); - increaseEnergy(solo, ticketPrice * 10); - const uint64 balanceBefore = getBalance(solo); - - for (int i = 0; i < 5; ++i) - { - EXPECT_EQ(ctl.buyTicket(solo, ticketPrice).returnCode, RL::EReturnCode::SUCCESS); - } - EXPECT_EQ(ctl.state()->getPlayerCounter(), 5u); - - const uint64 winnersBeforeCount = ctl.getWinners().winnersCounter; - - ctl.advanceOneDayAndDraw(); - - // All tickets refunded, no winner recorded - EXPECT_EQ(getBalance(solo), balanceBefore); - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBeforeCount); - EXPECT_EQ(getBalance(contractAddress), 0u); - } - - // --- Scenario 3: Multiple players (winner chosen, fees processed, correct remaining on contract) --- - { - ctl.beginEpochWithValidTime(); - - constexpr uint32 N = 5 * 2; - struct PlayerInfo - { - id addr; - uint64 balanceBefore; - uint64 balanceAfterBuy; - }; - std::vector infos; - infos.reserve(N); - - // Add N/2 distinct players, each making two valid purchases - for (uint32 i = 0; i < N; i += 2) - { - const id randomUser = id::randomValue(); - ctl.increaseAndBuy(ctl, randomUser, ticketPrice); - ctl.increaseAndBuy(ctl, randomUser, ticketPrice); - const uint64 bBefore = getBalance(randomUser); - infos.push_back({randomUser, bBefore + (ticketPrice * 2), bBefore}); // account for ticket deduction - } - - EXPECT_EQ(ctl.state()->getPlayerCounter(), N); - - const uint64 contractBalanceBefore = getBalance(contractAddress); - EXPECT_EQ(contractBalanceBefore, ticketPrice * N); - - const uint64 teamBalanceBefore = getBalance(ctl.state()->team()); - - const RL::GetWinners_output winnersBefore = ctl.getWinners(); - const uint64 winnersCountBefore = winnersBefore.winnersCounter; - - ctl.advanceOneDayAndDraw(); - - // Players reset after draw - EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); - - const RL::GetWinners_output winnersAfter = ctl.getWinners(); - EXPECT_EQ(winnersAfter.winnersCounter, winnersCountBefore + 1); - - // Newly appended winner info - const RL::WinnerInfo wi = winnersAfter.winners.get(mod(winnersCountBefore, winnersAfter.winners.capacity())); - EXPECT_NE(wi.winnerAddress, id::zero()); - EXPECT_EQ(wi.revenue, (ticketPrice * N * winnerPercent) / 100); - - // Winner address must be one of the players - bool found = false; - for (const PlayerInfo& inf : infos) - { - if (inf.addr == wi.winnerAddress) - { - found = true; - break; - } - } - EXPECT_TRUE(found); - - // Check winner balance increment and others unchanged - for (const PlayerInfo& inf : infos) - { - const uint64 bal = getBalance(inf.addr); - const uint64 balanceAfterBuy = inf.addr == wi.winnerAddress ? inf.balanceAfterBuy + wi.revenue : inf.balanceAfterBuy; - EXPECT_EQ(bal, balanceAfterBuy); - } - - // Team fee transferred - const uint64 teamFeeExpected = (ticketPrice * N * teamPercent) / 100; - EXPECT_EQ(getBalance(ctl.state()->team()), teamBalanceBefore + teamFeeExpected); - - // Burn (remaining on contract) - const uint64 burnExpected = ctl.expectedRemainingAfterPayout(contractBalanceBefore, fees); - EXPECT_EQ(getBalance(contractAddress), burnExpected); - } - - // --- Scenario 4: Several consecutive draws (winners accumulate, balances consistent) --- - { - const uint32 rounds = 3; - const uint32 playersPerRound = 6 * 2; // even number to mimic duplicates if desired - - // Remember starting winners count and team balance - const uint64 winnersStart = ctl.getWinners().winnersCounter; - const uint64 teamStartBal = getBalance(ctl.state()->team()); - - uint64 teamAccrued = 0; - - for (uint32 r = 0; r < rounds; ++r) - { - ctl.beginEpochWithValidTime(); - - struct P - { - id addr; - uint64 balAfterBuy; - }; - std::vector

roundPlayers; - roundPlayers.reserve(playersPerRound); - - // Each player buys two tickets in this round - for (uint32 i = 0; i < playersPerRound; i += 2) - { - const id u = id::randomValue(); - ctl.increaseAndBuy(ctl, u, ticketPrice); - ctl.increaseAndBuy(ctl, u, ticketPrice); - const uint64 balAfter = getBalance(u); - roundPlayers.push_back({u, balAfter}); - } - - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersPerRound); - - const uint64 winnersBefore = ctl.getWinners().winnersCounter; - const uint64 contractBefore = getBalance(contractAddress); - const uint64 teamBalBeforeRound = getBalance(ctl.state()->team()); - - ctl.advanceOneDayAndDraw(); - - // Winners should increase by exactly one - const RL::GetWinners_output wOut = ctl.getWinners(); - EXPECT_EQ(wOut.winnersCounter, winnersBefore + 1); - - // Validate winner entry - const RL::WinnerInfo newWi = wOut.winners.get(mod(winnersBefore, wOut.winners.capacity())); - EXPECT_NE(newWi.winnerAddress, id::zero()); - EXPECT_EQ(newWi.revenue, (contractBefore * winnerPercent) / 100); - - // Winner must be one of the current round players - bool inRound = false; - for (const auto& p : roundPlayers) - { - if (p.addr == newWi.winnerAddress) - { - inRound = true; - break; - } - } - EXPECT_TRUE(inRound); - - // Check players' balances after payout - for (const auto& p : roundPlayers) - { - const uint64 b = getBalance(p.addr); - const uint64 expected = (p.addr == newWi.winnerAddress) ? (p.balAfterBuy + newWi.revenue) : p.balAfterBuy; - EXPECT_EQ(b, expected); - } - - // Team fee for the whole round's contract balance - const uint64 teamFee = (contractBefore * teamPercent) / 100; - teamAccrued += teamFee; - EXPECT_EQ(getBalance(ctl.state()->team()), teamBalBeforeRound + teamFee); - - // Contract remaining should match expected - const uint64 expectedRemaining = ctl.expectedRemainingAfterPayout(contractBefore, fees); - EXPECT_EQ(getBalance(contractAddress), expectedRemaining); - } - - // After all rounds winners increased by rounds and team received cumulative fees - EXPECT_EQ(ctl.getWinners().winnersCounter, winnersStart + rounds); - EXPECT_EQ(getBalance(ctl.state()->team()), teamStartBal + teamAccrued); - } -} -TEST(ContractRandomLottery, GetBalance) -{ - ContractTestingRL ctl; - - const id contractAddress = ctl.rlSelf(); - const uint64 ticketPrice = ctl.state()->getTicketPrice(); - - // Initially, contract balance is 0 - { - const RL::GetBalance_output out0 = ctl.getBalanceInfo(); - EXPECT_EQ(out0.balance, 0u); - EXPECT_EQ(out0.balance, getBalance(contractAddress)); - } - - // Open selling and perform several purchases - ctl.beginEpochWithValidTime(); - - constexpr uint32 K = 3; - for (uint32 i = 0; i < K; ++i) - { - const id user = id::randomValue(); - ctl.increaseAndBuy(ctl, user, ticketPrice); - ctl.expectContractBalanceEqualsGetBalance(ctl, contractAddress); - } - - // Before draw, balance equals the total cost of tickets - { - const RL::GetBalance_output outBefore = ctl.getBalanceInfo(); - EXPECT_EQ(outBefore.balance, ticketPrice * K); - } - - // Trigger draw and verify expected remaining amount against contract balance and function output - const uint64 contractBalanceBefore = getBalance(contractAddress); - const RL::GetFees_output fees = ctl.getFees(); - - // Ensure schedule allows draw and perform it - ctl.forceSchedule(RL_ANY_DAY_DRAW_SCHEDULE); - ctl.advanceOneDayAndDraw(); - - const RL::GetBalance_output outAfter = ctl.getBalanceInfo(); - const uint64 envAfter = getBalance(contractAddress); - EXPECT_EQ(outAfter.balance, envAfter); - - const uint64 expectedRemaining = ctl.expectedRemainingAfterPayout(contractBalanceBefore, fees); - EXPECT_EQ(outAfter.balance, expectedRemaining); -} - -TEST(ContractRandomLottery, GetTicketPrice) -{ - ContractTestingRL ctl; - - const RL::GetTicketPrice_output out = ctl.getTicketPrice(); - EXPECT_EQ(out.ticketPrice, ctl.state()->getTicketPrice()); -} - -TEST(ContractRandomLottery, GetMaxNumberOfPlayers) -{ - ContractTestingRL ctl; - - const RL::GetMaxNumberOfPlayers_output out = ctl.getMaxNumberOfPlayers(); - // Compare against the known constant via GetPlayers capacity - const RL::GetPlayers_output playersOut = ctl.getPlayers(); - EXPECT_EQ(static_cast(out.numberOfPlayers), static_cast(playersOut.players.capacity())); -} - -TEST(ContractRandomLottery, GetState) -{ - ContractTestingRL ctl; - - // Initially LOCKED - { - const RL::GetState_output out0 = ctl.getStateInfo(); - EXPECT_EQ(out0.currentState, STATE_LOCKED); - } - - // After BeginEpoch — SELLING - ctl.beginEpochWithValidTime(); - { - const RL::GetState_output out1 = ctl.getStateInfo(); - EXPECT_EQ(out1.currentState, STATE_SELLING); - } - - // After END_EPOCH — back to LOCKED (selling disabled until next epoch) - ctl.EndEpoch(); - { - const RL::GetState_output out2 = ctl.getStateInfo(); - EXPECT_EQ(out2.currentState, STATE_LOCKED); - } -} - -// --- New tests for SetPrice and NextEpochData --- - -TEST(ContractRandomLottery, SetPrice_AccessControl) -{ - ContractTestingRL ctl; - - const uint64 oldPrice = ctl.state()->getTicketPrice(); - const uint64 newPrice = oldPrice * 2; - - // Random user must not have permission - const id randomUser = id::randomValue(); - increaseEnergy(randomUser, 1); - - const RL::SetPrice_output outDenied = ctl.setPrice(randomUser, newPrice); - EXPECT_EQ(outDenied.returnCode, RL::EReturnCode::ACCESS_DENIED); - - // Price doesn't change immediately nor after END_EPOCH implicitly - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); -} - -TEST(ContractRandomLottery, SetPrice_ZeroNotAllowed) -{ - ContractTestingRL ctl; - - increaseEnergy(ctl.state()->team(), 1); - - const uint64 oldPrice = ctl.state()->getTicketPrice(); - - const RL::SetPrice_output outInvalid = ctl.setPrice(ctl.state()->team(), 0); - EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::TICKET_INVALID_PRICE); - - // Price remains unchanged even after END_EPOCH - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); -} - -TEST(ContractRandomLottery, SetPrice_AppliesAfterEndEpoch) -{ - ContractTestingRL ctl; - - increaseEnergy(ctl.state()->team(), 1); - - const uint64 oldPrice = ctl.state()->getTicketPrice(); - const uint64 newPrice = oldPrice * 2; - - const RL::SetPrice_output outOk = ctl.setPrice(ctl.state()->team(), newPrice); - EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); - - // Check NextEpochData reflects pending change - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, newPrice); - - // Until END_EPOCH the price remains unchanged - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); - - // Applied after END_EPOCH - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); - - // NextEpochData cleared - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, 0u); - - // Another END_EPOCH without a new SetPrice doesn't change the price - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); -} - -TEST(ContractRandomLottery, SetPrice_OverrideBeforeEndEpoch) -{ - ContractTestingRL ctl; - - increaseEnergy(ctl.state()->team(), 1); - - const uint64 oldPrice = ctl.state()->getTicketPrice(); - const uint64 firstPrice = oldPrice + 1000; - const uint64 secondPrice = oldPrice + 7777; - - // Two SetPrice calls before END_EPOCH — the last one should apply - EXPECT_EQ(ctl.setPrice(ctl.state()->team(), firstPrice).returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.setPrice(ctl.state()->team(), secondPrice).returnCode, RL::EReturnCode::SUCCESS); - - // NextEpochData shows the last queued value - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, secondPrice); - - // Until END_EPOCH the old price remains - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); - - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, secondPrice); -} - -TEST(ContractRandomLottery, SetPrice_AffectsNextEpochBuys) -{ - ContractTestingRL ctl; - - increaseEnergy(ctl.state()->team(), 1); - - const uint64 oldPrice = ctl.state()->getTicketPrice(); - const uint64 newPrice = oldPrice * 3; - - // Open selling and buy at the old price - ctl.beginEpochWithValidTime(); - const id u1 = id::randomValue(); - increaseEnergy(u1, oldPrice * 2); - { - const RL::BuyTicket_output out1 = ctl.buyTicket(u1, oldPrice); - EXPECT_EQ(out1.returnCode, RL::EReturnCode::SUCCESS); - } - - // Set a new price, but before END_EPOCH purchases should use the old price logic (split by old price) - { - const RL::SetPrice_output setOut = ctl.setPrice(ctl.state()->team(), newPrice); - EXPECT_EQ(setOut.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, newPrice); - } - - const id u2 = id::randomValue(); - increaseEnergy(u2, newPrice * 2); - { - const uint64 balBefore = getBalance(u2); - const uint64 playersBefore = ctl.state()->getPlayerCounter(); - const RL::BuyTicket_output outNow = ctl.buyTicket(u2, newPrice); - EXPECT_EQ(outNow.returnCode, RL::EReturnCode::SUCCESS); - // floor(newPrice/oldPrice) tickets were bought, the remainder was refunded - const uint64 bought = newPrice / oldPrice; - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + bought); - EXPECT_EQ(getBalance(u2), balBefore - bought * oldPrice); - } - - // END_EPOCH: new price will apply - ctl.EndEpoch(); - EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); - - // In the next epoch, a purchase at the new price should succeed exactly once per price - ctl.beginEpochWithValidTime(); - { - const uint64 balBefore = getBalance(u2); - const uint64 playersBefore = ctl.state()->getPlayerCounter(); - const RL::BuyTicket_output outOk = ctl.buyTicket(u2, newPrice); - EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + 1); - EXPECT_EQ(getBalance(u2), balBefore - newPrice); - } -} - -TEST(ContractRandomLottery, BuyMultipleTickets_ExactMultiple_NoRemainder) -{ - ContractTestingRL ctl; - ctl.beginEpochWithValidTime(); - const uint64 price = ctl.state()->getTicketPrice(); - const id user = id::randomValue(); - constexpr uint64 k = 7; - increaseEnergy(user, price * k); - const uint64 playersBefore = ctl.state()->getPlayerCounter(); - const RL::BuyTicket_output out = ctl.buyTicket(user, price * k); - EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + k); -} - -TEST(ContractRandomLottery, BuyMultipleTickets_WithRemainder_Refunded) -{ - ContractTestingRL ctl; - ctl.beginEpochWithValidTime(); - const uint64 price = ctl.state()->getTicketPrice(); - const id user = id::randomValue(); - constexpr uint64 k = 5; - const uint64 r = price / 3; // partial remainder - increaseEnergy(user, price * k + r); - const uint64 balBefore = getBalance(user); - const uint64 playersBefore = ctl.state()->getPlayerCounter(); - const RL::BuyTicket_output out = ctl.buyTicket(user, price * k + r); - EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + k); - // Remainder refunded, only k * price spent - EXPECT_EQ(getBalance(user), balBefore - k * price); -} - -TEST(ContractRandomLottery, BuyMultipleTickets_CapacityPartialRefund) -{ - ContractTestingRL ctl; - ctl.beginEpochWithValidTime(); - const uint64 price = ctl.state()->getTicketPrice(); - const uint64 capacity = ctl.getPlayers().players.capacity(); - - // Fill almost up to capacity - const uint64 toFill = (capacity > 5) ? (capacity - 5) : 0; - for (uint64 i = 0; i < toFill; ++i) - { - const id u = id::randomValue(); - increaseEnergy(u, price); - EXPECT_EQ(ctl.buyTicket(u, price).returnCode, RL::EReturnCode::SUCCESS); - } - EXPECT_EQ(ctl.state()->getPlayerCounter(), toFill); - - // Try to buy 10 tickets — only remaining 5 accepted, the rest refunded - const id buyer = id::randomValue(); - increaseEnergy(buyer, price * 10); - const uint64 balBefore = getBalance(buyer); - const RL::BuyTicket_output out = ctl.buyTicket(buyer, price * 10); - EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.state()->getPlayerCounter(), capacity); - EXPECT_EQ(getBalance(buyer), balBefore - price * 5); -} - -TEST(ContractRandomLottery, BuyMultipleTickets_AllSoldOut) -{ - ContractTestingRL ctl; - ctl.beginEpochWithValidTime(); - const uint64 price = ctl.state()->getTicketPrice(); - const uint64 capacity = ctl.getPlayers().players.capacity(); - - // Fill to capacity - for (uint64 i = 0; i < capacity; ++i) - { - const id u = id::randomValue(); - increaseEnergy(u, price); - EXPECT_EQ(ctl.buyTicket(u, price).returnCode, RL::EReturnCode::SUCCESS); - } - EXPECT_EQ(ctl.state()->getPlayerCounter(), capacity); - - // Any purchase refunds the full amount and returns ALL_SOLD_OUT code - const id buyer = id::randomValue(); - increaseEnergy(buyer, price * 3); - const uint64 balBefore = getBalance(buyer); - const RL::BuyTicket_output out = ctl.buyTicket(buyer, price * 3); - EXPECT_EQ(out.returnCode, RL::EReturnCode::TICKET_ALL_SOLD_OUT); - EXPECT_EQ(getBalance(buyer), balBefore); -} - -// functions related to schedule and draw hour - -TEST(ContractRandomLottery, GetSchedule_And_SetSchedule) -{ - ContractTestingRL ctl; - - // Default schedule set on initialize must include Wednesday (bit 0) - const RL::GetSchedule_output s0 = ctl.getSchedule(); - EXPECT_NE(s0.schedule, 0u); - - // Access control: random user cannot set schedule - const id rnd = id::randomValue(); - increaseEnergy(rnd, 1); - const RL::SetSchedule_output outDenied = ctl.setSchedule(rnd, RL_ANY_DAY_DRAW_SCHEDULE); - EXPECT_EQ(outDenied.returnCode, RL::EReturnCode::ACCESS_DENIED); - - // Invalid value: zero mask not allowed - increaseEnergy(ctl.state()->team(), 1); - const RL::SetSchedule_output outInvalid = ctl.setSchedule(ctl.state()->team(), 0); - EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::INVALID_VALUE); - - // Valid update queues into NextEpochData and applies after END_EPOCH - const uint8 newMask = 0x5A; // some non-zero mask (bits set for selected days) - const RL::SetSchedule_output outOk = ctl.setSchedule(ctl.state()->team(), newMask); - EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.schedule, newMask); - - // Not applied yet - EXPECT_NE(ctl.getSchedule().schedule, newMask); - - // Apply - ctl.EndEpoch(); - EXPECT_EQ(ctl.getSchedule().schedule, newMask); - EXPECT_EQ(ctl.getNextEpochData().nextEpochData.schedule, 0u); -} - -TEST(ContractRandomLottery, GetDrawHour_DefaultAfterBeginEpoch) -{ - ContractTestingRL ctl; - - // Initially drawHour is 0 (not configured) - EXPECT_EQ(ctl.getDrawHour().drawHour, 0u); - - // After BeginEpoch default is 11 UTC - ctl.beginEpochWithValidTime(); - EXPECT_EQ(ctl.getDrawHour().drawHour, RL_DEFAULT_DRAW_HOUR); -} - - diff --git a/test/contract_testex.cpp b/test/contract_testex.cpp deleted file mode 100644 index 104e2b3f7..000000000 --- a/test/contract_testex.cpp +++ /dev/null @@ -1,2173 +0,0 @@ -#define NO_UEFI - -#include -#include - -#include "contract_testing.h" -#include "oracle_testing.h" - -static const id TESTEXA_CONTRACT_ID(TESTEXA_CONTRACT_INDEX, 0, 0, 0); -static const id TESTEXB_CONTRACT_ID(TESTEXB_CONTRACT_INDEX, 0, 0, 0); -static const id TESTEXC_CONTRACT_ID(TESTEXC_CONTRACT_INDEX, 0, 0, 0); -static const id TESTEXD_CONTRACT_ID(TESTEXD_CONTRACT_INDEX, 0, 0, 0); -static const id USER1(123, 456, 789, 876); -static const id USER2(42, 424, 4242, 42424); -static const id USER3(98, 76, 54, 3210); -static const id USER4(9878, 7645, 541, 3210); - -void checkPreManagementRightsTransferInput(const PreManagementRightsTransfer_input& observed, const PreManagementRightsTransfer_input& expected) -{ - EXPECT_EQ(observed.asset.assetName, expected.asset.assetName); - EXPECT_EQ(observed.asset.issuer, expected.asset.issuer); - EXPECT_EQ(observed.numberOfShares, expected.numberOfShares); - EXPECT_EQ(observed.offeredFee, expected.offeredFee); - EXPECT_EQ((int)observed.otherContractIndex, (int)expected.otherContractIndex); - EXPECT_EQ(observed.owner, expected.owner); - EXPECT_EQ(observed.possessor, expected.possessor); -} - -void checkPostManagementRightsTransferInput(const PostManagementRightsTransfer_input& observed, const PostManagementRightsTransfer_input& expected) -{ - EXPECT_EQ(observed.asset.assetName, expected.asset.assetName); - EXPECT_EQ(observed.asset.issuer, expected.asset.issuer); - EXPECT_EQ(observed.numberOfShares, expected.numberOfShares); - EXPECT_EQ(observed.receivedFee, expected.receivedFee); - EXPECT_EQ((int)observed.otherContractIndex, (int)expected.otherContractIndex); - EXPECT_EQ(observed.owner, expected.owner); - EXPECT_EQ(observed.possessor, expected.possessor); -} - -class StateCheckerTestExampleA : public TESTEXA -{ -public: - void checkPostReleaseCounter(uint32 expectedCount) - { - EXPECT_EQ(this->postReleaseSharesCounter, expectedCount); - } - - const PreManagementRightsTransfer_input& getPreReleaseInput() const - { - return this->prevPreReleaseSharesInput; - } - - const PostManagementRightsTransfer_input& getPostReleaseInput() const - { - return this->prevPostReleaseSharesInput; - } - - void checkPostAcquireCounter(uint32 expectedCount) - { - EXPECT_EQ(this->postAcquireShareCounter, expectedCount); - } - - const PreManagementRightsTransfer_input& getPreAcquireInput() const - { - return this->prevPreAcquireSharesInput; - } - - const PostManagementRightsTransfer_input& getPostAcquireInput() const - { - return this->prevPostAcquireSharesInput; - } - - void checkVariablesSetByProposal( - uint64 expectedVariable1, - uint32 expectedVariable2, - sint8 expectedVariable3) const - { - EXPECT_EQ(this->dummyStateVariable1, expectedVariable1); - EXPECT_EQ(this->dummyStateVariable2, expectedVariable2); - EXPECT_EQ(this->dummyStateVariable3, expectedVariable3); - } -}; - -class StateCheckerTestExampleB : public TESTEXB -{ -public: - void checkPostReleaseCounter(uint32 expectedCount) - { - EXPECT_EQ(this->postReleaseSharesCounter, expectedCount); - } - - const PreManagementRightsTransfer_input& getPreReleaseInput() const - { - return this->prevPreReleaseSharesInput; - } - - const PostManagementRightsTransfer_input& getPostReleaseInput() const - { - return this->prevPostReleaseSharesInput; - } - - void checkPostAcquireCounter(uint32 expectedCount) - { - EXPECT_EQ(this->postAcquireShareCounter, expectedCount); - } - - const PreManagementRightsTransfer_input& getPreAcquireInput() const - { - return this->prevPreAcquireSharesInput; - } - - const PostManagementRightsTransfer_input& getPostAcquireInput() const - { - return this->prevPostAcquireSharesInput; - } - - void checkVariablesSetByProposal( - sint64 expectedVariable1, - sint64 expectedVariable2, - sint64 expectedVariable3) const - { - EXPECT_EQ(this->fee1, expectedVariable1); - EXPECT_EQ(this->fee2, expectedVariable2); - EXPECT_EQ(this->fee3, expectedVariable3); - } -}; - -class ContractTestingTestEx : protected ContractTesting -{ -public: - QX::Fees_output qxFees; - - ContractTestingTestEx() - { - initEmptySpectrum(); - initEmptyUniverse(); - INIT_CONTRACT(TESTEXA); - callSystemProcedure(TESTEXA_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(TESTEXB); - callSystemProcedure(TESTEXB_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(TESTEXC); - callSystemProcedure(TESTEXC_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(TESTEXD); - callSystemProcedure(TESTEXD_CONTRACT_INDEX, INITIALIZE); - INIT_CONTRACT(QX); - callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); - - EXPECT_TRUE(oracleEngine.init(computorPublicKeys)); - EXPECT_TRUE(OI::initOracleInterfaces()); - - checkContractExecCleanup(); - - // query QX fees - callFunction(QX_CONTRACT_INDEX, 1, QX::Fees_input(), qxFees); - } - - ~ContractTestingTestEx() - { - oracleEngine.deinit(); - checkContractExecCleanup(); - } - - StateCheckerTestExampleA* getStateTestExampleA() - { - return (StateCheckerTestExampleA*)contractStates[TESTEXA_CONTRACT_INDEX]; - } - - StateCheckerTestExampleB* getStateTestExampleB() - { - return (StateCheckerTestExampleB*)contractStates[TESTEXB_CONTRACT_INDEX]; - } - - sint64 issueAssetQx(const Asset& asset, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) - { - QX::IssueAsset_input input{ asset.assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; - QX::IssueAsset_output output; - invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, asset.issuer, qxFees.assetIssuanceFee); - return output.issuedNumberOfShares; - } - - sint64 issueAssetTestExA(const Asset& asset, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) - { - TESTEXA::IssueAsset_input input{ asset.assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; - TESTEXA::IssueAsset_output output; - invokeUserProcedure(TESTEXA_CONTRACT_INDEX, 1, input, output, asset.issuer, 0); - return output.issuedNumberOfShares; - } - - sint64 transferShareOwnershipAndPossessionQx(const Asset& asset, const id& currentOwnerAndPossessor, const id& newOwnerAndPossessor, sint64 numberOfShares) - { - QX::TransferShareOwnershipAndPossession_input input; - QX::TransferShareOwnershipAndPossession_output output; - - input.assetName = asset.assetName; - input.issuer = asset.issuer; - input.newOwnerAndPossessor = newOwnerAndPossessor; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, currentOwnerAndPossessor, qxFees.transferFee); - - return output.transferredNumberOfShares; - } - - template - sint64 transferShareOwnershipAndPossession(const Asset& asset, const id& currentOwnerAndPossessor, const id& newOwnerAndPossessor, sint64 numberOfShares) - { - typename StateStruct::TransferShareOwnershipAndPossession_input input; - typename StateStruct::TransferShareOwnershipAndPossession_output output; - - input.asset = asset; - input.newOwnerAndPossessor = newOwnerAndPossessor; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(StateStruct::__contract_index, 2, input, output, currentOwnerAndPossessor, 0); - - return output.transferredNumberOfShares; - } - - sint64 transferShareManagementRightsQx(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, unsigned int newManagingContractIndex, sint64 fee = 0) - { - QX::TransferShareManagementRights_input input; - QX::TransferShareManagementRights_output output; - - input.asset = asset; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, currentOwnerAndPossessor, fee); - - return output.transferredNumberOfShares; - } - - template - sint64 transferShareManagementRights(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, unsigned int newManagingContractIndex, sint64 fee = 0) - { - typename StateStruct::TransferShareManagementRights_input input; - typename StateStruct::TransferShareManagementRights_output output; - - input.asset = asset; - input.newManagingContractIndex = newManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(StateStruct::__contract_index, 3, input, output, currentOwnerAndPossessor, fee); - - return output.transferredNumberOfShares; - } - - template - void setPreReleaseSharesOutput(bool allowTransfer, sint64 requestedFee) - { - typename StateStruct::SetPreReleaseSharesOutput_input input{ allowTransfer, requestedFee }; - typename StateStruct::SetPreReleaseSharesOutput_output output; - invokeUserProcedure(StateStruct::__contract_index, 4, input, output, USER1, 0); - } - - template - void setPreAcquireSharesOutput(bool allowTransfer, sint64 requestedFee) - { - typename StateStruct::SetPreReleaseSharesOutput_input input{ allowTransfer, requestedFee }; - typename StateStruct::SetPreReleaseSharesOutput_output output; - invokeUserProcedure(StateStruct::__contract_index, 5, input, output, USER1, 0); - } - - - template - sint64 acquireShareManagementRights(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, unsigned int prevManagingContractIndex, sint64 fee = 0, const id& originator = NULL_ID) - { - typename StateStruct::AcquireShareManagementRights_input input; - typename StateStruct::AcquireShareManagementRights_output output; - - input.asset = asset; - input.ownerAndPossessor = currentOwnerAndPossessor; - input.oldManagingContractIndex = prevManagingContractIndex; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(StateStruct::__contract_index, 6, input, output, - (isZero(originator)) ? currentOwnerAndPossessor : originator, fee); - - return output.transferredNumberOfShares; - } - - sint64 getTestExAsShareManagementRightsByInvokingTestExB(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, sint64 fee = 0) - { - TESTEXB::GetTestExampleAShareManagementRights_input input; - TESTEXB::GetTestExampleAShareManagementRights_output output; - - input.asset = asset; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(TESTEXB::__contract_index, 7, input, output, currentOwnerAndPossessor, fee); - - return output.transferredNumberOfShares; - } - - sint64 getTestExAsShareManagementRightsByInvokingTestExC(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, sint64 fee = 0) - { - TESTEXC::GetTestExampleAShareManagementRights_input input; - TESTEXC::GetTestExampleAShareManagementRights_output output; - - input.asset = asset; - input.numberOfShares = numberOfShares; - - invokeUserProcedure(TESTEXC::__contract_index, 7, input, output, currentOwnerAndPossessor, fee); - - return output.transferredNumberOfShares; - } - - TESTEXA::QueryQpiFunctions_output queryQpiFunctions(const TESTEXA::QueryQpiFunctions_input& input) - { - TESTEXA::QueryQpiFunctions_output output; - callFunction(TESTEXA_CONTRACT_INDEX, 1, input, output); - return output; - } - - unsigned int callFunctionOfTestExampleAFromTextExampleB(const TESTEXA::QueryQpiFunctions_input& input, TESTEXA::QueryQpiFunctions_output& output, bool expectSuccess) - { - return callFunction(TESTEXB_CONTRACT_INDEX, 1, input, output, true, expectSuccess); - } - - unsigned int callErrorTriggerFunction() - { - TESTEXA::ErrorTriggerFunction_input input; - TESTEXA::ErrorTriggerFunction_output output; - return callFunction(TESTEXA_CONTRACT_INDEX, 5, input, output, true, false); - } - - template - typename StateStruct::IncomingTransferAmounts_output getIncomingTransferAmounts() - { - typename StateStruct::IncomingTransferAmounts_input input; - typename StateStruct::IncomingTransferAmounts_output output; - EXPECT_EQ(callFunction(StateStruct::__contract_index, 20, input, output), NoContractError); - return output; - } - - template - bool qpiTransfer(const id& destinationPublicKey, sint64 amount, sint64 fee = 0, const id& originator = USER1) - { - typename StateStruct::QpiTransfer_input input{ destinationPublicKey, amount }; - typename StateStruct::QpiTransfer_output output; - return invokeUserProcedure(StateStruct::__contract_index, 20, input, output, originator, fee); - } - - template - bool qpiDistributeDividends(sint64 amountPerShare, sint64 fee = 0, const id& originator = USER1) - { - typename StateStruct::QpiDistributeDividends_input input{ amountPerShare }; - typename StateStruct::QpiDistributeDividends_output output; - return invokeUserProcedure(StateStruct::__contract_index, 21, input, output, originator, fee); - } - - template - typename StateStruct::GetIpoBid_output getIpoBid(unsigned int ipoContractIndex, unsigned int bidIndex) - { - typename StateStruct::GetIpoBid_input input{ ipoContractIndex, bidIndex }; - typename StateStruct::GetIpoBid_output output; - EXPECT_EQ(callFunction(StateStruct::__contract_index, 30, input, output), NoContractError); - return output; - } - - template - long long qpiBidInIpo(unsigned int ipoContractIndex, long long pricePerShare, unsigned short numberOfShares, sint64 fee = 0, const id& originator = USER1) - { - typename StateStruct::QpiBidInIpo_input input{ ipoContractIndex, pricePerShare, numberOfShares }; - typename StateStruct::QpiBidInIpo_output output; - if (invokeUserProcedure(StateStruct::__contract_index, 30, input, output, originator, fee)) - return output; - else - return -2; - } - - template - uint16 setShareholderProposal(const id& originator, const typename StateStruct::SetShareholderProposal_input& input) - { - typename StateStruct::SetShareholderProposal_output output; - EXPECT_TRUE(invokeUserProcedure(StateStruct::__contract_index, 65534, input, output, originator, 0)); - return output; - } - - template - bool setShareholderVotes(const id& originator, uint16 proposalIndex, const typename StateStruct::ProposalDataT& proposalData, sint64 voteValue) - { - // Contract procedure expects ProposalMultiVoteDataV1, but ProposalSingleVoteDataV1 is compatible - ProposalSingleVoteDataV1 input{ proposalIndex, proposalData.type, proposalData.tick, voteValue }; - typename StateStruct::SetShareholderVotes_output output; - invokeUserProcedure(StateStruct::__contract_index, 65535, input, output, originator, 0, false); - return output; - } - - template - bool setShareholderVotes(const id& originator, uint16 proposalIndex, const typename StateStruct::ProposalDataT& proposalData, - const std::vector>& voteValueCountPairs) - { - ASSERT(voteValueCountPairs.size() <= 8); - ProposalMultiVoteDataV1 input{ proposalIndex, proposalData.type, proposalData.tick }; - input.voteValues.set(0, NO_VOTE_VALUE); // default with no voteValueCountPairs (vote count 0): set all to no votes - for (size_t i = 0; i < voteValueCountPairs.size(); ++i) - { - input.voteValues.set(i, voteValueCountPairs[i].first); - input.voteCounts.set(i, voteValueCountPairs[i].second); - } - typename StateStruct::SetShareholderVotes_output output; - invokeUserProcedure(StateStruct::__contract_index, 65535, input, output, originator, 0); - return output; - } - - TESTEXB::TestInterContractCallError_output testInterContractCallError() - { - TESTEXB::TestInterContractCallError_input input; - input.dummy = 0; - TESTEXB::TestInterContractCallError_output output; - invokeUserProcedure(TESTEXB_CONTRACT_INDEX, 50, input, output, USER1, 0); - return output; - } - - template - std::vector getShareholderProposalIndices(bit activeProposals) - { - typename StateStruct::GetShareholderProposalIndices_input input{ activeProposals, -1 }; - typename StateStruct::GetShareholderProposalIndices_output output; - std::vector indices; - do - { - callFunction(StateStruct::__contract_index, 65532, input, output); - for (uint16 i = 0; i < output.numOfIndices; ++i) - indices.push_back(output.indices.get(i)); - } while (output.numOfIndices == output.indices.capacity()); - return indices; - } - - template - StateStruct::GetShareholderProposal_output getShareholderProposal(uint16 proposalIndex) - { - typename StateStruct::GetShareholderProposal_input input{ proposalIndex }; - typename StateStruct::GetShareholderProposal_output output; - callFunction(StateStruct::__contract_index, 65533, input, output); - return output; - } - - template - ProposalMultiVoteDataV1 getShareholderVotes(uint16 proposalIndex, const id& voter) - { - typename StateStruct::GetShareholderVotes_input input{ voter, proposalIndex }; - typename StateStruct::GetShareholderVotes_output output; - callFunction(StateStruct::__contract_index, 65534, input, output); - return output; - } - - template - ProposalSummarizedVotingDataV1 getShareholderVotingResults(uint16 proposalIndex) - { - typename StateStruct::GetShareholderVotingResults_input input{ proposalIndex }; - typename StateStruct::GetShareholderVotingResults_output output; - callFunction(StateStruct::__contract_index, 65535, input, output); - return output; - } - - uint16 setupShareholderProposalTestExA( - const id& proposer, uint16 type, - bool setVar1 = false, uint64 valueVar1 = 0, - bool setVar2 = false, uint32 valueVar2 = 0, - bool setVar3 = false, sint8 valueVar3 = 0, - bool expectSuccess = true) - { - TESTEXA::SetShareholderProposal_input input; - setMemory(input, 0); - input.proposalData.epoch = system.epoch; - input.proposalData.type = type; - switch (ProposalTypes::cls(type)) - { - case ProposalTypes::Class::Variable: - { - if (setVar1) - { - EXPECT_FALSE(setVar2); - EXPECT_FALSE(setVar3); - input.proposalData.data.variableOptions.variable = 0; - input.proposalData.data.variableOptions.value = valueVar1; - } - else if (setVar2) - { - EXPECT_FALSE(setVar1); - EXPECT_FALSE(setVar3); - input.proposalData.data.variableOptions.variable = 1; - input.proposalData.data.variableOptions.value = valueVar2; - } - else if (setVar3) - { - EXPECT_FALSE(setVar1); - EXPECT_FALSE(setVar2); - input.proposalData.data.variableOptions.variable = 2; - input.proposalData.data.variableOptions.value = valueVar3; - } - break; - } - case ProposalTypes::Class::MultiVariables: - input.multiVarData.hasValueDummyStateVariable1 = setVar1; - input.multiVarData.hasValueDummyStateVariable2 = setVar2; - input.multiVarData.hasValueDummyStateVariable3 = setVar3; - input.multiVarData.optionYesValues.dummyStateVariable1 = valueVar1; - input.multiVarData.optionYesValues.dummyStateVariable2 = valueVar2; - input.multiVarData.optionYesValues.dummyStateVariable3 = valueVar3; - break; - } - uint16 proposalIdx = this->setShareholderProposal(proposer, input); - if (expectSuccess) - EXPECT_NE((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); - else - EXPECT_EQ((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); - return proposalIdx; - } - - template - uint16 setProposalInOtherContractAsShareholder(const id& originator, uint16 otherContractIndex, const FullProposalDataT& fullProposalData) - { - typename StateStruct::SetProposalInOtherContractAsShareholder_input input; - copyToBuffer(input, fullProposalData); - input.otherContractIndex = otherContractIndex; - typename StateStruct::SetProposalInOtherContractAsShareholder_output output; - invokeUserProcedure(StateStruct::__contract_index, 40, input, output, originator, 0); - return output.proposalIndex; - } - - template - bool setVotesInOtherContractAsShareholder(const id& originator, uint16 otherContractIndex, uint16 proposalIndex, const ProposalDataT& proposalData, - const std::vector>& voteValueCountPairs) - { - ASSERT(voteValueCountPairs.size() <= 8); - typename StateStruct::SetVotesInOtherContractAsShareholder_input input{ {proposalIndex, proposalData.type, proposalData.tick} }; - input.otherContractIndex = otherContractIndex; - input.voteData.voteValues.set(0, NO_VOTE_VALUE); // default with no voteValueCountPairs (vote count 0): set all to no votes - for (size_t i = 0; i < voteValueCountPairs.size(); ++i) - { - input.voteData.voteValues.set(i, voteValueCountPairs[i].first); - input.voteData.voteCounts.set(i, voteValueCountPairs[i].second); - } - typename StateStruct::SetVotesInOtherContractAsShareholder_output output; - invokeUserProcedure(StateStruct::__contract_index, 41, input, output, originator, 0); - return output.success; - } - - void beginEpoch(bool expectSuccess = true) - { - callSystemProcedure(TESTEXD_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - callSystemProcedure(TESTEXC_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - callSystemProcedure(TESTEXB_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - callSystemProcedure(TESTEXA_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - callSystemProcedure(QX_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); - } - - void endEpoch(bool expectSuccess = true) - { - callSystemProcedure(TESTEXD_CONTRACT_INDEX, END_EPOCH, expectSuccess); - callSystemProcedure(TESTEXC_CONTRACT_INDEX, END_EPOCH, expectSuccess); - callSystemProcedure(TESTEXB_CONTRACT_INDEX, END_EPOCH, expectSuccess); - callSystemProcedure(TESTEXA_CONTRACT_INDEX, END_EPOCH, expectSuccess); - callSystemProcedure(QX_CONTRACT_INDEX, END_EPOCH, expectSuccess); - } - - void endTick(bool expectSuccess = true) - { - callSystemProcedure(TESTEXD_CONTRACT_INDEX, END_TICK, expectSuccess); - callSystemProcedure(TESTEXC_CONTRACT_INDEX, END_TICK, expectSuccess); - callSystemProcedure(TESTEXB_CONTRACT_INDEX, END_TICK, expectSuccess); - callSystemProcedure(TESTEXA_CONTRACT_INDEX, END_TICK, expectSuccess); - callSystemProcedure(QX_CONTRACT_INDEX, END_TICK, expectSuccess); - } - - uint64 queryPriceOracle(const id& invocator, uint32 timeoutMilliseconds, const OI::Price::OracleQuery& query) - { - TESTEXC::QueryPriceOracle_input input; - input.priceOracleQuery = query; - input.timeoutMilliseconds = timeoutMilliseconds; - TESTEXC::QueryPriceOracle_output output; - EXPECT_TRUE(invokeUserProcedure(TESTEXC_CONTRACT_INDEX, 100, input, output, invocator, 0)); - return output.oracleQueryId; - } -}; - -void checkVoteCounts(const ProposalMultiVoteDataV1& votes, const std::vector>& expectedVoteValueCountPairs) -{ - std::vector> expectedPairsNotFound = expectedVoteValueCountPairs; - for (int i = 0; i < votes.voteCounts.capacity(); ++i) - { - sint64 value = votes.voteValues.get(i); - uint32 count = votes.voteCounts.get(i); - std::pair pair(value, count); - auto it = std::find(expectedPairsNotFound.begin(), expectedPairsNotFound.end(), pair); - if (it != expectedPairsNotFound.end()) - { - // value-count pair found - expectedPairsNotFound.erase(it); - } - else if (count) - { - FAIL() << "Error: unexpected vote value/count pair " << value << "/" << count; - } - } - for (const auto& it : expectedPairsNotFound) - { - FAIL() << "Error: missing vote value/count pair " << it.first << "/" << it.second; - } -} - -bool operator==(const TESTEXA::MultiVariablesProposalExtraData& p1, const TESTEXA::MultiVariablesProposalExtraData& p2) -{ - return memcmp(&p1, &p2, sizeof(p1)) == 0; -} - - -TEST(ContractTestEx, QpiReleaseShares) -{ - ContractTestingTestEx test; - - const Asset asset1{ USER1, assetNameFromString("BLOB") }; - const sint64 totalShareCount = 1000000000; - const sint64 transferShareCount = totalShareCount/4; - - // make sure the entities have enough qu - increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(USER2, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(USER3, test.qxFees.assetIssuanceFee * 10); - - // issueAsset with QX - EXPECT_EQ(test.issueAssetQx(asset1, totalShareCount, 0, 0), totalShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount); - - // run ownership/possession transfer in QX -> should work (has management rights from issueAsset) - EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, asset1.issuer, USER2, transferShareCount), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); - - // run ownership/possession transfer in TESTEXA -> should fail (requires management rights) - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, USER2, USER3, transferShareCount), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - - //////////////////////////////////// - // RELEASE FROM QX TO TESTEXA - - // invoke release of shares in QX to TESTEXA -> fails because default response of TESTEXA is to reject - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostAcquireCounter(0); - - // enable that TESTEXA accepts transfer for 0 fee - test.setPreAcquireSharesOutput(true, 0); - - // invoke release of shares in QX to TESTEXA -> succeed - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostAcquireCounter(1); - checkPreManagementRightsTransferInput( - test.getStateTestExampleA()->getPreAcquireInput(), - { asset1, USER2, USER2, transferShareCount, 0, QX_CONTRACT_INDEX }); - checkPostManagementRightsTransferInput( - test.getStateTestExampleA()->getPostAcquireInput(), - { asset1, USER2, USER2, transferShareCount, 0, QX_CONTRACT_INDEX }); - - // run ownership/possession transfer in TESTEXA -> should work - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, USER2, USER3, transferShareCount), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), transferShareCount); - - // release shares error case: too few shares -> fail - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, 0, TESTEXA_CONTRACT_INDEX, 100), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostAcquireCounter(1); - - // release shares error case: more shares than available -> fail - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, totalShareCount + 1, TESTEXA_CONTRACT_INDEX, 100), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostAcquireCounter(1); - - // release shares error case: fee requested by TESTEXA is too high to release shares (QX expects 0) -> fail - test.setPreAcquireSharesOutput(true, 1000); - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostAcquireCounter(1); - - // release shares error case: fee requested by TESTEXA is invalid - test.setPreAcquireSharesOutput(true, -100); - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); - test.setPreAcquireSharesOutput(true, MAX_AMOUNT + 1000); - EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostAcquireCounter(1); - - //////////////////////////////////// - // RELEASE FROM TESTEXA TO TESTEXB - - // invoke release of shares in TESTEXA to TESTEXB with fee -> succeed - test.getStateTestExampleB()->checkPostAcquireCounter(0); - sint64 balanceUser3 = getBalance(USER3); - sint64 balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); - sint64 balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); - test.setPreAcquireSharesOutput(true, 1000); - EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, transferShareCount, TESTEXB_CONTRACT_INDEX, 1500), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); - test.getStateTestExampleB()->checkPostAcquireCounter(1); - checkPreManagementRightsTransferInput( - test.getStateTestExampleB()->getPreAcquireInput(), - { asset1, USER3, USER3, transferShareCount, 1500, TESTEXA_CONTRACT_INDEX }); - checkPostManagementRightsTransferInput( - test.getStateTestExampleB()->getPostAcquireInput(), - { asset1, USER3, USER3, transferShareCount, 1000, TESTEXA_CONTRACT_INDEX }); - EXPECT_EQ(getBalance(USER3), balanceUser3 - 1500); - EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 1500 - 1000); - EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 1000); - - //////////////////////////////////// - // RELEASE FROM TESTEXB TO QX - - // run ownership/possession transfer in QX -> should fail (requires management rights) - EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, USER3, USER2, transferShareCount), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); - - // invoke release of shares from TESTEXB to QX with 0 qu -> fails because transfer fee is required by QX - EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, transferShareCount, QX_CONTRACT_INDEX, 0), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); - - // invoke release of shares from TESTEXB to QX with sufficient amount but too many shares -> should fail - EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, totalShareCount, QX_CONTRACT_INDEX, test.qxFees.transferFee), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); - - // invoke release of shares from TESTEXB to QX with sufficient amount and correct shares -> should work - EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, transferShareCount, QX_CONTRACT_INDEX, test.qxFees.transferFee), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), 0); - - // run ownership/possession transfer in QX -> should work again - EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, USER3, USER2, transferShareCount), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); -} - -TEST(ContractTestEx, QpiAcquireShares) -{ - ContractTestingTestEx test; - - const Asset asset1{ USER1, assetNameFromString("BLURB") }; - const sint64 totalShareCount = 100000000; - const sint64 transferShareCount = totalShareCount / 4; - - // make sure the entities have enough qu - increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(USER2, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(USER3, test.qxFees.assetIssuanceFee * 10); - - // issueAsset with TestExampleA - EXPECT_EQ(test.issueAssetTestExA(asset1, totalShareCount, 0, 0), totalShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount); - - // run ownership/possession transfer in TestExampleA -> should work (has management rights from issueAsset) - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, USER2, transferShareCount), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - - // run ownership/possession transfer in QX -> should fail (requires management rights) - EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, USER2, USER3, transferShareCount), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); - - - ////////////////////////////////////////////// - // TESTEXB ACQUIRES FROM TESTEXA - - // TestExampleB tries / fails to acquire management rights (negative shares count) - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, -100, TESTEXA_CONTRACT_INDEX, 0), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (more shares than available) - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount + 1, TESTEXA_CONTRACT_INDEX, 0), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (negative offered fee) - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, -100), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (rejected by TESTEXA) - test.setPreReleaseSharesOutput(false, 0); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 4999), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (requested fee is negative) - test.setPreReleaseSharesOutput(true, -5000); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 5000), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (offered fee lower than requested fee) - test.setPreReleaseSharesOutput(true, 5000); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 4999), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (not enough QU owned to pay fee) - test.setPreReleaseSharesOutput(true, test.qxFees.assetIssuanceFee * 11); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, test.qxFees.assetIssuanceFee * 11), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB tries / fails to acquire management rights (with wrong originator USER3) - test.setPreReleaseSharesOutput(true, 1234); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 1234, USER3), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); - test.getStateTestExampleA()->checkPostReleaseCounter(0); - - // TestExampleB acquires management rights (success) - test.setPreReleaseSharesOutput(true, 1234); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount / 4, TESTEXA_CONTRACT_INDEX, 1239), transferShareCount / 4); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount * 3 / 4); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount / 4); - test.getStateTestExampleA()->checkPostReleaseCounter(1); - checkPreManagementRightsTransferInput( - test.getStateTestExampleA()->getPreReleaseInput(), - { asset1, USER2, USER2, transferShareCount / 4, 1239, TESTEXB_CONTRACT_INDEX }); - checkPostManagementRightsTransferInput( - test.getStateTestExampleA()->getPostReleaseInput(), - { asset1, USER2, USER2, transferShareCount / 4, 1234, TESTEXB_CONTRACT_INDEX }); - - // TestExampleB acquires management rights (success) - test.setPreReleaseSharesOutput(true, 10); - sint64 balanceUser2 = getBalance(USER2); - sint64 balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); - sint64 balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount * 3 / 4, TESTEXA_CONTRACT_INDEX, 15), transferShareCount * 3 / 4); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount); - test.getStateTestExampleA()->checkPostReleaseCounter(2); - checkPreManagementRightsTransferInput( - test.getStateTestExampleA()->getPreReleaseInput(), - { asset1, USER2, USER2, transferShareCount * 3 / 4, 15, TESTEXB_CONTRACT_INDEX }); - checkPostManagementRightsTransferInput( - test.getStateTestExampleA()->getPostReleaseInput(), - { asset1, USER2, USER2, transferShareCount * 3 / 4, 10, TESTEXB_CONTRACT_INDEX }); - EXPECT_EQ(getBalance(USER2), balanceUser2 - 15); - EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 10); - EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 15 - 10); - - // run ownership/possession transfer in TestExampleB -> should work now (after acquiring management rights) - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, USER2, USER3, transferShareCount / 2), transferShareCount / 2); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount / 2); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount / 2); - - ////////////////////////////////////////////// - // TESTEXA ACQUIRES FROM TESTEXB - - // TestExampleA acquires management rights from TestExampleB of shares owned by USER2 (success) - test.getStateTestExampleB()->checkPostReleaseCounter(0); - test.setPreReleaseSharesOutput(true, 13); - balanceUser2 = getBalance(USER2); - balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); - balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount / 10, TESTEXB_CONTRACT_INDEX, 42), transferShareCount / 10); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount / 10); - EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount * 4 / 10); - test.getStateTestExampleB()->checkPostReleaseCounter(1); - checkPreManagementRightsTransferInput( - test.getStateTestExampleB()->getPreReleaseInput(), - { asset1, USER2, USER2, transferShareCount / 10, 42, TESTEXA_CONTRACT_INDEX }); - checkPostManagementRightsTransferInput( - test.getStateTestExampleB()->getPostReleaseInput(), - { asset1, USER2, USER2, transferShareCount / 10, 13, TESTEXA_CONTRACT_INDEX }); - EXPECT_EQ(getBalance(USER2), balanceUser2 - 42); - EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 42 - 13); - EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 13); - - // TestExampleA acquires management rights from TestExampleB of shares owned by USER3 (success) - test.setPreReleaseSharesOutput(true, 123); - sint64 balanceUser3 = getBalance(USER3); - balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); - balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); - EXPECT_EQ(test.acquireShareManagementRights(asset1, USER3, transferShareCount * 2 / 10, TESTEXB_CONTRACT_INDEX, 124), transferShareCount * 2 / 10); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), transferShareCount * 2 / 10); - EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount * 3 / 10); - test.getStateTestExampleB()->checkPostReleaseCounter(2); - checkPreManagementRightsTransferInput( - test.getStateTestExampleB()->getPreReleaseInput(), - { asset1, USER3, USER3, transferShareCount * 2 / 10, 124, TESTEXA_CONTRACT_INDEX }); - checkPostManagementRightsTransferInput( - test.getStateTestExampleB()->getPostReleaseInput(), - { asset1, USER3, USER3, transferShareCount * 2 / 10, 123, TESTEXA_CONTRACT_INDEX }); - EXPECT_EQ(getBalance(USER3), balanceUser3 - 124); - EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 124 - 123); - EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 123); - - // Some count final checks - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byManagingContract(QX_CONTRACT_INDEX)), 0); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byManagingContract(TESTEXA_CONTRACT_INDEX)), totalShareCount - transferShareCount * 7 / 10); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byManagingContract(TESTEXB_CONTRACT_INDEX)), transferShareCount * 7 / 10); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byOwner(USER1)), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byOwner(USER2)), transferShareCount / 2); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byOwner(USER3)), transferShareCount / 2); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(QX_CONTRACT_INDEX)), 0); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(TESTEXA_CONTRACT_INDEX)), totalShareCount - transferShareCount * 7 / 10); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(TESTEXB_CONTRACT_INDEX)), transferShareCount * 7 / 10); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(USER1)), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(USER2)), transferShareCount / 2); - EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(USER3)), transferShareCount / 2); - EXPECT_EQ(numberOfShares(asset1), totalShareCount); -} - -TEST(ContractTestEx, GetManagementRightsByInvokingOtherContractsRelease) -{ - ContractTestingTestEx test; - - const Asset asset1{ USER1, assetNameFromString("BLURB") }; - const sint64 totalShareCount = 1000000; - const sint64 transferShareCount = totalShareCount / 5; - - // make sure the entities have enough qu - increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(USER2, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(USER3, test.qxFees.assetIssuanceFee * 10); - - // issueAsset with TestExampleA - EXPECT_EQ(test.issueAssetTestExA(asset1, totalShareCount, 0, 0), totalShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount); - - /////////////////////////////////////////////////////////////////////////// - // TESTEXB ACQUIRES FROM TESTEXA BY INVOKING TESTEXA PROCEDURE (QX PATTERN) - - // run ownership/possession transfer to TestExB in TestExampleA -> should work (has management rights from issueAsset) - // (TestExB needs to own/possess shares in order to invoke TestExA for transferring rights to TestExB in the next step) - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, TESTEXB_CONTRACT_ID, transferShareCount), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); - - // Transfer rights to TestExB using the QX approach - // -> Test that we don't get a deadlock in the following case: - // invoke procedure of TestExB, which invokes procedure of TestExA for calling qpi.releaseShares(), which runs - // callback PRE_ACQUIRE_SHARES of TestExB - // Attempt 1: fail due to forbidding by default - EXPECT_EQ(test.getTestExAsShareManagementRightsByInvokingTestExB(asset1, USER1, transferShareCount, 0), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); - - // Attempt 2: allow -> fail because requested fee > offered fee - test.setPreAcquireSharesOutput(true, 13); - EXPECT_EQ(test.getTestExAsShareManagementRightsByInvokingTestExB(asset1, USER1, transferShareCount, 0), 0); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); - - // Attempt 2: allow -> success - EXPECT_EQ(test.getTestExAsShareManagementRightsByInvokingTestExB(asset1, USER1, transferShareCount, 15), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXB_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXB_CONTRACT_INDEX }), transferShareCount); -} - -// Test stopping + cleanup of contract functions execution for recursive function leading to stack overflow -TEST(ContractTestEx, AbortFunction) -{ - ContractTestingTestEx test; - - // Successfully run function - TESTEXA::QueryQpiFunctions_input input{}; - TESTEXA::QueryQpiFunctions_output output{}; - EXPECT_EQ(test.callFunctionOfTestExampleAFromTextExampleB(input, output, true), NoContractError); - - // Check that error handling works when error is supposed to happen - EXPECT_EQ(test.callErrorTriggerFunction(), ContractErrorAllocLocalsFailed); -} - -static id getUser(unsigned long long i) -{ - return id(i, i / 2 + 4, i + 10, i * 3 + 8); -} - -static void concurrentContractFunctionCall(ContractTestingTestEx* test) -{ - // This calls a user function in contract TestExampleA that calls a function in TestExampleB. - // When running concurrently with a management rights transfer, this may trigger a deadlock - // that needs to be resolved. - for (int i = 0; i < 3; ++i) - { - TESTEXA::QueryQpiFunctions_input queryQpiFuncInput; - TESTEXA::QueryQpiFunctions_output qpiReturned; - setMemory(utcTime, 0); - utcTime.Year = 2022; - utcTime.Month = 4; - utcTime.Day = 13; - utcTime.Hour = 12; - updateQpiTime(); - bool expectSuccess = false; - unsigned int errorCode = test->callFunctionOfTestExampleAFromTextExampleB(queryQpiFuncInput, qpiReturned, expectSuccess); - ASSERT(errorCode == ContractErrorStoppedToResolveDeadlock || errorCode == NoContractError); - if (errorCode == NoContractError) - { - EXPECT_EQ(qpiReturned.qpiFunctionsOutput.year, 22); - EXPECT_EQ(qpiReturned.qpiFunctionsOutput.month, 4); - EXPECT_EQ(qpiReturned.qpiFunctionsOutput.day, 13); - EXPECT_EQ(qpiReturned.qpiFunctionsOutput.hour, 12); - } - } -} - -TEST(ContractTestEx, ResolveDeadlockCallbackProcedureAndConcurrentFunction) -{ - // deadlock pattern (with index of TestExC > TestExB > TestExA): - // 1. TestExC procedure invokes TestExA procedure, which runs qpi.releaseShares() to TestExC leading to a call of - // PRE_ACQUIRE_SHARES in TestExC - // -> solution to resolve deadlock: reusing lock - // 2. PRE_ACQUIRE_SHARES in TestExC invokes a TestExA procedure (this runs a lot of computation to wait for - // concurrent execution of contract function needed for test 3) - // -> solution to resolve deadlock: reusing lock - // 3. PRE_ACQUIRE_SHARES in TestExC tries to invoke a procedure of TestExB; this leads to a deadlock if a contract - // function of TextExB is running in parallel that tries to run a function of TextExA: - // -> contract function of TestExB running in request processor is waiting for a read lock of TestExA - // -> read lock of TestExA cannot be acquired before qpi.releaseShares() returns, which it doesn't because - // PRE_ACQUIRE_SHARES is waiting for a write lock of TestExB, which cannot be acquired before the contract - // function of TestExB is finished - // -> solution to resolve deadlock: cancel contract function of TestExB - - ContractTestingTestEx test; - - const Asset asset1{ USER1, assetNameFromString("WOBBL") }; - const sint64 totalShareCount = 100000000; - const int numberOfUsers = 500; - const sint64 transferShareCount = totalShareCount / numberOfUsers / 10; - - // populate spectrum - increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); - increaseEnergy(TESTEXC_CONTRACT_ID, test.qxFees.assetIssuanceFee * 10); - for (int i = 0; i < numberOfUsers; ++i) - increaseEnergy(getUser(i), test.qxFees.assetIssuanceFee * (i % 1000 + 1)); - - // issueAsset with TestExampleA - EXPECT_EQ(test.issueAssetTestExA(asset1, totalShareCount, 0, 0), totalShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount); - - // run ownership/possession transfer to TestExC in TestExA (needed to run deadlock test below) - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, TESTEXC_CONTRACT_ID, transferShareCount), transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); - EXPECT_EQ(numberOfShares(asset1, { TESTEXC_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXC_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); - - // populate universe - for (int i = 0; i < numberOfUsers; ++i) - EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, getUser(i), transferShareCount), transferShareCount); - - // start procedure call for deadlock test (baseline without concurrent function call) - { - std::cout << "Test callback procedure without concurrent function ..." << std::endl; - auto startTime = std::chrono::high_resolution_clock::now(); - test.getTestExAsShareManagementRightsByInvokingTestExC(asset1, TESTEXC_CONTRACT_ID, 1, 0); - auto endTime = std::chrono::high_resolution_clock::now(); - auto durationMilliSec = std::chrono::duration_cast(endTime - startTime).count(); - std::cout << "Run-time of procedure without concurrent function: " << durationMilliSec << " milliseconds\n" << std::endl; - } - - // start function call (baseline without concurrent procedure call) - { - std::cout << "Test function without concurrent procedure ..." << std::endl; - auto startTime = std::chrono::high_resolution_clock::now(); - TESTEXA::QueryQpiFunctions_input input; - TESTEXA::QueryQpiFunctions_output output; - test.callFunctionOfTestExampleAFromTextExampleB(input, output, true); - auto endTime = std::chrono::high_resolution_clock::now(); - auto durationMilliSec = std::chrono::duration_cast(endTime - startTime).count(); - std::cout << "Run-time of function without concurrent procedure: " << durationMilliSec << " milliseconds\n" << std::endl; - } - - // deadlock test with procedure and concurrent function call - for (int i = 0; i < 3; ++i) - { - std::cout << "Test callback procedure with concurrent function ..." << std::endl; - auto startTime = std::chrono::high_resolution_clock::now(); - - auto lambda = [](ContractTestingTestEx* test, const Asset& asset1) { test->getTestExAsShareManagementRightsByInvokingTestExC(asset1, TESTEXC_CONTRACT_ID, 1, 0); }; - - auto userProcedureThread = std::thread(lambda, &test, asset1); - auto userFunctionThread = std::thread(concurrentContractFunctionCall, &test); - - userProcedureThread.join(); - userFunctionThread.join(); - - auto endTime = std::chrono::high_resolution_clock::now(); - auto durationMilliSec = std::chrono::duration_cast(endTime - startTime).count(); - std::cout << "Run-time of procedure with concurrent function: " << durationMilliSec << " milliseconds\n" << std::endl; - } -} - -TEST(ContractTestEx, QueryBasicQpiFunctions) -{ - ContractTestingTestEx test; - - // some simple QPI functions tests that are independent of the tick - test.beginEpoch(); - - id arbitratorPubKey; - getPublicKeyFromIdentity((const unsigned char*)ARBITRATOR, arbitratorPubKey.m256i_u8); - - // prepare data for qpi.K12() and qpi.signatureValidity() - TESTEXA::QueryQpiFunctions_input queryQpiFuncInput1; - id subseed1(123456789, 987654321, 1357986420, 0xabcdef); - id privateKey1, digest1; - getPrivateKey(subseed1.m256i_u8, privateKey1.m256i_u8); - getPublicKey(privateKey1.m256i_u8, queryQpiFuncInput1.entity.m256i_u8); - for (uint64 i = 0; i < queryQpiFuncInput1.data.capacity(); ++i) - queryQpiFuncInput1.data.set(i, static_cast(i - 50)); - KangarooTwelve(&queryQpiFuncInput1.data, sizeof(queryQpiFuncInput1.data), &digest1, sizeof(digest1)); - sign(subseed1.m256i_u8, queryQpiFuncInput1.entity.m256i_u8, digest1.m256i_u8, (unsigned char*)&queryQpiFuncInput1.signature); - - // Test 1 with initial time - setMemory(utcTime, 0); - utcTime.Year = 2022; - utcTime.Month = 4; - utcTime.Day = 13; - utcTime.Hour = 12; - updateQpiTime(); - numberTickTransactions = 123; - system.tick = 4567890; - system.epoch = 987; - auto qpiReturned1 = test.queryQpiFunctions(queryQpiFuncInput1); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.year, 22); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.month, 4); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.day, 13); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.hour, 12); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.minute, 0); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.second, 0); - EXPECT_EQ((int)qpiReturned1.qpiFunctionsOutput.millisecond, 0); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.dayOfWeek, 0); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.arbitrator, arbitratorPubKey); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.computor0, id::zero()); - EXPECT_EQ((int)qpiReturned1.qpiFunctionsOutput.epoch, (int)system.epoch); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.invocationReward, 0); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.invocator, id::zero()); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.numberOfTickTransactions, numberTickTransactions); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.originator, id::zero()); - EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.tick, system.tick); - EXPECT_EQ(qpiReturned1.inputDataK12, digest1); - EXPECT_TRUE(qpiReturned1.inputSignatureValid); - - // K12 test for wrong signature: change data but not signature - TESTEXA::QueryQpiFunctions_input queryQpiFuncInput2 = queryQpiFuncInput1; - id digest2; - queryQpiFuncInput2.data.set(0, 0); - KangarooTwelve(&queryQpiFuncInput2.data, sizeof(queryQpiFuncInput2.data), &digest2, sizeof(digest2)); - EXPECT_NE(digest1, digest2); - - // Test 2 with current time - updateTime(); - updateQpiTime(); - numberTickTransactions = 42; - system.tick = 4567891; - system.epoch = 988; - broadcastedComputors.computors.publicKeys[0] = id(12, 34, 56, 78); - auto qpiReturned2 = test.queryQpiFunctions(queryQpiFuncInput2); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.year, utcTime.Year - 2000); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.month, utcTime.Month); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.day, utcTime.Day); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.hour, utcTime.Hour); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.minute, utcTime.Minute); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.second, utcTime.Second); - EXPECT_EQ((int)qpiReturned2.qpiFunctionsOutput.millisecond, utcTime.Nanosecond / 1000000); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.dayOfWeek, dayIndex(qpiReturned2.qpiFunctionsOutput.year, qpiReturned2.qpiFunctionsOutput.month, qpiReturned2.qpiFunctionsOutput.day) % 7); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.arbitrator, arbitratorPubKey); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.computor0, id(12, 34, 56, 78)); - EXPECT_EQ((int)qpiReturned2.qpiFunctionsOutput.epoch, (int)system.epoch); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.invocationReward, 0); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.invocator, id::zero()); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.numberOfTickTransactions, numberTickTransactions); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.originator, id::zero()); - EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.tick, system.tick); - EXPECT_EQ(qpiReturned2.inputDataK12, digest2); - EXPECT_FALSE(qpiReturned2.inputSignatureValid); -} - -TEST(ContractTestEx, QpiFunctionsIPO) -{ - // test IPO functions with IPO of TESTEXD - ContractTestingTestEx test; - system.epoch = contractDescriptions[TESTEXD_CONTRACT_INDEX].constructionEpoch - 1; - constexpr long long initialBalance = 12345678; - increaseEnergy(USER1, initialBalance); - increaseEnergy(TESTEXB_CONTRACT_ID, initialBalance); - increaseEnergy(TESTEXC_CONTRACT_ID, initialBalance); - - // Test output of qpi functions for invalid contract index - for (int i = 0; i < NUMBER_OF_COMPUTORS + 2; ++i) - { - const auto bid = test.getIpoBid(contractCount, i); - EXPECT_TRUE(isZero(bid.publicKey)); - EXPECT_EQ(bid.price, -1); - } - - // Test output of qpi functions for contract that is not in IPO - for (int i = 0; i < NUMBER_OF_COMPUTORS + 2; ++i) - { - const auto bid = test.getIpoBid(TESTEXB_CONTRACT_INDEX, i); - EXPECT_TRUE(isZero(bid.publicKey)); - EXPECT_EQ(bid.price, -2); - } - - // Test output of qpi functions without any bids - for (int i = 0; i < NUMBER_OF_COMPUTORS + 2; ++i) - { - const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); - EXPECT_TRUE(isZero(bid.publicKey)); - EXPECT_EQ(bid.price, (i < NUMBER_OF_COMPUTORS) ? 0 : -3); - } - - // Test bids with invalid contract, price, and quantity - EXPECT_EQ(test.qpiBidInIpo(contractCount, 10, 100), -1); - EXPECT_EQ(test.qpiBidInIpo(TESTEXC_CONTRACT_INDEX, 10, 100), -1); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 0, 100), -1); - EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, MAX_AMOUNT, 100), -1); - EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 10, 0), -1); - EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 10, NUMBER_OF_COMPUTORS + 1), -1); - EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); - - // Successfully bid in IPO - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 10, 100), 100); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); - EXPECT_EQ(bid.publicKey, (i < 100) ? TESTEXC_CONTRACT_ID : NULL_ID); - EXPECT_EQ(bid.price, (i < 100) ? 10 : 0); - } - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 100, 600), 600); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); - EXPECT_EQ(bid.publicKey, (i < 600) ? TESTEXB_CONTRACT_ID : TESTEXC_CONTRACT_ID); - EXPECT_EQ(bid.price, (i < 600) ? 100 : 10); - } - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 1000, 10), 10); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); - if (i < 10) - { - EXPECT_EQ(bid.publicKey, TESTEXC_CONTRACT_ID); - EXPECT_EQ(bid.price, 1000); - } - else if (i < 10 + 600) - { - EXPECT_EQ(bid.publicKey, TESTEXB_CONTRACT_ID); - EXPECT_EQ(bid.price, 100); - } - else - { - EXPECT_EQ(bid.publicKey, TESTEXC_CONTRACT_ID); - EXPECT_EQ(bid.price, 10); - } - } - - // Test too low bids - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 1, 10), 0); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 2, 10), 0); - - // Simulate end of IPO - finishIPOs(); - - // Check contract shares - Asset asset{NULL_ID, assetNameFromString("TESTEXD")}; - EXPECT_EQ(600, numberOfShares(asset, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX })); - EXPECT_EQ(76, numberOfShares(asset, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX })); - - // Check balances - const long long finalPrice = 10; - EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), initialBalance - 600 * finalPrice); - EXPECT_EQ(getBalance(TESTEXC_CONTRACT_ID), initialBalance - 76 * finalPrice); -} - -//------------------------------------------------------------------- -// Test CallbackPostIncomingTransfer - -class ContractTestCallbackPostIncomingTransfer : public ContractTestingTestEx -{ -public: - // test qpi.transfer() on contract SrcStateStruct. DstStateStruct is another contract to check. - template - void testQpiTransfer(const id& dstPublicKey, sint64 amount, sint64 fee = 0, const id& originator = USER1) - { - const id srcPublicKey(SrcStateStruct::__contract_index, 0, 0, 0); - const sint64 originatorBalanceBefore = getBalance(originator); - const sint64 srcBalanceBefore = getBalance(srcPublicKey); - const sint64 dstBalanceBefore = getBalance(dstPublicKey); - const auto srcBefore = getIncomingTransferAmounts(); - const auto dstBefore = getIncomingTransferAmounts(); - - EXPECT_GE(originatorBalanceBefore, fee); - bool success = qpiTransfer(dstPublicKey, amount, fee, originator); - EXPECT_TRUE(success); - - if (success) - { - const sint64 originatorBalanceAfter = getBalance(originator); - const sint64 srcBalanceAfter = getBalance(srcPublicKey); - const sint64 dstBalanceAfter = getBalance(dstPublicKey); - EXPECT_EQ(originatorBalanceAfter, originatorBalanceBefore - fee); - if (srcPublicKey != dstPublicKey) - { - EXPECT_EQ(srcBalanceAfter, srcBalanceBefore + fee - amount); - EXPECT_EQ(dstBalanceAfter, dstBalanceBefore + amount); - } - else - { - EXPECT_EQ(srcBalanceAfter, srcBalanceBefore + fee); - } - - const auto srcAfter = getIncomingTransferAmounts(); - const auto dstAfter = getIncomingTransferAmounts(); - EXPECT_EQ(srcAfter.procedureTransactionAmount, srcBefore.procedureTransactionAmount + fee); - if (srcPublicKey != dstPublicKey) - { - EXPECT_EQ(dstAfter.procedureTransactionAmount, dstBefore.procedureTransactionAmount); - EXPECT_EQ(srcAfter.qpiTransferAmount, srcBefore.qpiTransferAmount); - } - if (dstPublicKey == id(DstStateStruct::__contract_index, 0, 0, 0)) - { - EXPECT_EQ(dstAfter.qpiTransferAmount, dstBefore.qpiTransferAmount + amount); - } - else - { - EXPECT_EQ(dstAfter.qpiTransferAmount, dstBefore.qpiTransferAmount); - } - EXPECT_EQ(srcAfter.standardTransactionAmount, srcBefore.standardTransactionAmount); - EXPECT_EQ(dstAfter.standardTransactionAmount, dstBefore.standardTransactionAmount); - EXPECT_EQ(srcAfter.qpiDistributeDividendsAmount, srcBefore.qpiDistributeDividendsAmount); - EXPECT_EQ(dstAfter.qpiDistributeDividendsAmount, dstBefore.qpiDistributeDividendsAmount); - EXPECT_EQ(srcAfter.revenueDonationAmount, srcBefore.revenueDonationAmount); - EXPECT_EQ(dstAfter.revenueDonationAmount, dstBefore.revenueDonationAmount); - EXPECT_EQ(srcAfter.ipoBidRefundAmount, srcBefore.ipoBidRefundAmount); - EXPECT_EQ(dstAfter.ipoBidRefundAmount, dstBefore.ipoBidRefundAmount); - } - } - - // test qpi.distributeDividends() on contract SrcStateStruct. DstStateStruct is another contract to check. - template - void testQpiDistributeDividends(sint64 amountPerShare, const std::vector>& shareholders, sint64 fee = 0, const id& originator = USER1) - { - // check number of shares - unsigned int totalShareCount = 0; - for (const auto& ownerShareCountPair : shareholders) - totalShareCount += ownerShareCountPair.second; - EXPECT_EQ(totalShareCount, NUMBER_OF_COMPUTORS); - - // get state before call and compute state expected after call - const id srcPublicKey(SrcStateStruct::__contract_index, 0, 0, 0); - const id dstPublicKey(DstStateStruct::__contract_index, 0, 0, 0); - std::map expectedBalances; - expectedBalances[originator] = getBalance(originator); - EXPECT_GE(expectedBalances[originator], fee); - expectedBalances[srcPublicKey] = getBalance(srcPublicKey); - expectedBalances[dstPublicKey] = getBalance(dstPublicKey); - for (const auto& ownerShareCountPair : shareholders) - expectedBalances[ownerShareCountPair.first] = getBalance(ownerShareCountPair.first); - expectedBalances[originator] -= fee; - expectedBalances[srcPublicKey] += fee - amountPerShare * NUMBER_OF_COMPUTORS; - auto expectedIncomingSrc = getIncomingTransferAmounts(); - auto expectedIncomingDst = getIncomingTransferAmounts(); - expectedIncomingSrc.procedureTransactionAmount += fee; - if (srcPublicKey == dstPublicKey) - expectedIncomingDst.procedureTransactionAmount += fee; - for (const auto& ownerShareCountPair : shareholders) - { - const sint64 dividend = amountPerShare * ownerShareCountPair.second; - expectedBalances[ownerShareCountPair.first] += dividend; - if (ownerShareCountPair.first == srcPublicKey) - expectedIncomingSrc.qpiDistributeDividendsAmount += dividend; - if (ownerShareCountPair.first == dstPublicKey) - expectedIncomingDst.qpiDistributeDividendsAmount += dividend; - } - - bool success = qpiDistributeDividends(amountPerShare, fee, originator); - EXPECT_TRUE(success); - - if (success) - { - for (const auto& idBalancePair : expectedBalances) - { - EXPECT_EQ(getBalance(idBalancePair.first), idBalancePair.second); - } - - const auto observedIncomingSrc = getIncomingTransferAmounts(); - const auto observedIncomingDst = getIncomingTransferAmounts(); - EXPECT_EQ(expectedIncomingSrc.standardTransactionAmount, observedIncomingSrc.standardTransactionAmount); - EXPECT_EQ(expectedIncomingDst.standardTransactionAmount, observedIncomingDst.standardTransactionAmount); - EXPECT_EQ(expectedIncomingSrc.procedureTransactionAmount, observedIncomingSrc.procedureTransactionAmount); - EXPECT_EQ(expectedIncomingDst.procedureTransactionAmount, observedIncomingDst.procedureTransactionAmount); - EXPECT_EQ(expectedIncomingSrc.qpiTransferAmount, observedIncomingSrc.qpiTransferAmount); - EXPECT_EQ(expectedIncomingDst.qpiTransferAmount, observedIncomingDst.qpiTransferAmount); - EXPECT_EQ(expectedIncomingSrc.qpiDistributeDividendsAmount, observedIncomingSrc.qpiDistributeDividendsAmount); - EXPECT_EQ(expectedIncomingDst.qpiDistributeDividendsAmount, observedIncomingDst.qpiDistributeDividendsAmount); - EXPECT_EQ(expectedIncomingSrc.revenueDonationAmount, observedIncomingSrc.revenueDonationAmount); - EXPECT_EQ(expectedIncomingDst.revenueDonationAmount, observedIncomingDst.revenueDonationAmount); - EXPECT_EQ(expectedIncomingSrc.ipoBidRefundAmount, observedIncomingSrc.ipoBidRefundAmount); - EXPECT_EQ(expectedIncomingDst.ipoBidRefundAmount, observedIncomingDst.ipoBidRefundAmount); - } - } -}; - -TEST(ContractTestEx, CallbackPostIncomingTransfer) -{ - // Tested types of incoming transfers (should be also tested in testnet): - // - TransferType::qpiTransfer (including transfer to oneself) - // - TransferType::qpiDistributeDividends (including dividends to oneself) - // - TransferType::procedureTransaction - // - TransferType::ipoBidRefund through qpi.bidInIpo() - // - // Important test: triggering callback from callback must be prevented (checked by ASSERTs in contracts) - // - // The following cannot be tested with Google Test at the moment and have to be tested in the testnet. - // - TransferType::standardTransaction - // - TransferType::revenueDonation - // - TransferType::ipoBidRefund through transaction - ContractTestCallbackPostIncomingTransfer test; - - increaseEnergy(USER1, 12345678); - increaseEnergy(USER2, 31427); - increaseEnergy(USER3, 218000); - increaseEnergy(TESTEXB_CONTRACT_ID, 19283764); - increaseEnergy(TESTEXC_CONTRACT_ID, 987654321); - - // qpi.transfer() to other contract - test.testQpiTransfer(TESTEXC_CONTRACT_ID, 100, 1000, USER1); - test.testQpiTransfer(TESTEXB_CONTRACT_ID, 2000, 200, USER1); - test.testQpiTransfer(TESTEXB_CONTRACT_ID, 300, 3000, USER1); - test.testQpiTransfer(TESTEXC_CONTRACT_ID, 4000, 400, USER1); - - // qpi.transfer() to self - test.testQpiTransfer(TESTEXB_CONTRACT_ID, 50, 500, USER1); - test.testQpiTransfer(TESTEXB_CONTRACT_ID, 600, 60, USER1); - test.testQpiTransfer(TESTEXC_CONTRACT_ID, 700, 7000, USER1); - test.testQpiTransfer(TESTEXC_CONTRACT_ID, 8000, 800, USER1); - - // qpi.transfer() to non-contract entity - test.testQpiTransfer(getUser(0), 900, 9000, USER1); - test.testQpiTransfer(getUser(1), 10000, 1000, USER1); - test.testQpiTransfer(getUser(2), 11000, 1100, USER1); - test.testQpiTransfer(getUser(3), 12000, 1200, USER1); - - // issue contract shares - std::vector> sharesTestExB{ {USER1, 356}, {TESTEXC_CONTRACT_ID, 200}, {TESTEXB_CONTRACT_ID, 100}, {TESTEXA_CONTRACT_ID, 20} }; - issueContractShares(TESTEXB_CONTRACT_INDEX, sharesTestExB); - std::vector> sharesTestExC{ {USER2, 576}, {USER3, 40}, {TESTEXC_CONTRACT_ID, 30}, {TESTEXB_CONTRACT_ID, 20}, {TESTEXA_CONTRACT_ID, 10} }; - issueContractShares(TESTEXC_CONTRACT_INDEX, sharesTestExC); - - // test qpi.distributeDividends() - test.testQpiDistributeDividends(1, sharesTestExB, 1234, USER1); - test.testQpiDistributeDividends(11, sharesTestExB, 9764, USER2); - test.testQpiDistributeDividends(3, sharesTestExB, 42, USER3); - test.testQpiDistributeDividends(2, sharesTestExC, 12345, USER1); - test.testQpiDistributeDividends(13, sharesTestExC, 98, USER2); - test.testQpiDistributeDividends(4, sharesTestExC, 9, USER3); - - // test refund in qpi.bidInIPO() and finalizeIpo() - system.epoch = contractDescriptions[TESTEXD_CONTRACT_INDEX].constructionEpoch - 1; - auto itaB1 = test.getIncomingTransferAmounts(); - auto itaC1 = test.getIncomingTransferAmounts(); - EXPECT_EQ(itaB1.ipoBidRefundAmount, 0); - EXPECT_EQ(itaC1.ipoBidRefundAmount, 0); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 20, NUMBER_OF_COMPUTORS, 42), NUMBER_OF_COMPUTORS); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 30, NUMBER_OF_COMPUTORS * 3 / 4, 13), NUMBER_OF_COMPUTORS * 3 / 4); - auto itaB2 = test.getIncomingTransferAmounts(); - auto itaC2 = test.getIncomingTransferAmounts(); - // -> in 75% 30 (C), in 25% 20 (B), refund 75% 20 (B) - EXPECT_EQ(itaB2.ipoBidRefundAmount, 20 * NUMBER_OF_COMPUTORS * 3 / 4); - EXPECT_EQ(itaC2.ipoBidRefundAmount, 0); - EXPECT_EQ(itaB2.procedureTransactionAmount, itaB1.procedureTransactionAmount + 42); - EXPECT_EQ(itaC2.procedureTransactionAmount, itaC1.procedureTransactionAmount + 13); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 50, NUMBER_OF_COMPUTORS / 2, 3), NUMBER_OF_COMPUTORS / 2); - auto itaB3 = test.getIncomingTransferAmounts(); - auto itaC3 = test.getIncomingTransferAmounts(); - // -> in 50% 50 (C), in 50% 30 (C), ex 25% 30 (C), ex 25% 20 (B) - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); - EXPECT_EQ(bid.publicKey, TESTEXC_CONTRACT_ID); - EXPECT_EQ(bid.price, (i < NUMBER_OF_COMPUTORS / 2) ? 50 : 30); - } - EXPECT_EQ(itaB3.ipoBidRefundAmount, itaB2.ipoBidRefundAmount + 20 * NUMBER_OF_COMPUTORS / 4); - EXPECT_EQ(itaC3.ipoBidRefundAmount, itaC2.ipoBidRefundAmount + 30 * NUMBER_OF_COMPUTORS / 4); - EXPECT_EQ(itaB3.procedureTransactionAmount, itaB2.procedureTransactionAmount); - EXPECT_EQ(itaC3.procedureTransactionAmount, itaC2.procedureTransactionAmount + 3); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 99, NUMBER_OF_COMPUTORS * 3 / 4, 14), NUMBER_OF_COMPUTORS * 3 / 4); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 9, NUMBER_OF_COMPUTORS / 2, 123), 0); - EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 60, NUMBER_OF_COMPUTORS, 654), NUMBER_OF_COMPUTORS / 4); - auto itaB4 = test.getIncomingTransferAmounts(); - auto itaC4 = test.getIncomingTransferAmounts(); - // -> in 75% 99 (B), in 25% 60 (C), ex 75% 60 (C), ex 50% 50 (C), ex 50% 30 (C), ex 50% 9 (B) - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); - EXPECT_EQ(bid.publicKey, (i < NUMBER_OF_COMPUTORS * 3 / 4) ? TESTEXB_CONTRACT_ID : TESTEXC_CONTRACT_ID); - EXPECT_EQ(bid.price, (i < NUMBER_OF_COMPUTORS * 3 / 4) ? 99 : 60); - } - EXPECT_EQ(itaB4.ipoBidRefundAmount, itaB3.ipoBidRefundAmount + 9 * NUMBER_OF_COMPUTORS / 2); - EXPECT_EQ(itaC4.ipoBidRefundAmount, itaC3.ipoBidRefundAmount + 60 * NUMBER_OF_COMPUTORS * 3 / 4 + 50 * NUMBER_OF_COMPUTORS / 2 + 30 * NUMBER_OF_COMPUTORS / 2); - EXPECT_EQ(itaB4.procedureTransactionAmount, itaB3.procedureTransactionAmount + 14 + 123); - EXPECT_EQ(itaC4.procedureTransactionAmount, itaC3.procedureTransactionAmount + 654); - - // simulate end of IPO - finishIPOs(); - - // check contract shares - Asset asset{ NULL_ID, assetNameFromString("TESTEXD") }; - EXPECT_EQ(NUMBER_OF_COMPUTORS * 3 / 4, numberOfShares(asset, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX })); - EXPECT_EQ(NUMBER_OF_COMPUTORS * 1 / 4, numberOfShares(asset, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX })); - - // check refunds (finalPrice = 60) - auto itaB5 = test.getIncomingTransferAmounts(); - auto itaC5 = test.getIncomingTransferAmounts(); - EXPECT_EQ(itaB5.ipoBidRefundAmount, itaB4.ipoBidRefundAmount + NUMBER_OF_COMPUTORS * 3 / 4 * (99 - 60)); - EXPECT_EQ(itaC5.ipoBidRefundAmount, itaC4.ipoBidRefundAmount); - EXPECT_EQ(itaB5.procedureTransactionAmount, itaB4.procedureTransactionAmount); - EXPECT_EQ(itaC5.procedureTransactionAmount, itaC4.procedureTransactionAmount); - EXPECT_EQ(itaB5.standardTransactionAmount, itaB1.standardTransactionAmount); - EXPECT_EQ(itaC5.standardTransactionAmount, itaC1.standardTransactionAmount); - EXPECT_EQ(itaB5.qpiDistributeDividendsAmount, itaB1.qpiDistributeDividendsAmount); - EXPECT_EQ(itaC5.qpiDistributeDividendsAmount, itaC1.qpiDistributeDividendsAmount); - EXPECT_EQ(itaB5.qpiTransferAmount, itaB1.qpiTransferAmount); - EXPECT_EQ(itaC5.qpiTransferAmount, itaC1.qpiTransferAmount); - EXPECT_EQ(itaB5.revenueDonationAmount, itaB1.revenueDonationAmount); - EXPECT_EQ(itaC5.revenueDonationAmount, itaC1.revenueDonationAmount); -} - -TEST(ContractTestEx, BurnAssets) -{ - ContractTestCallbackPostIncomingTransfer test; - - increaseEnergy(USER1, 1234567890123llu); - increaseEnergy(TESTEXB_CONTRACT_ID, 19283764); - - { - // issue contract shares - Asset asset{ NULL_ID, assetNameFromString("TESTEXB") }; - std::vector> sharesTestExB{ {USER1, 356}, {TESTEXC_CONTRACT_ID, 200}, {TESTEXB_CONTRACT_ID, 100}, {TESTEXA_CONTRACT_ID, 20} }; - issueContractShares(TESTEXB_CONTRACT_INDEX, sharesTestExB); - EXPECT_EQ(356, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); - - // burning contract shares is supposed to fail - EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset, USER1, NULL_ID, 100), 0); - EXPECT_EQ(356, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); - } - - { - // issue non-contract asset shares - Asset asset{ USER1, assetNameFromString("BLOB") }; - EXPECT_EQ(test.issueAssetQx(asset, 1000000, 0, 0), 1000000); - EXPECT_EQ(1000000, numberOfShares(asset)); - EXPECT_EQ(1000000, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX })); - EXPECT_EQ(1000000, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); - - // burn non-contract shares - EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset, USER1, NULL_ID, 100), 100); - EXPECT_EQ(1000000 - 100, numberOfShares(asset)); - EXPECT_EQ(1000000 - 100, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX })); - EXPECT_EQ(1000000 - 100, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); - } -} - -TEST(ContractTestEx, ShareholderProposals) -{ - ContractTestingTestEx test; - uint16 proposalIdx = 0; - - system.epoch = 200; - - increaseEnergy(USER1, 12345678); - increaseEnergy(USER2, 31427); - increaseEnergy(USER3, 218000); - increaseEnergy(USER4, 218000); - increaseEnergy(TESTEXA_CONTRACT_ID, 987654321); - increaseEnergy(TESTEXB_CONTRACT_ID, 19283764); - - // issue contract shares - std::vector> sharesTestExA{ - {USER1, 356}, - {USER2, 200}, - {TESTEXB_CONTRACT_ID, 100}, - {USER3, 20} - }; - issueContractShares(TESTEXA_CONTRACT_INDEX, sharesTestExA); - - // enable that TESTEXA accepts transfer for 0 fee - test.setPreAcquireSharesOutput(true, 0); - - // transfer management rights of some shares to other contract to cover case of multiple asset records of single possessor - const Asset TESTEXA_ASSET{ NULL_ID, assetNameFromString("TESTEXA") }; - EXPECT_EQ(test.transferShareManagementRightsQx(TESTEXA_ASSET, USER2, 50, TESTEXA_CONTRACT_INDEX), 50); - EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), 356); - EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 150); - EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 50); - EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }), 100); - EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 20); - EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER4, QX_CONTRACT_INDEX }, { USER4, QX_CONTRACT_INDEX }), 0); - - // fail: 4 options not supported with Yes/No proposals - test.setupShareholderProposalTestExA(USER2, ProposalTypes::FourOptions, false, 0, false, 0, false, 0, false); - - // fail: no right, because no shareholder - test.setupShareholderProposalTestExA(USER4, ProposalTypes::ThreeOptions, false, 0, false, 0, false, 0, false); - - // fail: transfer not allowed - test.setupShareholderProposalTestExA(USER2, ProposalTypes::TransferYesNo, false, 0, false, 0, false, 0, false); - - // fail: invalid value of variable - test.setupShareholderProposalTestExA(USER2, ProposalTypes::VariableYesNo, false, 0, false, 0, true, 120, false); - - // check that no active/inactive proposals - EXPECT_EQ(test.getShareholderProposalIndices(true).size(), 0); - EXPECT_EQ(test.getShareholderProposalIndices(false).size(), 0); - - // success: set var3 with single-var proposal - proposalIdx = test.setupShareholderProposalTestExA(USER2, ProposalTypes::VariableYesNo, false, 0, false, 0, true, 100, true); - - // check that no active/inactive proposals - auto proposalIndices = test.getShareholderProposalIndices(true); - EXPECT_TRUE(proposalIndices.size() == 1 && proposalIndices[0] == proposalIdx); - EXPECT_EQ(test.getShareholderProposalIndices(false).size(), 0); - - // fail: try to get non-existing proposal - auto fullProposalData = test.getShareholderProposal(proposalIdx + 1); - EXPECT_EQ(fullProposalData.proposerPubicKey, NULL_ID); - EXPECT_EQ((int)fullProposalData.proposal.type, 0); - - // success: get existing proposal - fullProposalData = test.getShareholderProposal(proposalIdx); - EXPECT_EQ(fullProposalData.proposerPubicKey, USER2); - EXPECT_EQ((int)fullProposalData.proposal.type, (int)ProposalTypes::VariableYesNo); - auto proposal = fullProposalData.proposal; - - // fail: try to get shareholder votes of user who is no shareholder - auto votes = test.getShareholderVotes(proposalIdx, USER4); - EXPECT_EQ((int)votes.proposalType, 0); - - // fail: try to get shareholder votes of non-existing proposal - votes = test.getShareholderVotes(proposalIdx + 1, USER1); - EXPECT_EQ((int)votes.proposalType, 0); - - // success: get shareholder votes of user who is no shareholder - votes = test.getShareholderVotes(proposalIdx, USER1); - EXPECT_EQ((int)votes.proposalType, (int)proposal.type); - EXPECT_EQ((int)votes.proposalIndex, (int)proposalIdx); - EXPECT_EQ(votes.proposalTick, proposal.tick); - checkVoteCounts(votes, {}); - - // set all votes of USER1 to option 0 with single-vote struct - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx, proposal, 0)); - - // get shareholder votes of user who is no shareholder and check that they are correct - votes = test.getShareholderVotes(proposalIdx, USER1); - EXPECT_EQ((int)votes.proposalType, (int)proposal.type); - checkVoteCounts(votes, { {0, 356} }); - - // set 50 votes of USER2 to option 0 and 150 to option 1 - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {0, 50}, {1, 150} })); - votes = test.getShareholderVotes(proposalIdx, USER2); - EXPECT_EQ((int)votes.proposalType, (int)proposal.type); - checkVoteCounts(votes, { {0, 50}, {1, 150} }); - - // fail: set 51 votes of USER2 to option 1 and 150 to option 0 (more votes than shares) - EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {1, 51}, {0, 150} })); - votes = test.getShareholderVotes(proposalIdx, USER2); - EXPECT_EQ((int)votes.proposalType, (int)proposal.type); - checkVoteCounts(votes, { {0, 50}, {1, 150} }); - - // set 20 votes of USER2 to option 0 and 30 to option 1 (some votes unused) - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {0, 20}, {1, 30} })); - votes = test.getShareholderVotes(proposalIdx, USER2); - EXPECT_EQ((int)votes.proposalType, (int)proposal.type); - checkVoteCounts(votes, { {0, 20}, {1, 30} }); - - // fail: try to get voting results of invalid proposal - auto results = test.getShareholderVotingResults(proposalIdx + 1); - EXPECT_EQ(results.totalVotesAuthorized, 0); - - // check voting results - results = test.getShareholderVotingResults(proposalIdx); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ((int)results.optionCount, 2); - EXPECT_EQ(results.optionVoteCount.get(0), 20 + 356); - EXPECT_EQ(results.optionVoteCount.get(1), 30); - EXPECT_EQ(results.totalVotesCasted, 20 + 356 + 30); - EXPECT_EQ(results.getAcceptedOption(), -1); - EXPECT_EQ(results.getMostVotedOption(), 0); - - // set 1 vote of USER3 to option 0 and 19 to option 1 - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdx, proposal, { {0, 1}, {1, 19} })); - - // change votes of USER1 - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx, proposal, { {0, 300}, {1, 50} })); - - votes = test.getShareholderVotes(proposalIdx, USER3); - EXPECT_EQ((int)votes.proposalType, (int)proposal.type); - checkVoteCounts(votes, { {0, 1}, {1, 19} }); - votes = test.getShareholderVotes(proposalIdx, USER2); - checkVoteCounts(votes, { {0, 20}, {1, 30} }); - votes = test.getShareholderVotes(proposalIdx, USER1); - checkVoteCounts(votes, { {0, 300}, {1, 50} }); - - results = test.getShareholderVotingResults(proposalIdx); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ((int)results.optionCount, 2); - EXPECT_EQ(results.optionVoteCount.get(0), 1 + 20 + 300); - EXPECT_EQ(results.optionVoteCount.get(1), 19 + 30 + 50); - EXPECT_EQ(results.totalVotesCasted, 1 + 20 + 300 + 19 + 30 + 50); - EXPECT_EQ(results.getAcceptedOption(), -1); - EXPECT_EQ(results.getMostVotedOption(), 0); - - // withdraw votes of USER1 and USER3 - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx, proposal, std::vector>())); - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdx, proposal, NO_VOTE_VALUE)); - - votes = test.getShareholderVotes(proposalIdx, USER3); - checkVoteCounts(votes, {}); - votes = test.getShareholderVotes(proposalIdx, USER2); - checkVoteCounts(votes, { {0, 20}, {1, 30} }); - votes = test.getShareholderVotes(proposalIdx, USER1); - checkVoteCounts(votes, {}); - - results = test.getShareholderVotingResults(proposalIdx); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 20); - EXPECT_EQ(results.optionVoteCount.get(1), 30); - EXPECT_EQ(results.totalVotesCasted, 20 + 30); - EXPECT_EQ(results.getAcceptedOption(), -1); - EXPECT_EQ(results.getMostVotedOption(), 1); - - // fail: try to set all votes of USER2 to invalid value with single-vote struct - // (uses Multi-Vote internally for testing compatibility, so votes of the user are reset) - EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, 4)); - votes = test.getShareholderVotes(proposalIdx, USER2); - checkVoteCounts(votes, {}); - - // fail: try to set votes of invalid proposal index - EXPECT_FALSE(test.setShareholderVotes(USER2, 0xffff, proposal, { { 0, 111 } })); - - // fail: try to set votes of inactive proposal - ++system.epoch; - EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 } })); - --system.epoch; - - // fail: try to set votes of with wrong proposal type - proposal.type = ProposalTypes::VariableThreeValues; - EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 } })); - proposal.type = ProposalTypes::VariableYesNo; - - // fail: try to set votes of with wrong proposal tick - ++proposal.tick; - EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 } })); - --proposal.tick; - - // fail: try to set votes for USER4 who is no shareholder - EXPECT_FALSE(test.setShareholderVotes(USER4, proposalIdx, proposal, { { 0, 111 } })); - - // success: set votes with duplicate values in array - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 }, {1, 10}, {0, 22}, {1, 3} })); - votes = test.getShareholderVotes(proposalIdx, USER2); - checkVoteCounts(votes, { {0, 133}, {1, 13} }); - - // fail: try to set votes of USER2 to invalid value with multi-vote struct - // (votes of the user are reset) - EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {0, 12}, {1, 23}, {2, 34} })); - votes = test.getShareholderVotes(proposalIdx, USER2); - checkVoteCounts(votes, {}); - - // voting of TESTEXB as shareholder of TESTEXA (originator not checked by procedure) - // user procedure TESTEXB::setVotesInOtherContractAsShareholder - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdx, proposal, { {0, 10}, {1, 20}, {0, 70} })); - votes = test.getShareholderVotes(proposalIdx, TESTEXB_CONTRACT_ID); - checkVoteCounts(votes, { {0, 80}, {1, 20} }); - - results = test.getShareholderVotingResults(proposalIdx); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 80); - EXPECT_EQ(results.optionVoteCount.get(1), 20); - EXPECT_EQ(results.totalVotesCasted, 100); - EXPECT_EQ(results.getAcceptedOption(), -1); - EXPECT_EQ(results.getMostVotedOption(), 0); - - ////////////////////////////////////////////////////// - // create new shareholder proposal in TESTEXA as shareholder TESTEXB - TESTEXA::SetShareholderProposal_input setShareholderProposalInput2; - setShareholderProposalInput2.proposalData.type = ProposalTypes::MultiVariablesYesNo; - setShareholderProposalInput2.proposalData.epoch = system.epoch; - setMemory(setShareholderProposalInput2.multiVarData, 0); - - // fails to create proposal, because multiVarData is invalid (originator not checked by procedure) - uint16 proposalIdx2 = test.setProposalInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, setShareholderProposalInput2); - EXPECT_EQ((int)proposalIdx2, (int)INVALID_PROPOSAL_INDEX); - - // create proposal (originator not checked by procedure) - setShareholderProposalInput2.multiVarData.hasValueDummyStateVariable1 = true; - setShareholderProposalInput2.multiVarData.hasValueDummyStateVariable2 = true; - setShareholderProposalInput2.multiVarData.hasValueDummyStateVariable3 = true; - setShareholderProposalInput2.multiVarData.optionYesValues.dummyStateVariable1 = 1; - setShareholderProposalInput2.multiVarData.optionYesValues.dummyStateVariable2 = 2; - setShareholderProposalInput2.multiVarData.optionYesValues.dummyStateVariable3 = 3; - proposalIdx2 = test.setProposalInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, setShareholderProposalInput2); - - // get and check new proposal - auto fullProposalData2 = test.getShareholderProposal(proposalIdx2); - EXPECT_EQ(fullProposalData2.proposerPubicKey, TESTEXB_CONTRACT_ID); - EXPECT_EQ((int)fullProposalData2.proposal.type, (int)ProposalTypes::MultiVariablesYesNo); - auto proposal2 = fullProposalData2.proposal; - EXPECT_EQ(fullProposalData2.multiVarData, setShareholderProposalInput2.multiVarData); - - // cast votes - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdx2, proposal2, { {1, 90} })); - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx2, proposal2, { {0, 50}, {1, 260} })); - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx2, proposal2, { {0, 10}, {1, 160} })); - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdx2, proposal2, { {0, 1}, {1, 15} })); - results = test.getShareholderVotingResults(proposalIdx2); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 61); - EXPECT_EQ(results.optionVoteCount.get(1), 525); - EXPECT_EQ(results.getAcceptedOption(), 1); - EXPECT_EQ(results.totalVotesCasted, 61 + 525); - - // test proposal listing function (2 active, 0 inactive) - proposalIndices = test.getShareholderProposalIndices(true); - EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdx && proposalIndices[1] == proposalIdx2); - EXPECT_EQ(test.getShareholderProposalIndices(false).size(), 0); - - // test that variables are set correctly after epoch switch - test.getStateTestExampleA()->checkVariablesSetByProposal(0, 0, 0); - test.endEpoch(); - ++system.epoch; - test.getStateTestExampleA()->checkVariablesSetByProposal(1, 2, 3); - - // test proposal listing function (2 inactive by USER2/TESTEXB, 0 active) - proposalIndices = test.getShareholderProposalIndices(false); - EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdx && proposalIndices[1] == proposalIdx2); - EXPECT_EQ(test.getShareholderProposalIndices(true).size(), 0); - - // Setup proposal to change variable 1 - uint16 proposalIdxA1 = test.setupShareholderProposalTestExA(USER1, ProposalTypes::VariableYesNo, true, 13); - EXPECT_NE((int)proposalIdxA1, (int)INVALID_PROPOSAL_INDEX); - auto proposalDataA1 = test.getShareholderProposal(proposalIdxA1); - auto proposalA1 = proposalDataA1.proposal; - EXPECT_EQ((int)proposalA1.type, (int)ProposalTypes::VariableYesNo); - - // Setup proposal to change variable 2 and 3 - uint16 proposalIdxA2 = test.setupShareholderProposalTestExA(USER2, ProposalTypes::MultiVariablesYesNo, false, 0, true, 4, true, 5); - EXPECT_NE((int)proposalIdxA2, (int)INVALID_PROPOSAL_INDEX); - auto proposalDataA2 = test.getShareholderProposal(proposalIdxA2); - auto proposalA2 = proposalDataA2.proposal; - EXPECT_EQ((int)proposalA2.type, (int)ProposalTypes::MultiVariablesYesNo); - EXPECT_EQ(proposalDataA2.proposerPubicKey, USER2); - EXPECT_EQ(proposalDataA2.multiVarData.optionYesValues.dummyStateVariable2, 4); - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxA2, proposalA2, { {0, 3} })); - checkVoteCounts(test.getShareholderVotes(proposalIdxA2, USER2), { {0, 3} }); - - // Overwrite proposal to change variable 2 and 3 - proposalIdxA2 = test.setupShareholderProposalTestExA(USER2, ProposalTypes::MultiVariablesYesNo, false, 0, true, 1337, true, 42); - EXPECT_NE((int)proposalIdxA2, (int)INVALID_PROPOSAL_INDEX); - checkVoteCounts(test.getShareholderVotes(proposalIdxA2, USER2), {}); - - /////////////////////////////////////////////////////////////// - // Proposals in TestExB - - // issue contract shares - std::vector> sharesTestExB{ - {TESTEXA_CONTRACT_ID, 256}, - {USER2, 200}, - {USER3, 100}, - {USER4, 120} - }; - issueContractShares(TESTEXB_CONTRACT_INDEX, sharesTestExB); - const Asset TESTEXB_ASSET{ NULL_ID, assetNameFromString("TESTEXB") }; - EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { TESTEXA_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXA_CONTRACT_ID, QX_CONTRACT_INDEX }), 256); - EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 200); - EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 100); - EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER4, QX_CONTRACT_INDEX }, { USER4, QX_CONTRACT_INDEX }), 120); - EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), 0); - - // Create scalar variable proposal - TESTEXB::ProposalDataT proposalB1; - proposalB1.epoch = system.epoch; - proposalB1.type = ProposalTypes::VariableScalarMean; - proposalB1.data.variableScalar.variable = 0; - proposalB1.data.variableScalar.minValue = 0; - proposalB1.data.variableScalar.maxValue = MAX_AMOUNT; - proposalB1.data.variableScalar.proposedValue = 1000; - uint16 proposalIdxB1 = test.setShareholderProposal(USER2, { proposalB1 }); - EXPECT_NE((int)proposalIdxB1, (int)INVALID_PROPOSAL_INDEX); - auto proposalDataB1 = test.getShareholderProposal(proposalIdxB1); - proposalB1 = proposalDataB1.proposal; // needed to set tick - EXPECT_EQ((int)proposalDataB1.proposal.type, (int)ProposalTypes::VariableScalarMean); - EXPECT_EQ(proposalDataB1.proposerPubicKey, USER2); - EXPECT_EQ(proposalDataB1.proposal.data.variableScalar.maxValue, MAX_AMOUNT); - EXPECT_EQ(proposalDataB1.proposal.data.variableScalar.proposedValue, 1000); - - // Create multi-option variable proposal as shareholder TESTEXA - TESTEXB::ProposalDataT proposalB2; - proposalB2.epoch = system.epoch; - proposalB2.type = ProposalTypes::VariableFourValues; - proposalB2.data.variableOptions.variable = 1; - proposalB2.data.variableOptions.values.set(0, 100); - proposalB2.data.variableOptions.values.set(1, 1000); - proposalB2.data.variableOptions.values.set(2, 10000); - proposalB2.data.variableOptions.values.set(3, 100000); - uint16 proposalIdxB2 = test.setProposalInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, TESTEXB::SetShareholderProposal_input{ proposalB2 }); - EXPECT_NE((int)proposalIdxB2, (int)INVALID_PROPOSAL_INDEX); - auto proposalDataB2 = test.getShareholderProposal(proposalIdxB2); - proposalB2 = proposalDataB2.proposal; // needed to set tick - EXPECT_EQ((int)proposalDataB2.proposal.type, (int)ProposalTypes::VariableFourValues); - EXPECT_EQ(proposalDataB2.proposerPubicKey, TESTEXA_CONTRACT_ID); - EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.variable, 1); - EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(0), 100); - EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(1), 1000); - EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(2), 10000); - EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(3), 100000); - - // cast votes in A1 - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdxA1, proposalA1, { {0, 60}, {1, 270} })); - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxA1, proposalA1, { {0, 15}, {1, 180} })); - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdxA1, proposalA1, { {1, 80}, {0, 15} })); - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxA1, proposalA1, { {0, 9}, {1, 11} })); - results = test.getShareholderVotingResults(proposalIdxA1); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 99); - EXPECT_EQ(results.optionVoteCount.get(1), 541); - EXPECT_EQ(results.getAcceptedOption(), 1); - EXPECT_EQ(results.totalVotesCasted, 99 + 541); - - // cast votes in A2 - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdxA2, proposalA2, { {0, 150}, {1, 150} })); - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxA2, proposalA2, { {0, 100}, {1, 100} })); - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdxA2, proposalA2, { {1, 50}, {0, 50} })); - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxA2, proposalA2, { {0, 10}, {1, 10} })); - results = test.getShareholderVotingResults(proposalIdxA2); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 310); - EXPECT_EQ(results.optionVoteCount.get(1), 310); - EXPECT_EQ(results.getAcceptedOption(), 0); - EXPECT_EQ(results.totalVotesCasted, 620); - EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdxA2, proposalA2, { {0, 0}, {1, 350} })); - results = test.getShareholderVotingResults(proposalIdxA2); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 160); - EXPECT_EQ(results.optionVoteCount.get(1), 510); - EXPECT_EQ(results.getAcceptedOption(), 1); - EXPECT_EQ(results.totalVotesCasted, 670); - - // cast votes in B1 - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB1, proposalB1, { {0, 10}, {100, 20}, {1000, 200}, {10000, 10}, {100000, 5}, {1000000, 5}, {10000000, 2}, {100000000, 2} })); - checkVoteCounts(test.getShareholderVotes(proposalIdxB1, TESTEXA_CONTRACT_ID), { {0, 10}, {100, 20}, {1000, 200}, {10000, 10}, {100000, 5}, {1000000, 5}, {10000000, 2}, {100000000, 2} }); - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxB1, proposalB1, { {100, 200} })); - checkVoteCounts(test.getShareholderVotes(proposalIdxB1, USER2), { {100, 200} }); - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxB1, proposalB1, { {150, 90}, {200, 10} })); - checkVoteCounts(test.getShareholderVotes(proposalIdxB1, USER3), { {150, 90}, {200, 10} }); - EXPECT_TRUE(test.setShareholderVotes(USER4, proposalIdxB1, proposalB1, { {300, 99}, {11974, 1} })); - checkVoteCounts(test.getShareholderVotes(proposalIdxB1, USER4), { {300, 99}, {11974, 1} }); - results = test.getShareholderVotingResults(proposalIdxB1); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ((int)results.optionCount, 0); - EXPECT_EQ(results.scalarVotingResult, 345381); - EXPECT_EQ(results.totalVotesCasted, 654); - - // cast votes in B2 - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB2, proposalB2, { {0, 10}, {1, 20}, {2, 30}, {3, 40} })); - checkVoteCounts(test.getShareholderVotes(proposalIdxB2, TESTEXA_CONTRACT_ID), { {0, 10}, {1, 20}, {2, 30}, {3, 40} }); - EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxB2, proposalB2, { {0, 20}, {1, 30}, {2, 40}, {3, 50}, {4, 3} })); - EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxB2, proposalB2, { {0, 5}, {1, 10}, {2, 15}, {3, 20}, {4, 2} })); - EXPECT_TRUE(test.setShareholderVotes(USER4, proposalIdxB2, proposalB2, { {0, 25}, {1, 20}, {2, 15}, {3, 10} })); - results = test.getShareholderVotingResults(proposalIdxB2); - EXPECT_EQ(results.totalVotesAuthorized, 676); - EXPECT_EQ(results.optionVoteCount.get(0), 60); - EXPECT_EQ(results.optionVoteCount.get(1), 80); - EXPECT_EQ(results.optionVoteCount.get(2), 100); - EXPECT_EQ(results.optionVoteCount.get(3), 120); - EXPECT_EQ(results.optionVoteCount.get(4), 5); - EXPECT_EQ(results.getAcceptedOption(), -1); - EXPECT_EQ(results.totalVotesCasted, 365); - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB2, proposalB2, { {0, 45}, {1, 50}, {2, 55}, {3, 50}, {4, 5} })); - results = test.getShareholderVotingResults(proposalIdxB2); - EXPECT_EQ(results.optionVoteCount.get(0), 95); - EXPECT_EQ(results.optionVoteCount.get(1), 110); - EXPECT_EQ(results.optionVoteCount.get(2), 125); - EXPECT_EQ(results.optionVoteCount.get(3), 130); - EXPECT_EQ(results.optionVoteCount.get(4), 10); - EXPECT_EQ(results.getAcceptedOption(), -1); - EXPECT_EQ(results.totalVotesCasted, 470); - EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB2, proposalB2, { {0, 5}, {1, 5}, {2, 5}, {3, 240} })); - results = test.getShareholderVotingResults(proposalIdxB2); - EXPECT_EQ(results.optionVoteCount.get(0), 55); - EXPECT_EQ(results.optionVoteCount.get(1), 65); - EXPECT_EQ(results.optionVoteCount.get(2), 75); - EXPECT_EQ(results.optionVoteCount.get(3), 320); - EXPECT_EQ(results.optionVoteCount.get(4), 5); - EXPECT_EQ(results.getAcceptedOption(), 3); - EXPECT_EQ(results.totalVotesCasted, 520); - - // test proposal listing function in TESTEXA: 1 inactive by TESTEXB, 2 active by USER2/USER1 - proposalIndices = test.getShareholderProposalIndices(false); - EXPECT_TRUE(proposalIndices.size() == 1 && proposalIndices[0] == proposalIdx2); - proposalIndices = test.getShareholderProposalIndices(true); - EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdxA2 && proposalIndices[1] == proposalIdxA1); - - // test proposal listing function in TESTEXB: 0 inactive, 2 active by USER1/TESTEXA - proposalIndices = test.getShareholderProposalIndices(false); - EXPECT_TRUE(proposalIndices.size() == 0); - proposalIndices = test.getShareholderProposalIndices(true); - EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdxB1 && proposalIndices[1] == proposalIdxB2); - - // test that variables are set correctly after epoch switch - test.getStateTestExampleA()->checkVariablesSetByProposal(1, 2, 3); - test.getStateTestExampleB()->checkVariablesSetByProposal(0, 0, 0); - test.endEpoch(); - ++system.epoch; - test.getStateTestExampleA()->checkVariablesSetByProposal(13, 1337, 42); - test.getStateTestExampleB()->checkVariablesSetByProposal(345381, 10000, 0); - - // test proposal listing function in TESTEXA: 3 inactive by TESTEXB/USER2/USER1, 0 active - EXPECT_TRUE(test.getShareholderProposalIndices(false).size() == 3); - EXPECT_TRUE(test.getShareholderProposalIndices(true).size() == 0); - - // test proposal listing function in TESTEXB: 2 inactive by USER1/TESTEXA, 0 active - EXPECT_TRUE(test.getShareholderProposalIndices(false).size() == 2); - EXPECT_TRUE(test.getShareholderProposalIndices(true).size() == 0); -} - -TEST(ContractTestEx, InterContractCallInsufficientFees) -{ - ContractTestingTestEx test; - increaseEnergy(USER1, 1000000); - - // First verify call works normally (TestExampleA has fees from constructor) - auto output1 = test.testInterContractCallError(); - EXPECT_EQ(output1.errorCode, QPI::NoCallError); - EXPECT_EQ(output1.callSucceeded, 1); - - // Save original fee reserve - long long originalFeeReserve = getContractFeeReserve(TESTEXA_CONTRACT_INDEX); - - // Drain TestExampleA's fee reserve - setContractFeeReserve(TESTEXA_CONTRACT_INDEX, 0); - - // Verify fee reserve is now 0 - EXPECT_EQ(getContractFeeReserve(TESTEXA_CONTRACT_INDEX), 0); - - // Try the call again - should fail with insufficient fees - auto output2 = test.testInterContractCallError(); - EXPECT_EQ(output2.errorCode, QPI::CallErrorInsufficientFees); - EXPECT_EQ(output2.callSucceeded, 0); - - // Restore fee reserve for other tests - setContractFeeReserve(TESTEXA_CONTRACT_INDEX, originalFeeReserve); -} - -TEST(ContractTestEx, SystemCallbacksWithNegativeFeeReserve) -{ - ContractTestingTestEx test; - - // Set TESTEXC fee reserve to negative value - setContractFeeReserve(TESTEXC_CONTRACT_INDEX, -1000); - EXPECT_EQ(getContractFeeReserve(TESTEXC_CONTRACT_INDEX), -1000); - - const auto initialIncomingC = test.getIncomingTransferAmounts(); - const sint64 initialBalanceC = getBalance(TESTEXC_CONTRACT_ID); - - // Give TESTEXB balance to make the transfer - increaseEnergy(TESTEXB_CONTRACT_ID, 10000); - increaseEnergy(USER1, 10000); - const sint64 transferAmount = 5000; - EXPECT_TRUE(test.qpiTransfer(TESTEXC_CONTRACT_ID, transferAmount, 1000, USER1)); - - // Verify callback executed and modified state - const auto afterIncomingC = test.getIncomingTransferAmounts(); - EXPECT_EQ(afterIncomingC.qpiTransferAmount, initialIncomingC.qpiTransferAmount + transferAmount); - EXPECT_EQ(getBalance(TESTEXC_CONTRACT_ID), initialBalanceC + transferAmount); - - // Verify TESTEXB not in error state - EXPECT_EQ(contractError[TESTEXB_CONTRACT_INDEX], NoContractError); - - // Verify TESTEXC fee reserve is still negative - EXPECT_LT(getContractFeeReserve(TESTEXC_CONTRACT_INDEX), 0); -} - -TEST(ContractTestEx, OracleQuery) -{ - ContractTestingTestEx test; - system.epoch = 200; - system.tick = 123456783; - - //------------------------------------------------------------------------- - // Test qpi.queryOracle() and generating message to oracle machine node - increaseEnergy(USER1, 100000000); - increaseEnergy(TESTEXC_CONTRACT_ID, 100000000); - - const id currencyBtc(Ch::B, Ch::T, Ch::C, 0, 0); - const id currencyUsd(Ch::U, Ch::S, Ch::D, 0, 0); - - uint64 expectedOracleQueryId = getContractOracleQueryId(system.tick, 0); - OI::Price::OracleQuery query = { NULL_ID, DateAndTime(2026, 1, 1), currencyBtc, currencyUsd}; - EXPECT_EQ(test.queryPriceOracle(USER1, 10, query), expectedOracleQueryId); - checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 10, query); - - expectedOracleQueryId = getContractOracleQueryId(system.tick, 1); - query.oracle = OI::Price::getCoingeckoOracleId(); - query.timestamp.addDays(20); - query.currency1 = currencyUsd; - query.currency2 = NULL_ID; - EXPECT_EQ(test.queryPriceOracle(USER1, 42, query), expectedOracleQueryId); - checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 42, query); - - expectedOracleQueryId = getContractOracleQueryId(system.tick, 2); - test.endTick(); - OI::Mock::OracleQuery mockQuery{ system.tick }; - ++system.tick; - checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 20000, mockQuery); - - expectedOracleQueryId = getContractOracleQueryId(system.tick, 0); - query.oracle = OI::Price::getMockOracleId(); - query.timestamp.addMillisec(123456); - query.currency1 = id(1, 23456, 7890, 42); - query.currency2 = currencyBtc; - EXPECT_EQ(test.queryPriceOracle(USER1, 13, query), expectedOracleQueryId); - checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 13, query); - - //------------------------------------------------------------------------- - // Test processing of oracle machine node reply message - struct - { - OracleMachineReply metadata; - OI::Price::OracleReply data; - } priceOracleMachineReply; - - priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; - priceOracleMachineReply.metadata.oracleQueryId = expectedOracleQueryId; - priceOracleMachineReply.data.numerator = 1234; - priceOracleMachineReply.data.denominator = 1; - - oracleEngine.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); -} diff --git a/test/contract_testing.h b/test/contract_testing.h deleted file mode 100644 index dec34b1f7..000000000 --- a/test/contract_testing.h +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once - -// Include this first, to ensure "logging/logging.h" isn't included before the custom LOG_BUFFER_SIZE has been defined -#include "logging_test.h" - -#include "gtest/gtest.h" - -// workaround for name clash with stdlib -#define system qubicSystemStruct - -// make test example contracts available in all compile units -#define INCLUDE_CONTRACT_TEST_EXAMPLES - -#include "contract_core/contract_def.h" -#include "contract_core/contract_exec.h" - -#include "contract_core/qpi_spectrum_impl.h" -#include "contract_core/qpi_asset_impl.h" -#include "contract_core/qpi_system_impl.h" -#include "contract_core/qpi_ticking_impl.h" -#include "contract_core/qpi_ipo_impl.h" -#include "contract_core/qpi_mining_impl.h" -#include "contract_core/qpi_oracle_impl.h" - -#include "test_util.h" - - -class ContractTesting : public LoggingTest -{ -public: - ContractTesting() - { - -#ifdef __AVX512F__ - initAVX512FourQConstants(); -#endif - commonBuffers.init(1); - initContractExec(); - initSpecialEntities(); - - contractStates[0] = (unsigned char*)malloc(contractDescriptions[0].stateSize); - setMem(contractStates[0], contractDescriptions[0].stateSize, 0); - } - - ~ContractTesting() - { - deinitSpecialEntities(); - deinitAssets(); - deinitSpectrum(); - commonBuffers.deinit(); - deinitContractExec(); - for (unsigned int i = 0; i < contractCount; ++i) - { - if (contractStates[i]) - { - free(contractStates[i]); - contractStates[i] = nullptr; - } - } - } - - void initEmptySpectrum() - { - initSpectrum(); - memset(spectrum, 0, spectrumSizeInBytes); - updateSpectrumInfo(); - } - - void initEmptyUniverse() - { - initAssets(); - memset(assets, 0, universeSizeInBytes); - as.indexLists.reset(); - } - - template - unsigned int callFunction(unsigned int contractIndex, unsigned short functionInputType, const InputType& input, OutputType& output, bool checkInputSize = true, bool expectSuccess = true) const - { - EXPECT_LT(contractIndex, contractCount); - EXPECT_NE(contractStates[contractIndex], nullptr); - QpiContextUserFunctionCall qpiContext(contractIndex); - if (checkInputSize) - { - unsigned short expectedInputSize = contractUserFunctionInputSizes[contractIndex][functionInputType]; - EXPECT_EQ((int)expectedInputSize, sizeof(input)); - } - unsigned int errorCode = qpiContext.call(functionInputType, &input, sizeof(input)); - EXPECT_EQ((int)qpiContext.outputSize, sizeof(output)); - if (expectSuccess) - { - EXPECT_EQ(errorCode, 0); - } - copyMem(&output, qpiContext.outputBuffer, sizeof(output)); - qpiContext.freeBuffer(); - return errorCode; - } - - template - bool invokeUserProcedure( - unsigned int contractIndex, unsigned short procedureInputType, const InputType& input, OutputType& output, - const id& user, sint64 amount, - bool checkInputSize = true, bool expectSuccess = true) - { - // check inputs and init output - EXPECT_LT(contractIndex, contractCount); - EXPECT_NE(contractStates[contractIndex], nullptr); - if (checkInputSize) - { - unsigned short expectedInputSize = contractUserProcedureInputSizes[contractIndex][procedureInputType]; - EXPECT_EQ((int)expectedInputSize, sizeof(input)); - } - setMemory(output, 0); - - // transfer amount (fee / invocation reward) - int userSpectrumIndex = spectrumIndex(user); - if (userSpectrumIndex < 0 || !decreaseEnergy(userSpectrumIndex, amount)) - return false; - increaseEnergy(id(contractIndex, 0, 0, 0), amount); - - // run callback for incoming transfer of amount / fee / invocation reward - if (amount > 0 && contractSystemProcedures[contractIndex][POST_INCOMING_TRANSFER]) - { - QpiContextSystemProcedureCall qpiContext(contractIndex, POST_INCOMING_TRANSFER); - QPI::PostIncomingTransfer_input input{ user, amount, QPI::TransferType::procedureTransaction }; - qpiContext.call(input); - } - - // run user procedure - QpiContextUserProcedureCall qpiContext(contractIndex, user, amount); - qpiContext.call(procedureInputType, &input, sizeof(input)); - - // check results, copy output and cleanup - EXPECT_EQ((int)qpiContext.outputSize, sizeof(output)); - if (expectSuccess) - { - EXPECT_EQ(contractError[contractIndex], 0); - } - copyMem(&output, qpiContext.outputBuffer, sizeof(output)); - qpiContext.freeBuffer(); - return true; - } - - void callSystemProcedure(unsigned int contractIndex, SystemProcedureID sysProcId, bool expectSuccess = true) - { - EXPECT_LT(contractIndex, contractCount); - EXPECT_NE(contractStates[contractIndex], nullptr); - QpiContextSystemProcedureCall qpiContext(contractIndex, sysProcId); - qpiContext.call(); - if (expectSuccess) - { - EXPECT_EQ(contractError[contractIndex], 0); - } - } -}; - -#define INIT_CONTRACT(contractName) { \ - constexpr unsigned int contractIndex = contractName##_CONTRACT_INDEX; \ - EXPECT_LT(contractIndex, contractCount); \ - const unsigned long long stateSize = contractDescriptions[contractIndex].stateSize; \ - EXPECT_GE(stateSize, max(sizeof(contractName), sizeof(IPO))); \ - contractStates[contractIndex] = (unsigned char*)malloc(stateSize); \ - setMem(contractStates[contractIndex], stateSize, 0); \ - REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(contractName); \ - setContractFeeReserve(contractIndex, 10000000); \ -} - -static inline long long getBalance(const id& pubKey) -{ - int index = spectrumIndex(pubKey); - if (index < 0) - return 0; - long long balance = energy(index); - EXPECT_GE(balance, 0ll); - return balance; -} - -// Update time returned by QPI functions based on utcTime, which can be set to current time with updateTime(). -static inline void updateQpiTime() -{ - etalonTick.millisecond = utcTime.Nanosecond / 1000000; - etalonTick.second = utcTime.Second; - etalonTick.minute = utcTime.Minute; - etalonTick.hour = utcTime.Hour; - etalonTick.day = utcTime.Day; - etalonTick.month = utcTime.Month; - etalonTick.year = utcTime.Year - 2000; -} - -// Check that the contract execution system state is clean (before / after running contracts). -static inline void checkContractExecCleanup() -{ - for (unsigned int i = 0; i < contractCount; ++i) - { - EXPECT_EQ(contractStateLock[i].getCurrentReaderLockCount(), 0); - } - - for (unsigned int i = 0; i < NUMBER_OF_CONTRACT_EXECUTION_BUFFERS; ++i) - { - EXPECT_EQ(contractLocalsStack[i].size(), 0); - EXPECT_EQ(contractLocalsStackLock[i], 0); - } - EXPECT_EQ(contractLocalsStackLockWaitingCount, 0); - EXPECT_EQ(contractCallbacksRunning, NoContractCallback); -} - -// Issue contract shares and transfer ownership/possession of all shares to one entity -static inline void issueContractShares(unsigned int contractIndex, std::vector>& initialOwnerShares, bool warnOnTooFewShares = true) -{ - int issuanceIndex, ownershipIndex, possessionIndex, dstOwnershipIndex, dstPossessionIndex; - EXPECT_EQ(issueAsset(m256i::zero(), (char*)contractDescriptions[contractIndex].assetName, 0, CONTRACT_ASSET_UNIT_OF_MEASUREMENT, NUMBER_OF_COMPUTORS, QX_CONTRACT_INDEX, &issuanceIndex, &ownershipIndex, &possessionIndex), NUMBER_OF_COMPUTORS); - - int totalShareCount = 0; - for (const auto& ownerShareCountPair : initialOwnerShares) - totalShareCount += ownerShareCountPair.second; - EXPECT_LE(totalShareCount, NUMBER_OF_COMPUTORS); - if (totalShareCount < NUMBER_OF_COMPUTORS) - { - if (warnOnTooFewShares) - std::cout << "Warning: issueContractShares() called with " << NUMBER_OF_COMPUTORS - totalShareCount << " less then expected shares, adding remaining shares to first owner." << std::endl; - initialOwnerShares[0].second += NUMBER_OF_COMPUTORS - totalShareCount; - } - - for (const auto& ownerShareCountPair : initialOwnerShares) - { - EXPECT_TRUE(transferShareOwnershipAndPossession(ownershipIndex, possessionIndex, ownerShareCountPair.first, ownerShareCountPair.second, &dstOwnershipIndex, &dstPossessionIndex, true)); - } - EXPECT_EQ(numberOfShares({ m256i::zero(), *(uint64*)contractDescriptions[contractIndex].assetName }), NUMBER_OF_COMPUTORS); -} diff --git a/test/custom_mining.cpp b/test/custom_mining.cpp deleted file mode 100644 index 9a68f8dcf..000000000 --- a/test/custom_mining.cpp +++ /dev/null @@ -1,279 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#ifndef SOLUTION_SECURITY_DEPOSIT -#define SOLUTION_SECURITY_DEPOSIT 1000000 -#endif - -#ifndef MAX_NUMBER_OF_TICKS_PER_EPOCH -#define MAX_NUMBER_OF_TICKS_PER_EPOCH 600000 -#endif - -#ifndef MAX_NUMBER_OF_PROCESSORS -#define MAX_NUMBER_OF_PROCESSORS 4 -#endif - -#include "src/mining/mining.h" - -TEST(CustomMining, TaskStorageGeneral) -{ - constexpr unsigned long long NUMBER_OF_TASKS = 100; - CustomMiningTaskV2Storage storage; - - storage.init(); - - for (unsigned long long i = 0; i < NUMBER_OF_TASKS; i++) - { - CustomMiningTaskV2 task; - task.taskIndex = NUMBER_OF_TASKS - i; - - storage.addData(&task); - } - - // Expect the task are sort in ascending order - for (unsigned long long i = 0; i < NUMBER_OF_TASKS - 1; i++) - { - CustomMiningTaskV2* task0 = storage.getDataByIndex(i); - CustomMiningTaskV2* task1 = storage.getDataByIndex(i + 1); - EXPECT_LT(task0->taskIndex, task1->taskIndex); - } - EXPECT_EQ(storage.getCount(), NUMBER_OF_TASKS); - - storage.deinit(); -} - -TEST(CustomMining, TaskStorageDuplicatedItems) -{ - constexpr unsigned long long NUMBER_OF_TASKS = 100; - constexpr unsigned long long DUPCATED_TASKS = 10; - CustomMiningTaskV2Storage storage; - - storage.init(); - - // For DUPCATED_TASKS will only recorded 1 task - for (unsigned long long i = 0; i < DUPCATED_TASKS; i++) - { - CustomMiningTaskV2 task; - task.taskIndex = 1; - - storage.addData(&task); - } - - for (unsigned long long i = DUPCATED_TASKS; i < NUMBER_OF_TASKS; i++) - { - CustomMiningTaskV2 task; - task.taskIndex = i; - - storage.addData(&task); - } - - // Expect the task are not duplicated - EXPECT_EQ(storage.getCount(), NUMBER_OF_TASKS - DUPCATED_TASKS + 1); - - storage.deinit(); -} - -TEST(CustomMining, TaskStorageExistedItem) -{ - constexpr unsigned long long NUMBER_OF_TASKS = 100; - constexpr unsigned long long DUPCATED_TASKS = 10; - CustomMiningTaskV2Storage storage; - - storage.init(); - - for (unsigned long long i = 1; i < NUMBER_OF_TASKS + 1 ; i++) - { - CustomMiningTaskV2 task; - task.taskIndex = i; - storage.addData(&task); - } - - // Test an existed task - CustomMiningTaskV2 task; - task.taskIndex = NUMBER_OF_TASKS - 10; - storage.addData(&task); - - EXPECT_EQ(storage.dataExisted(&task), true); - unsigned long long idx = storage.lookForTaskGE(task.taskIndex); - EXPECT_EQ(storage.getDataByIndex(idx) != NULL, true); - EXPECT_EQ(storage.getDataByIndex(idx)->taskIndex, task.taskIndex); - EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); - - // Test a non-existed task whose the taskIndex is greater than the last task - task.taskIndex = NUMBER_OF_TASKS + 10; - EXPECT_EQ(storage.dataExisted(&task), false); - idx = storage.lookForTaskGE(task.taskIndex); - EXPECT_EQ(idx, CUSTOM_MINING_INVALID_INDEX); - - // Test a non-existed task whose the taskIndex is lower than the last task - task.taskIndex = 0; - EXPECT_EQ(storage.dataExisted(&task), false); - idx = storage.lookForTaskGE(task.taskIndex); - EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); - - - storage.deinit(); -} - -TEST(CustomMining, TaskStorageOverflow) -{ - constexpr unsigned long long NUMBER_OF_TASKS = CUSTOM_MINING_TASK_STORAGE_COUNT; - CustomMiningTaskV2Storage storage; - - storage.init(); - - for (unsigned long long i = 0; i < NUMBER_OF_TASKS; i++) - { - CustomMiningTaskV2 task; - task.taskIndex = i; - storage.addData(&task); - } - - // Overflow. Add one more and get error status - CustomMiningTaskV2 task; - task.taskIndex = NUMBER_OF_TASKS + 1; - EXPECT_NE(storage.addData(&task), 0); - - storage.deinit(); -} - -TEST(CustomMining, SolutionStorageGeneral) -{ - constexpr unsigned long long NUMBER_OF_TASKS = 100; - CustomMiningSolutionStorage storage; - - storage.init(); - - for (unsigned long long i = 0; i < NUMBER_OF_TASKS; i++) - { - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = NUMBER_OF_TASKS - i; - - storage.addData(&entry); - } - - // Expect the task are sorted in ascending order - for (unsigned long long i = 0; i < NUMBER_OF_TASKS - 1; i++) - { - CustomMiningSolutionStorageEntry* entry0 = storage.getDataByIndex(i); - CustomMiningSolutionStorageEntry* entry1 = storage.getDataByIndex(i + 1); - EXPECT_LT(entry0->taskIndex, entry1->taskIndex); - } - EXPECT_EQ(storage.getCount(), NUMBER_OF_TASKS); - - storage.deinit(); -} - -TEST(CustomMining, SolutionStorageDuplicatedItems) -{ - constexpr unsigned long long NUMBER_OF_SOLS = 100; - constexpr unsigned long long DUPLICATED_SOLS = 10; - CustomMiningSolutionStorage storage; - - storage.init(); - - // For DUPCATED_TASKS will only recorded many - for (unsigned long long i = 0; i < DUPLICATED_SOLS; i++) - { - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = 1; - entry.nonce = i; - - storage.addData(&entry); - } - - for (unsigned long long i = DUPLICATED_SOLS; i < NUMBER_OF_SOLS; i++) - { - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = i; - entry.nonce = i; - - storage.addData(&entry); - } - - // Expect all elements are added - EXPECT_EQ(storage.getCount(), NUMBER_OF_SOLS); - - // Data is still in ascending order - int duplicatedDataCount = 0; - for (unsigned long long i = 0; i < NUMBER_OF_SOLS - 1; i++) - { - CustomMiningSolutionStorageEntry* entry0 = storage.getDataByIndex(i); - CustomMiningSolutionStorageEntry* entry1 = storage.getDataByIndex(i + 1); - EXPECT_LE(entry0->taskIndex, entry1->taskIndex); - if (entry0->taskIndex == entry1->taskIndex) - { - duplicatedDataCount++; - } - } - - storage.deinit(); -} - -TEST(CustomMining, SolutionStorageExistedItem) -{ - constexpr unsigned long long NUMBER_OF_SOLS = 100; - constexpr unsigned long long DUPCATED_SOLS = 10; - CustomMiningSolutionStorage storage; - - storage.init(); - - for (unsigned long long i = 1; i < NUMBER_OF_SOLS + 1; i++) - { - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = i; - entry.nonce = i; - - storage.addData(&entry); - } - - // Test an existed task - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = NUMBER_OF_SOLS - 10; - storage.addData(&entry); - - EXPECT_EQ(storage.dataExisted(&entry), true); - unsigned long long idx = storage.lookForTaskGE(entry.taskIndex); - EXPECT_EQ(storage.getDataByIndex(idx) != NULL, true); - EXPECT_EQ(storage.getDataByIndex(idx)->taskIndex, entry.taskIndex); - EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); - - // Test a non-existed task whose the taskIndex is greater than the last task - entry.taskIndex = NUMBER_OF_SOLS + 10; - EXPECT_EQ(storage.dataExisted(&entry), false); - idx = storage.lookForTaskGE(entry.taskIndex); - EXPECT_EQ(idx, CUSTOM_MINING_INVALID_INDEX); - - // Test a non-existed task whose the taskIndex is lower than the last task - entry.taskIndex = 0; - EXPECT_EQ(storage.dataExisted(&entry), false); - idx = storage.lookForTaskGE(entry.taskIndex); - EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); - - - storage.deinit(); -} - -TEST(CustomMining, SolutionsStorageOverflow) -{ - constexpr unsigned long long NUMBER_OF_SOLS = CUSTOM_MINING_SOLUTION_STORAGE_COUNT; - CustomMiningSolutionStorage storage; - - storage.init(); - - for (unsigned long long i = 0; i < NUMBER_OF_SOLS; i++) - { - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = i; - entry.nonce = i; - storage.addData(&entry); - } - - // Overflow. Add one more and get error status - CustomMiningSolutionStorageEntry entry; - entry.taskIndex = NUMBER_OF_SOLS + 1; - EXPECT_NE(storage.addData(&entry), CustomMiningSolutionStorage::OK); - - storage.deinit(); -} diff --git a/test/data/custom_revenue.eoe b/test/data/custom_revenue.eoe deleted file mode 100644 index ef2aac9cc1562c9b9f100b1ce3e70c0161e70360..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43264 zcmeI5d0fq3_vk-5=OmeuRH&2;MM%+HLLrh+gd`~?N*YL#Qj`p3N)pmQ2q8o?7*Z4= z2}!0zA%q6Q-FvTf?(2KpYrz4qE`uf6vE>`&+8`?za!?0*(o zAk}q1h7AIF9{DJglk|X3>JRdg21xT^AP)=&`AidJ8A@4{Q_#+N2(1$hO>`;P6)P+DTU4*IXadF7zLW^BKNJQwwzD4$}yFEJ0QIF110 z3PC?R(S9I~8;bF7MY}qbH_%TU_Pd8X3FDiOaWtYli~hb~9Q#lzVY@Dl`w0CVLLQCl zaZeZW@wogN8f$Q#v@lM3}mf$>wV*6pVUyboi!SSn5 z?~eTgv0pjL4(QJo$DfLRLNM=B(XJzMW8`1az5t~Tj^l*$S&Y0Nas|}walE6bJ7E5n zsX?9!2ZEf8^){s+=th{Y2=rH_0z44=O9|^u9eBC{NIA^IT;#KHeR|;d2hhJV&g=FF z=;wvp3g@4V^Vh++hGX7qa6SXkzc=z%XrI~#{FR~qR2)|m>q-^l_r|;#(P;W)7vXCtoH7_>hk%mezjLjT(^es64dLi=U7?ndZ06YZTb&%v0-JglE- z7|&eH{{$tN{{{5B2-~wTKdaDxJ?5<({ij(#zaVpv>S#CB3iL>>@*7O-eJAmLBBOPel^zH3AEpg_C{Er^_a&fagM}s z24lRI7@s1_McA%~?H)L;FV3qC^}W#{UqX7uPY4t7D+f%J7`+jqAP*hak5UukF~j~+n3r%IXA<69#^bz_(e5Dn z)k7J8`T2(PS%H3zpuB+NtU>$js5hcNVZ14*&qlixv{OU>TQDvj)>k#!aoBG=>XsPy zZM0v9btQ@MJjHrx#rk?I92eJXI?h7^?XP1TJJ4Sdw)Y5%xewI&1P`mM(P`_XR!?#BuoF9r2_wD(7U zsn|~%`+dQAxuJbu97h88Uh}Zs3wb_{a~#KC zhdc)7dtA5=2k->}B@8;#>=<2t^>_*!xPz0hx899I?fe4M|~-V6J6#c{r2UQ)2%OKe|{ zdi)aUM0; ze=zzLuK#4LD>>vgD23}Bgn4VmardD8Ow5A~`j5eO;XWw9@!fG=vlQVx6u#dZV*iF7 zz{{{sPvH3?FArRR_odHxzO=`D+u-v{`1h&zIKK*vcZ{+EbFl4lcP3q}yE1v9H}EBS zz_&~RK1U6B#1mO&H()aGSoE`22KeKNz_Vf@z)RZQnEekc;OhN>FRqqhd@b^Gw_u`f zX~4fC&&rcycGVk!{~Ql~OrJn}Q9-~>EPy8(0`KVs{K#0~p9>+rMmOLYXdk^1xQ8R~ z4QTJQ7`W36;1A8gkNiUf8dkQK7a?jP969h%0z*qGK?$#T4@<-s7X#cnu@DU$?JIRCn_MX6ZzXQIu7x1}?!1Ee_ zALC*Zg5 z03Y88_$%Zdw}1z91|B8__Wf@I?}PT50^ncJ{sP*YNCMxE+*1ntsGg% z>lFe&C;f1HoZ}`A#+khKh25yY@Cau83^MH3j`&8uLbAT74y-qXOS6>8v zqaFC4`VDx+dEkq9z;}HCzT+J5fgEuCCg7fDfS>*W^RfL1+zt&A<2T^9YJtBx4qUGZ_)X;fkxxN;`Bz}S1$oCWU_bi>@VO_z zPsV59*3W@E9s|A<`TiQ<4e7uSeFFQYr@#x20zdH{xM3A=>%+i{J^=6k7k z$fq9yd)tp-FZT%S1CSS^y%h3g$iKeBcq+i&X+QY6Qx81v0q{fnfWJnbkM@HO06+8w z>}QpOea}ST59@&I-v_>Y4{#T>H!lO;D*<@V*I>`T4gAI~;0m?CSCs;PfV?l-Un&89 zWGC3qLwnsDz!kRvAN&&h*c1Y<*a2MTIq(%lz}?W^@CERx$Oj=edj{MT{e0RAeu}Dq z@4Nzj3SxmzK|Zno_?|7m&pZbE^nBpF7~s4|z;iDFx8Df7!&Bhhkbm9)yaDYMbHKiB zy{P@33&5q(UK__fbPo7Vw2yxX{=a7dPmKUSF3A1SzA6;>mj_^9cM9xpg#&j&e(X5# zBD9~3el{EfekK^~2UUQd4o863tOmXv{U{s-ZW#nzxf1Npp#5^>g~+EI2K!wr!M?m4 z?4KS4-ntyP$z9+(kf--H_>cL( z8w-Gcj0T?J3Vh>b;JIk8H4pd{v|o+9{T$%KbHV=X2JoXd1NhoYz^9}C#mIv$053&9 zz3jkV8~sc}KF$_+E!w|b5B?>Q`y?^Xte>}!>c_Q$_bHLw6g8gYL;JdSc zcSo)`0XXk0@Kfu+-Vgb;OyDQOfoG2e`+!rxm#qb!X9;}p3E#{$d@ja$7Wq7V;Kmr|ks$CN zs0Z8(`MZ_CC+PrRbsWZhfcBb0fR`cP5(xGek=tVYRcQZZ5ZG5^oF@aozAtk3bnxHK z7x+C5u0=cs%*w5+%{=1?dH{@Bpfoox$?&zlr^2`IG{`V_@{mew* zS?=J!y*zO3J;1*$1a6AlZ#VF8SKv!!!QN{x@LD(Eo?U@Ep?_uMwOxQmCV+hg`YA%* zK39z&=!o!KO6;| zKOT76SK#Z`17AN1_`}b@vm${HHv|6S6Yz`Sz!bf_^y7v6Wi9ym z>IC*7YG7~w0(c)s;PHKc-$foW3wWq1@TX6}ez5~^MP=Z(9s}322d=3EJg)-%PY3Sb z6S(s|;BTh^KdcB``!?_i=qCbsSqbphQ^8)K0QU05zzdN(_Wd>9}0lyn1g+O2jD@MfzLJrevb#dYcBASM!uc)JqdCr$(3Fa-FJ8IsJ;k)y!R4+K8f8h8crtOkf9bvW?D>0rOk z5xDLl(6f_4TI>b6$R6Y*yoXKbD#6A}jA_SYSs+N>c#z67LC#$Tva2J=!EPY?p}lVi z=<{4b8lrvO7|?aMfHXz>?+&0(M``a3`hx`^w}gSL3;^kbfYg}=vL54pUmbM^TQfO*_|2=v~V=O&CVz!$h0`X847y6--a zfw)e4F|Ic3=eHW#BO*b1ZUWhid5p(-dh7ze6!YMV+ywJ>4f7U=`AGEv`?qUA_VfT* zjP1LZfPOUyqzcA43*#Q|2|O76jK=kISp>Wm<8{C~s9p=)5A*WF0Q6*QkdfO!PFW7} z0?tE#^X!Z3Q-$mP7VBp(`b|UoQXFqS#&OvS{Fz{#g>DCZ0QMUj2f7E2%ijt58(hyQ ztb^8xz+*x|I$-@D#JtwyI{BMG`z+j-I#|bwn6GKLKknf^*226x?S+0#;UIh9I6W|4 zXUuc%C}`KkdJ*oAWL&R#=vN!#m^KCMmtftU!2GFVUH6NHcG+N%8*%?M;e2l3zRrn< z_Kz6fRjhl{*}$JH1i1n0XN5cHCYV`>tasVD(C&=u8H@2)Vtzfb|3qBB&3Ha# zV?D`Z9hG3cOg036I}$;zzbu+WX_W?Zdn+#rco5h4$wzAa`Ki-(a4!a9`cR zc>J*6Q`~=>Fn)LJw?hN`Ove2`e*);&ux?9nJV(s;aC>M!gY_DQ^;3oO>^l?MM`3&; zalGeP$L+DcY;gSH824+;Zv&3g2jkAbbCSdTdJ^;MfVw>PKaKG?;dnoBJr`g+FR>ny zW*rx9A^{Ok2>a80@rsHju(#SWCyILF<6gT zxIa7(Ks;4A-y_>VzlwD&iSb(Dyj9WvXPozv(O~}x>&q0!)5H2Qz;#UU1-sF^LC)F= za?4bZE3v)W8uWVXciS2C8MtnDa6KMH19!*vWQ->+4ETGD^V0~>@8Z7yJQ?(vINmGF zdjLP^Ol5m z7cu`gaX(KB1wXQwC*wfSm*YAp>4Tmz8qQH8+((ncfxBT`MwpLrn1`v!(B2#4h=~P# zNDRm}tYdGiTc_i|7vXwd!F@4%7jSp1y9TV22s}^9aQt}8ixaMA{#fWY4d)+>^L~%< z8RCA^!*jV7>w6#eKZX0%758I|5%{wX0eJ`G^uoFx?g)Gi)*%n~O&p#Fu~?UO*xwx2 z>ov~HWj^$;#rYh^b&yyA{1n#n_r;)}#Co%iY=jHd|KcOL2zo1uL%&QCf4^d$6~h<>avKdCsM zzPL{xV!b+ITzJzbctF2m%yTK`Wz!+xb8$X(c<=a#dFYC9O~H6Gc7na^P>=^P zp6QXG%j0|{hk!m2^EDFpsh1t_nk^tb@Ep*<^&Y+fxB|xe2G>>pAn>PnuI^>Dc>eFl{A|E@1ihfY zZU)Fe99M5M=o*-}8MvS1aNi!lyjtM;S)2v?C|r*=%&!vG^&E_6I<8A1=DT(p^sC4D zs9_yW!u%Uyy_eg9-6E_D;rm4_uDcoLeU%-YqtaN9G#mOGMuEJ7^XrTIr(rbkTi9NK`LCP@{3x#Db{sbz z&-Dp-ez#)0QCN?=(5@@4&qd6O7uMHH^e=&RF&6956`$`(xNfSOVEnvfkkfGf4=h2K zoCz`(_pvADRro$ph<0W;uS{%zkNJ+?2L0u+E_&hmYGHl7!2M{s5$qIjeGcF`A^dxX z@O^U>K2N)Zf&EDICo>-OBRIbfn5V7wz#pNUgzIF5@jk$N!dA4ugL)$7r3%lf?`Yo> z&+mT7!?1qj(Qh*5X%E)Pk2n}#SO<#OzZutQF|Om6tzc(|>v#{(nKrDm+|$q=kNFvb z^FN2}KDcgXxZXNge~<9|@x(eB9|V5xVtm5qyCjaQhI!IJ>An!`qjBFhqQ2MyxFyzE z0@leE%u5uW6NZPt?mXt@>uAu2;=VXA4s`XkAdB%{cs~Sm;d9vv=Vyg}^|GLS&@_10J#~@YZsj7J>360v0e1 z82>Lkuj3-XUUz$e1rw%m&dLPrS_|yc!g4KzHsdcs!u${yG-@& z6DHrIQdRDL1La>{FML3C*{&%WR4&MOlA(U`Vm$iM_NFo?HmK(On>!{3M-=!DjIy)}tQd#zVoD`L3H3CLa z+0;Y!2(?o>e`+O_&dUbGP`<%#>3gbY7CvX~HWdN8C?9%q$~MYlO2^1i-FDu_$5i+6 zEp(>3-#f2ERJT*Vrb%_5p@nm)G+!UPmh$%{7Bi{d<%)k9)laPnIY6cJz3r?XHc08@ zQ@w23=@V2&yt}cO+ArKaYYCN#UCY>d-Lq(Ad3Jj@Y!kIhEDpF%S>mRc`4=oYX>r3c}JS9m;R+Q$0)B+ahpZ`NF|hNQCYW3xs~pm z?Cg6tXuEWxvnQ3&vikk0OqT0%k=pru>A><|o*HwP+AH!TqA9P8P+|4weP*6AwYQZ> zZl>JTu>X3h$DGoTr*eY-mw3w0DtNKF^(#=&q}*P2tqeqIg+9~R*np64edA}Q!hyE;kMs-i$Ub0mB9^P<^@+Ff}?@(Q8t0>Rf z)A1+e3r$9QQJFvDF59~M|G=}o2j*|YAPx&s* z=9N04RD$_QGdNCp(!Li*sc!DB&*mGr{Q6VMs|*T4Dfc?`U6RV9Y^oQa@#V-B`VU$_#U$ zylnRoU&>`Z7<{DqHpShlRGymd!{XO??8WL+DNl(Lw2DS^cP7?qEfETKVBZ$}_pE>EocgPuP|A_Cz_Ep);lO1ULlAd!5=y`afU5^e_R!s2jP3_EN0_6lm zu2;JsMfFYG(tXrk^0m%*YWGZF6HIx{;xGA>=LcPFr1~iLc$SZtvCUU05BbsSIpxQ$ z2TY|hGpT^>FSioaRn(8w@s z*(ZE^J=LS$E>EITYSemm{?4%Cim4vcQ-;;=QOl82sGdD1Rf+m36|jGp4w$}-OcKBndFE0)K9ecPIj(3^zdQ%kh-#y zahFx^YN)-3!_piok1q`xNbSGx)7Ve_ZCw6}J*PSw-eUDRmVbvm2e{`WSe!=5-}X>D zqpAxwRBHU1-b(%SExwvd^(*!JgQ!f?vSsIWlvDe=RNuzm!OlnXFu``JfAw^eqT?zy z`?I`ToKs=@&^qH9J7?~7eQwA6=v1(Mc%j}ggzB!vrdOzJTX(J-l}V>d1k_Jdmxs@3 z96w5JhEmRD-yTMJuO)IU?lJ9#v2}?q_kBU_%;(%(LhTNp*Bs3Jxv!L__Q9&2?40N@ z?}{wt{>K6?P$_%s-a9I98&B4v(r#(>M=GTRX{`RPMlRH&deX9ytPZE!4rTSXaYF>- zPqgD%y?uKVsY>k(R6_<($$wsShK_sJv7(jgPP*ymXnZx5OIZFrAJ64ce(b1fM=DL9 zw@jt>BLX(E`-Ah&`$g0~+bgjL<=tjA=~F%Q*Lil2xcs${)l1`ryN0y?INP5pR9?Cn zGmOgg%RSh==4EDPG3}?gw3gM?Y_2_9XJ_LxW2l|>-i`;ToPK1y5tT&^uBKG>>o{sQ z9XB)RHG96D;D@vJO0Vl|-^srX@uqefk~*wTJ0w|IQhN*C;q08B_8^5lzg3s+W$SwW zv`HT_Or63dg>8d;X#rpd?HdD-;56T6p08(Tl4{kp9=5J}~z-B}B$9B-4#p0jg&E-s~d z(g`hAFY(+w4LWXo$zfKXm1B##u=atsQ>h#mekp^>S7uGD{w*#`KA^ga@4z%FN7WCE zpn0}CazT^&X)Afl>VC!|Pqu$wJ&x~9xn0gJ7LUxDc3s%M0PIwlzVQc`pomE*!pKKw%kDdH<~W|QoPzYD968?WlwedlUp$L`cF ze|y|5YNwm|y^?Yhh2vVO8`B;|M8 zEScShSIvB?tG4W6`7``syN>yNVtawgjFv+aX!|q;3wFQMxhxq>xw&#uBbAr>KWEP~ zCxcb&-qyTI)0x`0iyHQZwi}IFKA6gL_X8eM`N*J1nzr{n`@Deavc@LnR3BRyd!FjL z2OhBe7$|xzq&)bdc`qvE^gHdM?Gu#3*t{Nj7_;$wg0`1aKA@Wc+kelbDmqa6+V=Wv z-uItrvvcc5%dsqK*K{l6B$djaeA&4+ND#p8V_bC5HOlJ>6(Mg+P1!vrrKbYx_s#Uu z1?u;?lCKPvR+p!r0fT6SBT z>OPvPPpE#^>KmI!ZO&wN9jDUa)E%l2uaSyz_l%SZXKluPVVZKEs^`yI*+Bi@sciP5I+uTUIF+M&S$?G4yR4Yqr`|Pn`$F}~)Wk9> zb!=VTsNIna7+?PU0d=a!j~d6`TN+%4+@y9_9}Ui?GCTbn+c(3xSSxB5wNkD>m5p0f z6sf$OJ6M8BjUjs0R2J$qrBK=6+r0~w{knW*?FR-PGNt;|oBVcEW}1BKNu`5ND66mN z?HA`!U64`2;++<#W=Qo-hcjN%I6}q^V*XcK7Uola{G;g|%3IYtuyHP*U%~oEyf$F> z^-Rr$dep9Y$Dox|HeBa4sPrAv`8e$tc6It*sxLZuhu!13*tBbuUs&lenR1_jw#;5L z|2=CTVD_G^uk}`2X4gD8^&+*?SbY2$l{2^P??a{b+KX&GHhnt8-rsMAooDOOxc7Q4q%ol9AMCIEP zaSN%`e86XQ+nm&m)lH8H(qn0zJC?Sw_ktnLO*5(8;~V%pYvj-NOTQYg3d-eOS8t}WtijHeN^^zciL}3yhaB5yeR(t3x{q6I!~7(s zyR!AEtWs>C_MD38FDm7}sIm98{2hh9R8Kl>vXe@!ZiUHIS|{bPb@x>2Iga`tKIItO z@3O5)P!DR8*m=?6_^V=SZ+t|X#Z~7}%I*tVv9H-W>A96Ke+vpzSpTabuWnKMmr)hQ zRQ4Nsvp@A;v*s2%XL6epMp6E0{%n@7gl2!1?^(OQZKPbzGKoDWB0P=Z9Ctmz{M}Mt zH-h@{+k9v+wQGEsyoO4L?~MUe{(Ln3K9!u}%HCA^RXtIdS)KTlC|FSLIDcm< zmHF1O%bEPv%+~kbi}V*%*WDe=&drii_wTgMlRiGtr{n&deU{aA#H>@E)J}ClF6*zI zX2|kf&M(cNJoZb%2PzfR)_V=Vg~W_m(SI+b;?lUP3W4I}4JeVx%mwvVoLG%%xnk~$f%dN6;{ zKaRE+1(vfs$gV4kpn7nJ5_WEGJN#3D+MCFoV0o7~FdEL8aWT2nu4{)dR_EK@4mZ<0 zd>y04-Z$3Y`?Z3$zvx^yn#vE+E9Te zp}lv+bFuyp8V8EyZh93~;`~fCZyYO*w}uX4|7A7dctYv_c%R7ry`?dm<>knac4Ehs zQ`emMPygn9OW3@g>a~vkC!b!C#{7KJSs^lC*JKrDKO!o8%s=gg@xJ%>Y(E zYdqt{+Ut*t5F2M#)vwm#{HNW%DY7oy;l0d%vy@_o@tI zb=ltKk-4~Wy%$c=6UQH}h!V-quFnc=UtPAT67et0&!42_yoP^kU;L@RkZ(EfC)Q6n z?}^B|3+?~PfA9JEH&6e6=jn^`jS1rR|KGa!f1@r;47Z5A|NLov6c^1Edw=+oy_Wpp zN&j5`{10oz-qYqkS@gg4emn6~tyq6`H7+dvsPmUZ>hjud2eJ3eKh5iRK6CeQcK^8W zvG(7}&pGpZV(p$x$`ZRCOE;9Tdtr~z@c$^zH)h)7|C#^aw%)dQcK?sY z<&F0jyS^38d&SmE^^NDOE`F61v-@mnQ5SZvTe9_qE?xJ`m7eV0=RIQcWU2>EKhN%~ zea30qP<>5%PnPGXU;Eg7EPn1L7H_A=rtIGHxxYQD>)X7?Oy51Ek=;98k0_6#{dX)l z%f`)X;@Ezw=jAZoztErMY56IAb80s%Y9o7JP<@uj#w{^}@zouMF@K|jPOgQ_eQ@#_7H`P9S8P667hPDp?CJw7AF3)#Slr5fzgYW7 zxpk~w7G&o!-CFOH$o*4x#tW98rAbrF>A369G_v`ezN&0SbyL0hBInqY5lL)5Z*O|C zan!>fvHs2G8`=G}qQ@@QzvXa$ww|F^a#`GWQkC_ozn!v0EI&T=yBHr-sLtly=jR4C z&e0D=jNeS%!(>^-eU|6V&lH)RFfT&+rTi#c=XZyWi@eu#{!q-;W393h%Zt$dY~n;# zC%3eDV(aRlWSH3cw=X;*_B@#ua9X6!H=G~D=F!9XB#U1-?(gzfzpJbs-hb`H;u$h{ zuGsyxkM~mSd2pccv)K2A^q5^D^Qs8QXa0Nj3}WN`9*50hb91^bLVm^MhS)fS?dm_L zv;Dq5HJ8oX?P?IqZ|BX)BF`0}{mT9O#PW4XXIWfAyLSg*odi2piL95f-6P6M?73Ie z_Zs69g5f<%Xx}4ji-`YAik#Sa#l`BebuE40!s@m1QI5!Y)_7Q5X^T*C&b|Yq-64^(C*Ii}tkBzJnsq+`* zDQureyTyp`DeL*?zBGZ+Gesi z3O>wc`>M9(WH-u%^GrCB$CvTZe>6UOc5yb>fvb5K zy}`*|z~!&%v$f5G&kgNVo1I$A=Nu)g9-rRN=iZh`&wE+V<6fIRwDLGA$-TT8(YozE zk88drbMnP@9{HiKTKCvIN$zLEF`i@%pIfkK&Zv`Fgbyl~?Rk*T<;K>#4V}p6BIU+C z7LYGvLT}&j?>$k#EnU(@#ff~$(t7d4vMln8c>gNLe#!?0+^5zdzH4&$T+JQ7<3peD zIA?`BqcfxgoTG}8dtn5Ri&D<3JgC6uyp(6=uAV0#Kg5*>-X!s)_xU*C)h{0T?#gcF zvJyU*v~zI4o^E_DSX#k2W+9*Zb*9IF{))tYhPHJ7am4?4)fa|we6I0wUj22_&ydI!sHJ@RZO@&Xsr%;mcjDi4lx64-9+x?^ zi@y3HK36b%^4kOCiM(2xr@e6A$9-n^}i$AxZP+Zytf z&o#xaycoQl&vktD?MstBpEGI~f2L0hkIPzFYI!q|$9*W#*C=);{mvJTw8$gte6T+B zAo=2``)wWPL(9oLR{C{%T}bL+-zGczr+iLkZGmn-@{2`f_S~cm#Gh}Wm5PK8k6YVs z&xQu_1%S&~Z>0oF0ax?Bd6+thFZ0W%#n}o5n1Qt0ceB7(_H0>XLZ; zbKI3a+~jk9ceg#UA-`-jjBecid-!;#ZyMb3{XuOMgc`k=snxZ_n;kW9~_i{r${nk)eRQswSg6n5@6~IIFy` z#82qL+>L#l`CMZ8;XM919#@iL8~v+2k&i>I$M+O)`&`>;7q;>_?O#5hPLVGSKE*x= z{;-bE*#%3Q<&k}&r1ZS+kshS}vp*;#%_8+=Q+4mN7OB568l`=D@;Hs{lUH0nCEzmm z7F$VN;mInWPN%ic8CwW;IZr5ZRgRlxRuR+BUPn${I?%I~HS&GliQ0n?N zkgUJ+pr&>vT?Aa2@tm%i`%*FV~?-pJbLx3b@Mm z8PAS33%H1EeyDD-1gErVnonsEsjE$~?V=u$yo|NDo<@FY-L`4wM~^Z-r=qr2?;+Wj zHE+K9UulxyE`_bp-7}x$N8Tbc_YC2K(~NJBdXpJ&?`@YZ?YYb0VV+YP`CN8*tHZ~3 z0`9K2VoKJ;_T>9yU1P6rWL=&OX?K4*IY*Zs$>Hr1aJurYa}&tA<=>s4*_(WMnxE6U z=_e=2bvpB0-cf>_6I#VN`^Y{@En6IUkDMpjo#S{5!pQ#3xZqVme(~Kkz|UH7m?UTW zMnCo;+5ZX|Z_cEVFT6H2UL3KlTELmN)m03CMb@|FS^mc9d`|Jwi&GbQl3dDXg~prT zc-%x|otU|fWIfLw_6YnU;BN2g+O^hIz`69FG;d%4pEFZB9yxL(S(nqV#{Zfu;NEur z`1$y3a{ie-_7+{=|eRKukO96#{T-NR}m zU#DBF;>o$Jy{z5CGsS#P+U2<6ygU zqoIJ)kR7pcT&#eTnqHl_lhj?^?zO62wFTVIAM-P_-t)O0dwewp~OLFV&+unMUy4D`rPCMl* zS(mIadrZmm(ZA36mNc>tN;G?`DU$eAC409kC3)B1cKLBAiI=Nux5s)ApRG8wAq%R@oWP*xLFnO-XyiJe0xslYZ>6~A8 zKag{9aCGx=B>~qkr}2`*QUMpWV0=hM6&d$*Mn~g=d@e)&$Xlmq0r^hhOw9TZe2%}u z*>M^rpNb4T@;?L9e-NDX}1rZ*yk~Mt|zRmzPy0s zf%{O`luGQ5Omr#TP0oKSkH9;Yg+`)=76RQ@JbxC%ATbL){ z;xFHj8Y3g%tk(L}7LezRUAx4APL%@g`>%paqlXH(2fm+TBgyl)GRRV2k<^95&*X;^ z%gA$o{LXJ~UZj3*4s}0G&gU!hHn$}V5O7XiHhZKt@wxh_#S=VN2soSVeq9wD$a73_ zj>$!GE@y|&I`24Fl8abqE4jIp&nZ3jh)F8uaY-g#0b8!|Imv7Mk{5Hx^VH~9R2=z| z_~)6u9VfPvpamrl+Nw;5h}Rc_>*%@P>zCSRJCEY7;^ zL7q#YX$DT|`Q-WUVPGLe@_$%yroxn3(odu9>34qt7rdr>{)Z4g7ZusPv15UNbJpGH z7D@77BhxAN@EKAs@fYH4$$8?lx^y02K%Ngv73{Z~QGM};qjyPtb&@Do+r;NCuQQd1 zCiRm!U-DtoXaTo2x5Y{>mhAUv&+LQboGMN^W)n`y2dvbq}b-Z{+Rg%m6bSVA_c|JBS z)lN!NYtLD{8@Fn|zJOD%Km1;Crht2VYv67Tat_FIBObgXaX#;>J}m1r$@86B?X%>( zlM1X`P+=g+)fKHz=&wi4^&KBCY`;qK)bnwp>{gYNr!hoII=EB5A>|QE#O=isMW`i`;3X4BX-Tv`L{k1|M9JB z{D+czhhgwj!*j{`Q=-~;2ay@Q)~EIzOZI%gwV3Qr$InXNy!Vj(dps|0EBP`w|AW@uZlq2#B%U}--ofMK zx-E$`n$G8{E}!@mmnOkAj+{EE2lh8?bILnOC}QKzTBsyX$&d?G||s2F7dIj=jp~dfGLP>`uO1_DfA497678 z<`p&hY9#)~_Z9MohLC;LVzjk6iOj!1>rA^WvOcSyMvd1a=SGLmo!(dTIGw2u6N6Tg zdtLDl_p(mpe5`w$BYlL&?X);N^(%S4=6z0DXQ4>m2aac_)sTFgve!>YCv_gt;g`v> znIvwBI|*@Dd7PA|^2&u|y<>MhZZ~)Vx&O4+p4MDS?z4l#J3c4pU2C+()@7uu){tlwD_FX9l!BaBwyA!?Jcy-$v&UiyW%xDZ!)))hP#sIU6gum z+^s9*JR7>Eo%#&2uZ}gWd2PYxuFlQA?@r!Bp3hq;Pyc0voyURFxrce&yxMDlk>t6y z@Z32W1M>dkoY(qzZ)Z}s16;?Q>qMU8SBgHo97y)RU-e$kA|4lHGNjwxR`ML)c)RG- zI`Teps&8tOA$bpUv_E^E+hxvzc?ZscOEC1wL|4fGmqmxj7{E4>Qhhc%9-&30jE>4W>Xlc$HuKIilxYVkn`o} z#uuweU7Xnb_B)A_JG{@Wuc-uQv?uB6L-K`Rp~4x{&bxTr{OXSrV)b~O|EF!Om;Q| zZzVE+nZ0H|^2Js}kZmQO)Fb(qlV$hF7y41nS-!izN^pkGpDrbi+5&9>sk~PDv&2uiIu_>_fh&Te8$oa%;E* zm#H?Z-@zW_ybKJjUqqR+F9;20PkEWwhsP2Ro+b|;6@?wpIu|^eS-K~RsCo)*%z9r=gwP_b1B_d&f(`xvadcH z?snGYaU;(EsOm}X@nxBju?xxl%)tKr>oy+gcVWScF-<(~;(>rp<1dl(J# z{dlu$HlNcwl{4msG^yiPZR=+&;BgkGPwLl_d#$tM>^@)Kle}J!kp0V2G{^8Y9 zeRCzbhr2FZwDoigdC%%P!6lf?FWW1nYb@E{hhKCZBSZd9AQe(PvW?uod@kHDn?>SX zJMYeCOA>##-8MxV49NMQmJ^Y7o#gNH)XJjyqfb0^#I;0No+YIcziPY6Ji}iPp5_^@$Is?hSFllq@R8U6V>mPgiFFjjE=DDRL zYa#hUHQzIK+(Gi(%ssZHGQ1a$>ts16Y$VC2YE!_T(qs~cjDKkcd5(DOU%K4nE{S*j zT9YC*J{M!!u0D>Q2T^9#wWRLsJ_d&PkiUBvAK zQKb77@;u2O+7>Upl*gTNi7yNy|DwnFeAT5MIXq5Uz_-aI@p=0P&N2BU!R5Mi+9GXC z?lnWk#|Ca7_lBtKVWFfxO9PZnr%mH?)sE47&XM|0pCz!_^@+UpIJ+Glb(6|S=A<9O!j&gYMj`}#ArSjU|b+|a={ zvTlDSc}@wR6}ynUCl7eL^o9w!5407%)@vnyKazI~(IR;g{=M`+m5mFJvcETH1eCJB zv;W?|>cIt(za#(NF4X;<;CEWbK5Iq(el2YOUB)~3iT%5HcK*qKtN-u*{-n#?x+>OR zvwcsoJhjJrvHW-cQM+RP<=?G;xBtJTD^5xj8)xvM6p`Oyevi*~ftE-f0v9xjt&iXP z{jTRPw-fuj(eHMD(m4wkk>5Rjw@Xdd5!)W?o+I)*T+#h5@O#vj@gl$f$u&-7@pZbo zn*Bf8{_fx7(gm@<^JUhTi1;a49KiCG*iTRF?$>va~tqD-X7yoBwJz7--| zMtuRREAyO;vEuyK^pF$T-*O3&tbTmBWRZ0d`mY-I++3U=gST_U=12ACZT363uz%fl z85U2(Q8%&v^NmM|{7x*>*NpwHWE&hS;z#JuaoI_c{BY-#+3$3rI^Xog#mNtg6UoDO zvvQVy*ZCGAc@z5olT_T_Lu|eM$^K8=S-oCt9crh+@8Eyx_m}^_@g3aXb@;DY2Zw11 z|Izbk)2AAd`%jJMDfWB(-}U*gS)c!^ye?VVByykmUmd4e?}3H5^Z$2$RSO>dTXw(q z`;-3indrYWvHiG{J@3A+*w3WFj(Cyt{^j#Fk>~H9{0h0l_X?5wo#W;RlYeKP!v1#a zPO&)tyN>^!&9N4~!}*;H^`@5O|0vF@QUk?)XCw6YyWDYYmDqK6{rFAn_hi5K7wTFO z#sBiYEVL8K2@)D2=Zwr7Uv_RZ6?_-rLi_9tx5r?KK2^OvqSg^edOE04AN9GlGY zotSCQ>@;=-iM(G3^TKz#!tRHGoursl@;uG%qpC{pMDG7dol2PBK$n|ryrjlv7MDfA zJa)fNY;VT;mv2AL>Rnqtk#UO_0h^c76K{6^&EK+JkH#NSG?&@GJYvu66$g)G`&}c; zm;L*+rsmab{7|n>tltu^BTP@t6|j6dEdI&t9DBcqJgg02&&iTUi7c*A`F`wsRK8Us z>qkDiVNx-tj_qHM?jh{Fsci4c=3ABXT4bNb`>C<`zMrrZsVk}G3nKUOsIu9tPWeN} zh&-o-^%*?5jJ0RR4Q1ooY0I&Dys-WIjn5+WDR*v-*nODf62tP@)MNR-eE!=G{KVEt z=>Jbrn3vz%lR{kB`-?`WP*#V(w`bgph50mmWN~EPZh?9I_V0&%j`CQ%!so>AeuR4B zDi!8WX~axcukj{HOh(iOvGtJ=j1zgT2>Z$1krK(9kY{#yEAkwPZQjoO3)_E}8XD8Z z<}-8d0#+wJylx`v^?Uz*E2Bi}M%b>pY$L1NtNr`1dM)R*iOj>~OpC~OpBnR3*!!T# zSrwMgl9b+T9##83uzWZN-4mI|)v+dQTpzpMBKz7x_baPgZvJf6uh~G2Nnu>E!{>>O z_fLOMGHL80l24(Zs5nm+uTXEAvQTW@W^4as^ET<+UZh3nm1AjB{Hv@k& s@HYd0Gw?S9e>3nm1AjB{Hv@k&@HYd0Gw?S9e>3nm1AjB{|6>OJ8zqz%H2?qr diff --git a/test/data/samples_20240815.csv b/test/data/samples_20240815.csv deleted file mode 100644 index 77188bea6..000000000 --- a/test/data/samples_20240815.csv +++ /dev/null @@ -1,1025 +0,0 @@ -seed, publickey, nonce -80f531580b97c8de305ee20b7c87624b022c65badf37863f75360d9b94c8b748, ec8dc8f960ce120cfc670b71120eb3756fc77039f81f7e9f763820c1d185ee78, 59039e870f2626a33bbc24821b9c2ca6bf11aa89149d3f726fdf8e3bdef41b03 -33599e8c36c33829a77ffde84b0c9a63fb6eb1fd984a7202b27d8e98a198d87f, 10833f675abafcda6ed8962325b5492d570f04f028c2c928a613ad8f8ab4b59e, cf2a3e440731066c9554203f364597e8e169306b16bf5dbed16bead466db9afa -8c53fb63add5ba46d7d35f3e884436c75d99141fbf8cecb2ceb4a49bec39b453, 45e4cc860d917fd83e2fbe418036ff8299b045ea8ff390c4e67f226ce809a703, 57ba057d57a2353e562eb11e3cd06184660e68e5a8295b0077cf65cda8455032 -00bcca3db8ecb2e5837ba713a3311c6296d4bea989759fb80bcc03684e33140e, ab0a50c2ea6eda36a5e4bfc9de3325682b7e95d3514332d437f91892783e0ee6, 60e4227369abce0de26272a110f9313b4422259d493a884f05c37f6dd85afc93 -09c6876f692ee4f6dab5ea33fe0c30ae760d963a65d7f5ef84081847d5d313ae, 269e8f56b6a6fc9467f19df64c7550f331478fced4351a8b88e8580cf6b9308f, 404466a7dbd477b2f07e88abe497e978b5f92f62bc2b73e70735fa1c6fe05b15 -b8057dc06435a52da037aa6b0cc8aa225eaaf35df702fd55a3c5b7e1854ba484, d19dd21972d9a0c94c022d919aa6f4eac56198d9f74d79fdc10f873dcb5200de, 6be282581136b5057f68efba9ce8192761a5964fc45be06953ae8311cab57865 -5905d90d0aa97296d4f7afb08903cd2099dfda1392a32dcb31ae0cc011885e83, dc1c49b4e9f7f37b9924dc6d950100f9757902792607a94123961aa3edf7ab91, 4b2259498dc9a5bc3c7cdb80d90cbe3fd770cbb36d505ed3766d57fd66a51088 -a2a682a0150c46484903c8230a9c1e6031d49c480e5a4d1279afa620f7476525, 4379baa2d19f02fda714720dd5499d1bebc3f77819d700367def523c1ecec35f, 99a9e19d1294be153356c4f9a23ab2dd782b7072f91479247397e160da77ef9a -3d8ea411bbbe6d7059a2b526c624c83d3c343db76f537c4ab2f9cdcc5675b572, 3431ab5a9fa62e8f4345ff4a125589dabca6e6c7054a55fa7d83b702c0555926, a0d29fe37d12c1620ff24a14d73d9286ab3dc4a42bc26998e4a4f014d1ee2aa1 -4e8fe95c3c588c4e2c91200a25b440bd6370666aa7da01463b46380c61687dc1, 16ea6d89ea0eeeaba1bc42b0975f65cb35f0abea32f0ea0f28aba05181a284d6, d3af183839bbc4796bb7f7caddca8692ed054af06bcf9f537d786fd7595be98f -d93555f4d9708d8d5769a9e67538bbb8bc972b13f0c050e1b9ae3e0e7b8b4f48, c72062a0540da03bd3c0896c6e76bd8d3f30f194a453d5a238b38f581ae599d1, d5755ed9b7aa87c0f2275d8904b1cbb62406fe83966d86eebac4eb76a86297e3 -32a31b82aed7bc6bedb3ab19359bdd61581ec14425fd50462f61838cdf638da5, e950efa0811a7dae3757009b39a3abdc32933e6fedb4d8e0209ab626fe5440d8, ef3067854218e09f7e852fc39b50a5eb3cf7b29032a783cd05519ca16e04669d -7e948db1a7684a32f89a42438ca2c87a47091285bdbfea312aae387d76947892, b583c78ad5c47d2325f367b35921d343bf9e6de0e44986134f2c7711314a0cd0, f993e2c24eda4ce2e62ef3741fdc8b3e0fca5a381b3eed4cad930bf9ada7dba6 -2e162de4cdee20213ebfcf2caf4c04a323de6bce26706ff75d9c10a6558f94c3, 09aa8fcbc2821fab3bf7f0bfb865a71c656500a100b0e12fa9d8bd526b360bbb, 81ea2e1944262bb84ff4be4cb8dd7d55a50e7c3dcc959d0294d22fbe9bec181c -faa36a4f7f4a24d6b0b0ed20a84f3f88107d145aced8d0121567ed306a1a5b54, a4b440a48970f9f6b6e206e6f7ca9f2565797b64fdd6fac9054560c7c0d8011f, 2197aab5575d88589eec41860452b624305c28e3e79441792b310c678c02be3f -e760dd45e081d630936cf558aa12df41933bbc2ef5feab212d05e4215410535f, 476886ba5fd8c152ba27a4cd96b3506c21d5960ef5ddbc568b2a99aa10415a6c, 7fb7e252ec4a5fed05add14be6e83e32a91bc1ee249940d6e476d2de8a8de664 -3e09334a94cf09a1276db79eafe31f635c0b7228133751bfbdec8fef6e627f1a, 15f94a5ef8e903bcc5006c1b4eff4cfa4450862cb3ec7106cc9ba314d3c6434f, ac69a5759ca9e7c360c1331773f633df73d707f56350a508ae85119806c725fb -3a1c7bc599df3ffd675c75a36863c6fd5cb64d9bd0c9b4d4be971a5dc3610bbb, 87216ca3baa3bbe6f865eccd26b5e004ec21cc479e54f206d77042956fe5ee05, e3a1dcd5e679fb18aa2c24d9a5d61435b27cab8922f623599218d864a20b2246 -841ec7849705b75df1c7a5f253b047acbcbe7b41c1d3f023870140fa42d1da7e, 4a052601f6268a63a30ef7c8003c7bdf5c3267fe296b7b14bf8b34c18494bcde, 71d4f9d959c5a605bdb28f42558b51fd828424fc0cd2967f9994947e669c0553 -c19ee36ec9622277e71b372ebea2f71a07a6b394160d44c75eb845f27e248b60, 406d3ae9d2ed39d0a2d7a70e3b5d04d01cdc610a8711971a3a016c469d77e761, cefc6eb11e83a15480e301258200d2619eafe2de434233064bd5c3d6e2e00dfa -7b5afbe57d21dcda54a22da1baae612c78d869bbaf1591f6be115e39b9c704d5, aae174b0420fcd0bac0db4e3c1b80ccf44c893bd0fb23b7a633c37d68bc16660, 171b332e29d830eeda828e2de0c7c033377203ee68de12ec30f1c114188c7d2d -8351f698ce4d8d51bafd60b3d4f65859d9e574340138d2310d4e212715e325c8, 58f276daf479d6003cfd58d8fbea1ca398c8f441f8ac1420a18ee135cb9fe0ac, 6f4961ea5932a4ef0b1949d9575c3e3c1a8a6a584f6ac9ca09e80f871b48299b -c849cfa6b1ec4b727101a06f3c822575b4924df54120813cad8bf669c165367e, 08a1d0cbb5de9d5097c06e117ef635c5f8270e3eb7980c9c25fa05d723e9bb2e, 3896216d13bd6ba119f955728370784b3f5502b1bc9886474b1e3bfba1a352d6 -b9cb313d7852624a4add3ac76bae5d6f51f755fe1c2755407f644127c645a863, be7edb2465d546372f384491df1cadf8cfb594b4dd37c3a13a94b2fdf628bb6f, 4381fa78fc094a53d6295bfd4cd038efd41b0d4f12ba481523302d9d0966bc37 -545cbb878e0126261dd69605e10f02e27cc8080d593edc1f5d9a6b7432af85db, 86e169e92c2e42970ec09e017b604fe2bd6728776eb792e5130832502c5e7541, 0a2cb9ada195325b2dcf914bd38c8a8cf1d32763561205377698348b7f01c734 -3fda0ed9c632857d6e0edd390b7f644f40739e5be9a028f21417a9f44601b0ca, cf920d57fe3a5f6b5abc28c068c94033db6d45314b3ec8c05c38439d9f07f5d6, 407927dd89cfef4f9343cc2760763e2f7a70eec9dcd563674b1c91bb07a93d42 -2fe4e34d532bef8c4ba5ecd715fe35d1e195803dd4c6968739391a2c47cd059c, 8b6f647242f84fddc5bb476455895af6506539727d537bcaf16f556862525aba, ba485348975a9afc1ee5c2edf87c25b8b02cea4f14f1d7b6751ef62bc4c27a6b -8e26e98b5da7ab3a69dd52c4e3e1344b584744eb4a03309140ad7311866b1f0a, ac502f8894c790ad38ee8d4e54fa816ba072d54f8376ef87325d0795532e0956, 7284ce4ecca0ef4be435026f9a4d94e1a37311a1235ed2272f4ce24757b73bf8 -1a053b9c78f7a7660c6255463b626a807fb47d4af36c6570b4467044495623dd, 335cf0c6f0959112911c889f606956b911ef608b80199284ce6b2c9a9f76473c, f1b50598e2ca32c6440e6db8aa42f2e585d65e52978fcb11e3660ba4b282bf31 -cf42c718a7f11d6b0e7e63da48029a82bbe57dc4f1279965462831ac24456aca, 3c082e6e8d68120df35eb7512a7f92c6ee4219bb77b50ff91a1402f0104269e2, c24506eb048805058bddcd39942f78901b5a511d1185088dbc543f594d80e52a -114a7aee8996ea110423cd7ffa024515005035b98e160a6ceaf26782cd625409, 70f1f878cfa0ddfcce0bc21f11cbb5ddb767d1456712687043fb6020f0e9c9f2, 8551e022a73368b63b39e77310218e268499c47aaf1af07962e5f562b39c917c -722836fca4ef287c7b70de23240793da04c49854285a3fcb7e9d80752d63a58c, 5958922a179c35522bd603b6674bb6cae9d9ec8d090e7ddbfa98b2e181bf5fb6, 780ff13e9c1775479c944b7f73839b43dd16810ac2604c0893ebf1348220d2bf -b3e29579b22478f6494e0d7e0fe966496c0f2c2e596cdde504ecb2e649a90c3d, 570ca0f5b6b4ab520ee601e6ca375d78609d8cf5516f20f259841eddb9df916e, 149719ea5a449fe6b86274877fe4e1d16ed83da12d90f440c840cd092c176134 -d155cd872b02c54f956c2f01aaa7cb1cb547b9f918f92eab81a560d7db304a9b, 8ab08fc8265aa85e2079486d9a472fc527add5eb1ae11d5082f9517213d42f33, b8fbf5dd9fd892964a6fbd7c98a577ac5ae87679302e80bc4c69ded252c3c809 -b4934b1dc30de253ee67ba9835858b4f9643d8cf8d428e20fdd827a0b3196946, f0a38cc0ed32712affd44fdf485d5e92f095327c6483ff436793e88bd2311ddd, 37422b8f8182bdc3eaf406c8911b36e546a0abbf00a5b408cc778b4944f3b178 -710ded5264331e70caa7d37b3251a5fdc4d5e6cb9bbe9f4622f0a0e8fecd8b3b, cd118a6eda0338bed8a04945a0070363a52adb0d861c39857b29179085e576a8, 37adf1f6236fbb2c3dad5dc351f2c6a3d5eecf09a1a5b64175ae6674f473b63a -a1d14d26171bb8bec64256d8541b2afd6d6fbec6509d8d144923930a022dcdb6, ea28643e287da364b2aacc372445ae4f121818d9b1972ac2050caf958ddc8cb1, 8b15b914822c6d6f6c69c9ead534714cf602bf167c7572e43a176732065bbb8b -c00607e0dbfc8f7081b426febe9303c1d70e2b12e978567a50a2510526dd7ddc, ad1b94dbbea6c49d2d7dc098072aba7f83236b11eff4fceae336bd582f31ccb9, 71bbbf348fe841ca5c0e6d337ac48e47c683dfef5d2a4eae6d6e13e68409a619 -3a74d031ce7fd00c6305a056b4cda29c5dfe2ea4b1f290340033d7957963ee59, 7e78d8492c77820066865da1bacff13131d4be8faff7d420b38b2e4f8bc725d7, 5345ec907518dfb86aca52b67c69c34caa2c661acf5bb88cda4ddac7a458e563 -e7067e81f59054fdd39b4c84c984125f25078c71a6a4f1b480f8680a41499a7f, 46578970dc609e8ec08898e5424adf322925d50d68d0c54b9578836525f354e1, 72143871ffcecdf24958fa56dcc29f2b552947d6577716aaa11ad11c4ffcd4ec -355e22ca9a74741323a2d43aca7d6ce61dcef619e092ed8b2b1dd9c133e397dc, 2cf8f941c787930fc3423b42be4abacc139edf6332a51c1c6f731db490ca4ad7, a13718621c0411eeca4c7d817615f22de7fa28d88f88f04df2a0cbbda2725feb -574d0edc49a71cc118e9330a494c179df083f22f2410e19d741bc0dfe5b4576c, 035111f0e11bf01ff64d57f1ae86ece12cd5aa8fc91b1746e7a9e40270a7b97c, 49ccad663b19f6fb2142d86e1fc9611e5ba3f02c86a7f4578149cd09530d3073 -d3f11bb4d6e8c5bd4c66aa3071cdd40463021e29fafc2342dff2c9d7a164079c, 1d410ad2f413e2238c8f6f9449938b239047b31b817119a6e76c02da3e6a99d8, 8d0718ecd0785c979079747de5f333e3a05c7841aee391d19959d40f95a8a6a3 -322fed3ec3298d6296f937dc30fc1696ac73809464a3ef60694bf34881a2c1e6, fef059179e8f2768bb5b1a279676c56917f26363036c40204461ad649a18fa54, 2557b0ede6113697e3477bebe14f6f37933305331942d9bb494d37cb322d4832 -c5f5f796ea7ebf8c60b951397cc345d3e5264228a903172164d606abea738468, 8f7e868a4b446ba230bab9c2b2ed4f8a055549d37b2256e10c1e8f0e24953146, 43281b5765f86b7b733394539b168be45b62f3198fd5e0be4b2c3b38aa3d3288 -6c13dbda91de56e65fe82e903be3192c4c4cffcd9a3a9b4d5c1418b24b8a2e64, 70ea1b92b31365a2bef42b7d2e0a3bcd9db39243919e9b6e41622fa2ef81e2b0, 7c265d33289e0495377621842cae112fc0be98f8c5906f7c8350d4e6301e2749 -7e6be75fce6aecec7d29d71c09dd1930047fc8dcb24726e2d341556ed300207a, 6c33cb6585242091cc9756d54d8234a8f69ddd0ed36d11564477e749c0777e70, ee962dbad0cf1144cbe6940bf5e06acdea8d81513275be544ab55c6dab5f73ca -6d9f82ec5eb789ae39effaeaa617b80e54968d5cc31a2244ea01bb96a9f35f1e, 0828f004370a092c22440a1dcf92d565f2b58d977c4d2bb0e8219c567db21b61, a06f4e96044a448fc768d8e7993938c5dded73f682a4537367b8cb0b347de4b9 -4f8b780f6fa325800ec29fa80b4544f64d1e78ec8ed6fe277859a9af9d0f971c, 44ba51403fced34ebfde037e578250a4f7e762e71cb12fa091eef99db65a15b1, 4352396b1c31b975c9fb9c4ccfb581d6a69a1fa79f4f4fad4a1650d66389fca4 -d99fceb52f1c4f9797f39560f8feaaab5ec097ffd0e3bb40e981949405344709, b58ee6eb07fe56bf5a31729113c41cd7dbc78c3271e3dc8f1e801c5a2e15d90d, 1de3611bd60e01402b67dc191e9650634c8fdec99c89a9e9f7fc0488cb29595a -c5fbb4330ffbb0d9d11e70968fc21ae53340424f648607d9c86dce186fb303b7, 7a4e817113da2ab9d5679a7a332baae68121a33affdc1221cbd3dbdf568ccf04, d3ea6a747e593e40bb6673f5123f42ee42b908b0a23c08ece26b94a82a4cd4d3 -80ebc8f072cb591e2c6e2e5de285def811c27dcae6ee76ac1a41d545ba8808d8, 3b65499e4f2c4301c47bca2f4710b42bca5bf3f76388b5a45e123441b5cb5b8f, 2d254a80ffbddddcf742e202ae2058fe92212bf8a9cc630bf18954f7a0a7b54c -8159bd0ae02c9cb041383605016cdd1841ba22156c0a46df7f4597e5e91401bb, afcd8eb53c1b30ee93ea50aff06135a1bb9e5b38dc79c73730f6c0319620b8ea, adb2711503dfcbc332028ad2ab72e287016a72dc6ed643466d297ba71edbc183 -47c0e32ba68b1d7100c52505738fbc3d6c7c66f496902aae2427f583c5e04062, cad6c50d1c38a192c5d9e3f5a0858165514e4f8c214e45e792fc560c92650131, fc9282b3aacd1ad0d7a6de5ed459006308b7a1c2911e74eca48893a13b51b589 -4b01ec0452998564fc1cd7f48ae09e9a24c87012c0724bb96ab5129afda7c929, 593e2b53604d9ad6193ecde26fbf81463df5df09a9ee7cd06b75414c97d97061, 12fbe71c09f60dafd9e29a1c78a09aabe7ecff7e6405de2e63dc0a32e9b707f6 -b6e5257179bbbac8bc4a5d067423861ebca1e69314be5b2b8136c84bd37b5ac6, e609de6e9a0597ebd48f150f53f895ff9a2c032086a2934e21ba9d15d66bf9ea, 6dbddf5bed01319f77c879fbfcf58bffbbc9354c6b2b50f1ad8e49cbd6bc91f8 -e2f11e807ee1df48706f9c746a19bf45d499377423ac9ea6d6047613c94bdd23, 9f0f970782dc87ad218730a4155c4e32720b0b757cfc0d2b6316a68dfd9e9bf6, 60c711c10a8929107bbb0a86f409a2d01d71f45b262666e5d3f517e89725ceeb -62eed40ef81368fb9706a813ea60c3e8abc751d96c255d1149c1f76207a2ec2e, 9316ffd3669bf42b75122254430c45b728c9b2e918da5d3e087b7fd4932d92a8, dd2de48a7550c62fc59027f156b1555597b29da60d1d707adceb31b22f526788 -0b4f401cd4adf4195466cf4a03b223fa4c6c5fd5055200a179b4ba0cb4723b9f, 43ab54d8c5a9879c769b0a049cb741595be1e462128257a302f907fe91c74461, 47339008af7a990342c939514b880844df93cdaea521330b2a21fd72a1d9abde -af10bb84310146adfafd888c1778cad1a9dbc96153fbefcf1fe6cd2cf5e461bc, e777348d4ad816787cb576c76ea931a56bca30ba553e58385661e2ad67acde7d, 36981badc6ac02e4385b820d808c2b860244ceb2bac1121771c8d123f023a9a6 -6c41e8f56cdd00a0495e93444dcf93fc69535114a44d9e4b026cbd8ecb08e52a, fed9ec22b5f417feaefd830fd6c334a5bd35a2f19ac8a26f9928c9e9c780e0a6, c674d66e3f1080f018d4fa0344c404cb2b9168b1951c62ec2edf8301fb4f71c4 -25a9c035146da8b3954fe3e4a065d437eda47aae3a515dd36a9914798500dad5, ee3e73beea28e372ed13c83dd4358a516d9e8b104b532d2660a23c934cd00829, fa937a718b34edd63d09cf7a927917cb8259d2fbf32ee922cc94a58933782ab3 -2602c7426a80b72b27b95c106dc8075b7e95a24793a7bcd4d728e662e034d489, a1957c226f67e458d043df8b9e18ae3c3a0482c6765c5ce7d3080f44c4f14b45, 6c114b8b6e25ffb34732d47f78c6488b5fd44205404fc8309bb1eebf0dde562d -276f29618bf141ded8b1ac4a1620a00f86ac0034f6c01b6c123813cc3143582c, 611350bec964aacd5a99fb5cbfe5c6aed904e5674ab17b12ec6222317d7287a4, a621021ccade9c4acccf473c390f69ade59f62fce9f3c0eff4180b1f2df5233e -35839e0216c1afb83377642c425f05e5eca30c4ddc26a1826240d5f53a3de3c9, b938cde395ce4db47158d687f78812129c68d1ec15d364d6ccb7e72fd084ec68, 0f1e559dd0245a6f8260fbfe275116471c4ada875435e5faed57e403ac8d8e16 -1aed9c13420ff5feeff438a5047704bb7e742a54fa60ba15614025d6ccf8efc6, 25e6a1fd14e4e4f9fd14e2de0510300e0fe2afae516ea5998ce859b48ea81324, ba9880062f566abe8febc61a257dfc74aecf228629e1084b46583db43cc6ef72 -801e645c7562fafa48d92d756fac59174d8f155fe9791f633a7df78449db9bf8, b5360fa37cd80b48608a0b58e25a389ff0855979497acae0a85ad0ecaab18336, 630beb8afc4bb3eb741332352c05cf01b0ed31a89c1ad7c1eb28f326695356cf -d3db1d83f3906421bb4fe950694a9144526a00d55c44a0af2dcbe668fdfdb57f, 13ef0d12fd31eec4cbeaa929a8b71fbd18da33b4e11c7d9cf28900f8fdfca1ef, 6a53e08906f9c1f169ec357e95fd4f239bb031e6276caa4347c306b86d84c779 -57488552401afc78c0ac1b048f740f4c122bf634f621b181a1ad6805669e4bf2, 8d228f797a67e68d6ad1654a85aa8fdc56ec1576c91e825f9e93e7a68cae4496, 2f106a87032409e652af9d2c16fdbf6292f66c61a61b598fea159d151df4cee2 -5410c73aa43ecdcc630e34722999ad5b474fe9f5a5c1de96180e9abc9e76c4e4, 46bec0ae397b683bac2e27cc5a191b9ba2f114e88c3d93134006110e4844121d, fffe9eba823edf55a7430e49ae8c68f5dd5eaf8a4f27b4bc067c9572be9e8e8d -a690a103714c9bfe69e0c40a7d42202bbf8bb5552970763a1dc2b3e0c7f604b9, f723fddf7ae23bdaf6a6111e6876568d3c749ec655f5671f4293b6de2d6b16ea, bd61e8a61e3122f9b1c84e1f2c24f1df3ca3ddb33c165f45910117876c4bf7c7 -b12df8dce59a09578d89f2e0f7add5dc03cabd17028bd4b18d806e00496d04fd, b5f6e5a816c30f307fe3d2fc819a5f37cbd19225a2e9192aef571d7380b14775, a99c4b95db06d69bc84307bfed0ab3b56b01eafd4ac98f6c2814699109820d11 -938ba4a622f87a67359ae215340be309c5b34d2209b53debb6f78b30acc2a7ba, b441f65fb5b58e615526e5ce5ba0411913b146e73dbb79776d7bfef7b1f55e09, 2e4e9184507090764f8c89e43efd8fb32dd52654699fd80b39c7b489a96825f1 -65897a29abe9b3ef9c0b67e92b639558b6ff56638313261bd32ef0ae32be5402, 757df81604644c15f4db9be2e280c94624ecc14ced514a95d2f32ff1cb1f5bc1, 157df3d056868f295f8872a35662eb7d6b4b4c8ce00c52e14fe23fff10e4a275 -53f665f9a5ee3eedd195affbe303454ae65ef7a088708a20c632d32fcd08d4da, ae2f9f855288cbe742c73443d446db85c7e162b0152ed2a520b179c006650efe, 72eb3685aa6610bf2af341a54662f312d3a9681a477103fafa22aec76cbdec47 -ff44b8242b818de51f991217da3c4c19f4b1c015dd861c15d05ae5b74565cb4c, fc28f8aec401dd7cf760ffbe6ed09e34b08febc886ea1307bd9c2174abad6332, 18f0e35b1a4fa56322647a5e0cb157638d6e4c833537315d46e4df7c26b92829 -c3606f11004f6feafdb739cbcc6647c8ab628a578b1be5767e541ef70363c324, 95b90bfe96f3a948ef2b4ee05f4df02181c2718670a3da96ad24385a63610a49, 088cd3f4165477aada13e45497597f165e0fefd2fa742bf1bd490ce00e893347 -e822051013fde3ac42d9817744483d04af70534d13c9b5bc0d71db36f3b4da02, 5f22bb3a984a7d550f4b308ba0e3f9e5d5d3d13ffac250564ad67087568965b8, 3271eb98004fd6f5d54af1bfbdde4e7601709772fd2c7ce41cc3ab2d08433459 -81988c5980a83413f0786fdea093e2d874ccd9e3c65b34efcb3e5e7b531b0997, 4382d71941e38bf76c0b204273486d8b92eaf095dbf8d29503741d894c4c4838, 8b2d2fd2e0a883bea1f6a352d4fedbf6cee63b3e4b28cb9c6dafefe17dbb0c10 -3a3a9c56c4fff9214f0798748edf4de1dda6d5393c256ff039e60b4bad3652c7, 813409c86434813dbd79f2d2957bc82c2f1775e3d3c78550f9662096ce3ad79f, a3e52f8c0fb0021c1de9e922d9239b451f7a1996cd6824e450218e0606ed3201 -5f3a0fd4573c1f0dccafb7d550520323ffb790a067ab48b2991848f1adaa17e8, 0bc24c53de33a3fe74591d885ddaec309daed16f7d8fa9ed819c175f88a06444, 53335086e38a90e2000e2a6349f09a6e6c64fe86dc2ed1e864c8cf50fc9e3e91 -85c121006d1eb5871286a9feb5368874335a2664fe24ffc300b55654e89e4627, a1dcaf54161a2c56b85c25aece8a56b866bfacf2906be1d2a55e4deb224fc7f4, 19dba2f65508fc4c8c9999b91d14a18e50c47fd867ddda0d8940025b0e2c20e8 -01e545a9e7df6abb6a3fcefec9f31d357fa82958de1c3306bba0a97b355b89d6, 1c8d250584e08f61976893821906cdfe23948ccc24f03319643f82025141b363, 08af1b7eed7cfef6e5180367677295cb393684def6305698cf21c8f60ad56a60 -89150c673568155693c1d6fba1becb71f02877dfd793a76bac295411c2c0a11b, acd4bbe38033d64a78893b6761bea9d77c7da033d6554486bc0974c46e90d0c7, e5d3dcf3dba349458e10d940483d35f0eae581313f89fdd065d3f7d00e99b7a8 -0033a1d4bf31867d1f2f22d23c873a4d7f465b2d06abda98109c3d824ffa90e4, c7c49f3ee2896184c14b90f78d1c105e56716c1b4f095ebf9aef1dbdb1a65999, a32e614e268adfa107c464e2e45854c11fda0cb0d49ae79862c9588d2fa44465 -22d86b31ee754c8371c57546f2a8e5af25fb269c89d5ed264603858366f959cf, 5c694b3ba0c84d46dc86f7468ef92e38d48092692284cf7b2181774217bf81f0, 537484ce83164fd942b755d84ce006d68a6c745bba2cf5c860beb8857cd908d9 -d15648691f0082bf7f2fa9b46fbd4bae8003f25069f895830d3e31002d9e0d0f, 57957fab5adc48843ca1ab337e92821397fc37dcecaeb783a536ca547418c07d, 075c4694fd39b7b3255c15ced1ef93d7c141813c644ec1c77f818071e1e5ecdd -384c70b7623433fb8b0532171446d6289c4fc76fa3300949a29f7daa2ade69bd, b1b49dc825e102e4bf5c4841e9d88271f1fa5f4c2a7b90155364057c7cf0687e, 775fcf9c57e16a3e903b19f37658d0d72e661e188d1f20437004205f74fcf670 -6f04496fdc89079ef20e544e73c79c0fa1f3ab06e9dc7dca59ae9b56bdab7f68, 78aa278b1755873d73345f1a4a6c94d60d4e0be7d1b473bcbd409d79b0e119e0, 61c2669638fd996f73989b30b1300c353f572648eeb2cd56f0fcd0aad4eefd51 -9f22f8f64464121d39d1ef106c5b467f7780bf81a84c53862f3e1c85da060d13, 3c5ca16916247dc638aa97f375eeeceeaa0566f2f2a29a67b2dbbaf5b4ef2eb0, 972db6218ada7f3599bcc3e3e0ffdab33a52d4b2894c77674c7dfbb712ef9762 -79c6f3482bab0f986b34c32c5c1165b4a037bf879e2a24ad3356e312894567cf, ed4b7d0fd0df05221065a642dd52d229ee7520c01771cd935e3ae99a4c2018f7, d006a2963e46d78315530801ab24b94477cf3947016150e30e3168eaad7eb856 -f0fdb60f27c5daf6ecbc1fcc29519119607fb768fb1893a684afcac17cdffd0b, 01ccb0ceacac5071ab06099bfac45fab8f2408d74d3d57a81185a4eda6ce8008, ffefde43352da4e6d1b85882afb50aa285e42b92939483923ec402bc4d485e6b -a688141f97ddecfe1fec93ce6dba05a118285c1d621619550d014b4de5044180, a3578a9fa204d9a08624a9b5e00af88a3204fbb63f3cfc64ef1097555463cf4d, 700d49974426955080d30cc5fe9d858b321fb85d742a90e1b51d1e8c5f22495a -141f67d566551797bcbcc4db14eadeb93e055072215da28ba12aa7e01e1b816e, 2457ce686634f37e947ac4731b46eb5756dbe2ad3c2a95ef2b709aae8c6ca2c8, 6db2bba3b5edfd81fa5caeb3e37b140ea8443daef20c66c9ca106bca3a362f9f -3b2731a00c9f8f5e45c1a55598a50da57ceffdd1de4b2fe84439fb819546859b, 6696ed771246df57a81ad93891e084cf5886fda3aed9d8fa97e87f5f022eddcf, cdd89cb1f07eedd2a1f0fce951648eaf054fdd9eb4ada1928b0ddcb0e3c0012e -7ec1a4aded428ba5553cea5abb68c51402f543a743d96b3817b274d604dae417, b7036b9464e1d68d61bfddae9493f419cb309f257da9a07728a639f1150a1eb8, 15a4a2f71cb584bbc95b785a2e4efa1bd104fe731d840886063799e40a1fa9c2 -812a1f2225ec3e060b7170a545fe926c072810fec8b3d37ccd36c76ba97a9620, 99cd89e8bdd19846da4d0e2e17c0adcadd6893b311605e09f3ffb92bd4ae5373, 5213562a965b1c3672aabd8ba642168ea3b31379413544579a9c77fd5a10b996 -04b91ffa150353087c33e51db967dcd8c9dad1ef6e39a03c3853b6843d9e7c1b, 164b7551ca68ec0e9d1a0d18a62264c2a77df66e5add21c75ecccd46dbf6716e, 87bf5e773ac3627185f3b6bf9f3bec2d2204a35f5fb3c57f4158b027c02f668a -c1f1e7eaeaa4ce46ce56e28bba6b4c500543a482eacfc6029a6d75018a493875, b913b642ca4e8989712398be58b90f9b4b450feaf11ba42ae8fde88b6bf6d81a, 2f86525660bc8a4981e7ab7e9d70c1e9e97a59d6ab09e62a48fa715d50713814 -2e0266a105bd9c4a4a21de81d2a780b90221f900ed0a6793b029cceecde5a96d, 1983882728801d9e34bf7a3b5a0b8c26641417ae20715b2f370a521e669c0783, bf7cff995468bc5ddc734673ba8222d06fee2e4b20e0c7d4f970644b8d67b097 -a1a22d00dbcfe8963dfe2d74c7a0521357cf339454f77540cfa80b39b4a12bc5, 39b74cad3782af7c74b31fb8409ca643122946d38bed96b123c050ffd6bc11a5, c97025f70f009cd3a3bbbfb639a19a5256a6021f02f98812253c42a0279cf45b -3310d531f1d354d1865ef43e3774f0559c13034ce56d8ca4a1bc3eb82bb64507, 1897e27f1340034d65ffeb609848d4cc48ca7f9642f9c08199594307359faa3c, 679535057ef092abfb6b1d1c58695fb63c521bfd4991197d2662ec199cf2c5ac -dd236414ef46b2dd599030dae78e5d720ac802870b567b07872cb4234e6879c8, 4263e277deefef6bc4f0bb01e9c07fe90662ed6a88a7826f28f6901a41ad6b8a, 229567a4e2e07ace7c1f6eec066a0115c787cb58f6cf1c62bc4db15730da67a1 -0bc36fc26a2d1a2a72d4c6e34769ce8fae9ee058ddb53fb3ab45648798845a70, 06169426550a2397cfc5e0c365c52689dd8ee04073063c1d7dcf93fde665874c, ab251b643a13478b461f47a6a94c81401c1585f9c1537a1083ccdca35ed2065d -9850ec88dda554e4c8f45993b7625b16323cb52864baf445d67abc70feae8e05, 9b890275142b66230d9ea50bc18c4eec1ca93fa31a9f3ab82a09df13cfbd555d, 6925edd3d7fc1b3091017a91ccf1d6d14e6475a08aea5d6d04c401918861ca04 -e0fdcc3c53ee1b130cc275890178e09125d3a7e246b8f1a51d023c8734e0da2d, 12db061a896635e8d155067a0c24e7389796b7c9d09cb4dd460ea81fa575cae4, cfb3356252fe2c0594363984f9b267e4ff0d1082bd4d5abfdc48c2768d681e37 -342c81eeff7e84f439844934a336633d0a13daffe2d742b312dfb7e68a663e3f, a8a16c5b7cf83fa370655820b53f7659d4eb6c66da080a61162e09b43993c614, c38bebb793d39f555e97c8c78fcb711cebc7b504b96aa2ed56128011a4d8a0ba -5d87d2f843cc093f60e2184749b317f84d6e7a8d2ccc082a61fabac5d0b6f73c, e9da5a5876d04c0de62bf868b10c1a50a3c933f4bafb9fe668905907966ee1e3, 5cfaeb3938967055f1b07c8f66f8f3d021f485fcef12d68a73c9d7b06d595e59 -7ae501e7bf2fecc1a6ffcba56cd05d4bbb0aa39fce5d2258798216f35be54cc3, a13678813238e0e387ccb37c27bb3c67a62cd3201c6d7e57e6b4bd462933ba57, 865e6b2c1e83c2f452b5940833709b71db12dd86695654fd65ce3a2a98bc96fb -6beb58cf2245d3b8da6419b6e555d713b94081d14e66ca62fd3829186dc7b54b, 95b21bd4ce58a6c26a4f904b274b55d46db0c7f90fbb5b5dfd4994c0b013b628, a26559a0544892d437c99e3dbf33d2acf3b97859b14e63f74b8af30b63b2ff71 -b6a12dd060d170dbe65d637027758886c98a730cc94424a6dd31d264f11da9af, ca04d85ac18ae93bf03152cca95032db62e1649898fdffbe33cfdc2852c1db5c, 17186b1b5a6ce1fedf29cdee5e1b9e660171a26c77749a97432a929d426ca670 -2df71e51b1d34608a4b525c5dd713099a0a0cc1aa43e78a2ce558c30133cacbb, ffd5a657684d5889da544e92d7a928adc876a8ac17bff1bd42ad073c3afeb715, a9065a635aa2b658fc75652d5a6fac2d256b96c477a9c612a169f68f7157e93a -667bcd645267073cbded4684c97df027a4e2ef974dfff4f8279a537a83fd382a, 0a095409a7837ce88da00a699ac3dd927dc2cc9e0f8cc36bb2cdbada0c22f3e7, 61611f9f3d26706dee1e9ffa0e96abb9ced62956c6ce58e0b72b8905cdf4b338 -2d97dbfed1b0566a3b8c978541e708103ccb9c05aaf0b9d5f4cb5843aafd55a6, c4f074b1ccfd72e495ee966d064779d8aef8204aa74913ab0e84f115c50f9e99, 8430bbc65ede711053c7c9d3cc346009369a01718054de2e8f1e043fa5bedfc5 -8483d8f30c64f2053bc0e27c05ebf517e2cd1bf7eeb4a72e96f738ef5b01ed62, 5e72904f7f68886a0853eb3721d6d90144d8888f923c77d5b7270eb611802759, 72676db61957f202835830cfb539fa5d48fcbd550f0aab92e66346a8d939f16c -8c8d24477e95b5a9932c7e2f190f4c6005e74a4854b294900ec824ac8b3fe51a, b45ad080c55d2e25d16620316b502710c27fa566d32d5a3ea2489d7b0a266f75, 396f1c0c248b0893e63574da3c3517947706c4a5d31df5cd1ed8590a31422a90 -6ddc43dbed0791192c3404d606bf6b98de107819ae082cfde725d2e7a1ab8210, de2db4a849c5ac0517d2c9a2db40537cd2f0186232083adfc489f8beec05f578, 5fd4dca905dd9ad5457f3bfda3a529b26c211f13f21206dc1fc1775a4aad8bd1 -45244bfff6eec268a03200ca48ef53017b7b695079741746948d6065e84fab62, db87b3847c87781eed1029f568b64e109ab4b0d661f28705d3ed23fdb4995d36, 15530ade117c0b6a583ffd2fa14a7f26cac1bafcc25ebaf35a71377a954ec97e -c1aca78b540244a1a71f90dd70e7f033ad1a9466280eafd32c3a17e8139b6510, 46e272077efa1b9f0d6775efbead432e51bd41ebb9c5d044f4fe55d22a4fdab5, 5cc7a5bd17ea082a24e9149b2cdf0be024e43dba972118d749dc71b59d58f7a4 -9a884f9abcc1cb1cff302a903afc92aa96885e0244b1e4798d136d1eeefb2b93, 426a12dd21a90f5fa3a857ca19abe8134c466292d7d944e7b6e6e90f0098f80c, 4743092421babe322f8ba52963f68389bbc5de4400462456c8000831c61df6ba -31298813340f565684b7623be3878f101aeed3c7191d190c71444138c5754d98, cc9cabf7a23a5409e2430cd30db171c980d6317e8e917b32edc998986b3317fa, a62f0280201dd584a9989edece8a333f9fc7d51a63eade9499ccd46f37242319 -54026f489756c050adb92434594b2db31467d68758b4ed1eff83857685750601, 385f0481a26bd3e23831c9b89bcd28a8e30eb94f90f8e6233cdb1db7c804206f, 91d001cdf4fa78079872690acf557560742f643d0ef20a9feabed4a2f6d31199 -5c4e7ae2da23ed358754d7906de527f04275ea4afd99e9ee580235ad2a338e6b, 66b39cb67b34996799cf0baf98e4874a409d4dabe475fb6dcf22c4e3ad43758c, 4da8e7ef6e99ad6e1882fb9f11f6a4a5edbf0f06981a8c752176d974c345eaa9 -a946fa860d6ef960758bf8d7a84325b9e37bcee7c3dc3c97aed8daae53f8ba7d, 26b4d2430bd657147b3c392697596ce20814210a3f17cb00785ecc91b2403116, 6614036329b9bb0a3f4dea190ef2cf90ac34bdfd9ded412ba29b91c91bbdd90e -d4368ea8f2b407c5d028f5f9a3ad5f5b38aa30e2fd4aa479f8c61c0b806c19e9, dad2691b2e70fb6b941256f4f4cd4267e8e9e1a569c7ed2af6d2289ed4d25054, b771c6a4b1210f655833dc176a6b9a8751beb75b7bad146bba70446282cc9161 -d4c18bd7700b5b29a6a7645c5cdcd8f7db14acdf3f1808730f8c700e2c65748d, 4c59eb2a9f01d7d54a5092bb5c76e27ca9cf79d44e6ebb92aab9a0f2de82ed0a, c38dc673cce24513a785123cb9ed15aec2680c7b4f02aa3f169b655ca09b8236 -0d121b82880e332a8a1ece9bc5a4569cc9ca10586b2383a86a6226d29a2a668e, 1f85cebd59218787707ba00591a3190b41a6949cb51837811daa0b7945dc4b5b, c4e5cd8e2aa7544e0eb53ebfbfbcba65d26016c6270fb2e29ad064c6b2529579 -ef531ffb5addaaf7abbfadb92591aa96fb4d9ed4e25a19e8f12bed26c27b4296, 6e67909a4a87ac486087adea2408c5848f44c9ab10553fec632f49aabd3f61db, bd31ca0366f1a3e2d6248ed5ffb99b41cc2e8304ac0afdf256e76e7788479913 -dd215bb4873dbb510acb973aa3787e100e42995616280cedfeb59fee7e4376f7, efd81a121f42a4040fc0053322a3daad2dbd41a9090c4432943715c352f712fe, d6da98b623a73383eb63024ea6ae755d93321f72e369efa85a7ef43675244564 -57442082bfb5092b223ce7a23160102f620d58e14be7097a3aeb6982e2850a7c, b4878e6b082c03c89d7404d4ab25779aeb25a6e2de946a7cc659430915214f37, 3891a9abbbd6093d9e6c7506691b532754e9beb963c9438e94ac4539c70c0240 -f1c764628048f6a64024aea8c54d402b4b18903bb7fb4d01bb9c5e122dcaf1e3, fcb07789c557a58c0dd5811060bed7fb38b8fe740f9ada8c3022843978822bf1, b060644e456f17bc028e12dfef777cfa399f8b7c8da506b75b18277b4881f735 -55e639d1c147399c7574ba12b19cb5819300fad39efef2e87074b216fdfa23d2, 04b1f2962156ed7e0b2a5463bd8684ab4103e82e09bae6ecc00419e2d7f2928c, 765cfb0d4561032596e817ca389c46cf70ef15f4094351ad4c4451ec8e4be072 -33cf79a8b708249dad152456f0a06a92d382521aa4b62eedf4d85219135da5c0, fdda516c77d28e4078469e61d78caebe6672a88cd7f471a98d8c41f6892dd38f, 9a41baca343fa96c9621b53ee230d9f732a13acdca1cf7e531a3ff6835a4c44a -fc5f04be235703a5c9a39e78bfb8d7b4309767b856d0d039f11739773cec0fb2, 3ac49d5f4580a8e37825ea6ef888945d919fd004ec38097979be8cfe1094900b, becea8cc7ac76ae2a756a601494fb609b89ecc98ffd492bc8818ec1f240ca1f7 -2d956625c3796f0d7458b526bb593d8afc3996e260d58655c2e70d2252cb901d, 3b5314f1868492e323e821b6b1d6ef229dd3dac52b041aa63eea66e04afd62cc, 314a375136f5740c3eebe1a53bf1d57a070eb5fd3507d17f74198efe91a0183a -0c125054d202e1edc36c26d1b5c4865f0df90c5f638d4d45f80ba331d36368d2, 7df808b4ab19309996b70985f0c35fbec7ce8e4c0036e27a361a1d02b4dcb2c0, b663e0ae8634d8d54a92373989db5b92411e409206030d968cca46d97827e244 -a85eda3053917954451b7f8f98253e12925302bc51164b8ee06f7b905ce381ea, 0c1dbb68218741a3f52c44979918d2c2f0c4c92a652df3965afaa4f518aa8153, b231ce4293fe7a3cdabbc3fd43b11151d9db8d45826f741e982ce68ca2ae5103 -ac9d625686bee8ef88c1ee22c7e39c444309fd07d85dd251634fcd04dd8e80dd, 55ef45548f2eee4650d07cd91c7d390aa62ff612f821a2939e43696ee4aa375b, d5b18cf35253572b55f65b6ca82b2ed451fd9109610edd93848cba607adb1c48 -8f6e049dbbf330450108bffcddb6bb8268187617c9a6a6a2abc8b67121449792, 75d3e6432703337e0e6d07d643972537b5b26a83dbd17e4cac4f8fbea360d1ea, 4bb8435992cf3073d8608d494378bff3388b88a602257927761a2bb3bcdb3542 -418469b56d9f6ee431953195c2cd80ad3730f3e2710a3e765e2596ec0148da6c, df260fa1aa7973e051426a4418b155ac465dca126b614e1a8506fc804fd6e4eb, c1ee21771bab3c1b043c6c3276a6c7c030bd8da1ead75bffe6bae404a0a2811a -33b38fdf29242ce513f78c3c85f3cbcefc044a59d265d944b5d96d41c004f516, 287fd00efa8e748ed74db7bdba6f2b2989fe6f1569a0ac991e81023c87ddd9dc, 1a40bb5c8a308ca4db4219fe7df6382f9723c7ab6015658463db43a2ed1a304b -a3649e30f02acc2abc96a32565c57b21b9f4a97585b5c1f77a9d38e63be958ba, 9f994b91331a64b97d0213980def07b6c88e64ec25c46e75c643ce4b12778454, 2446288131a6164727ef584b5ad8f7842986aa24d6c8a597068d10029e72cbd7 -ca2006dd43ffea48b44ab225c2f8da28a7287a67f97f71184c910d90797c7a43, 4e52cc178937f21b5752d8b6f71e3b1d9054775dd1e71ecafa4ffc7c131f9a2e, 4a32fef538ad13ce6ee207797680b8094ec065dcacfced16d9784603f293273e -131b32e8e1257e29a84bb034ced3a77eb8d8d29cd903701a44f7c9eeaf2d6a98, 7fc5c006b1b015605b6934f46c8845b5fbe698d9725b065009cbdac6bf83e323, f37fd1afcd207a14dad719a03919c5a59e499cc8cce586055423553687c3deef -f713d7ecf0b7626715d360fa02eebcff2f0a6346052cae09c4828a91a523290a, 93e384aba88634b37150eb7a812765dfeccf3fef1bfb29815fa74fadcab3ab4d, c449ef6c82fc767b09bb01c4120cd286dc5b38b15e40973f20deb524d3dde08a -9c0911229e3571736ed7551127dca4aedb72ccd5df1fc49f2f165075174639d8, 9b34a7b316e06696b043488bd9167692b3009d7b4a97f7542f3513b2a096a1ee, ba2dbfc11282ce3136d50d3bcae5d34bf86071dc39ff9d45400d7bbb0be5e44b -1df2ec653bd09ef52bc92f631610b6a57cf1e2725e605eb6c411c43bcdaa8b02, bfa0543ec0cdaca500aa2facfcb08d6037f455fdfd5ef402ebbb7c70e6be1f48, 8b8f933f669c17790871070b913bc052fa3e281fe89f53f08e3040c0921d75d6 -72143f4a0861e3b78b67a2a63bdb66e83c021d4f2bdf44052c2378cfcde32901, 59b3e2d4da370003df98a5698a9ce2d0e08ac41c2ecee532d04800ae74e2a476, d27dc95fee1c562cefae10d98cc7f5845dbf8606d1fd9894197f9b486c3d1c36 -360315484bf9554360dff00977b09dc7fd50a31b530af59cb6fc093462479b4e, f3b1c5b0c24a1fc6546b0dbdc307c047f57aaa511b47300bcc0d9b093cddb0e7, 29740a28ae1c8b5f52a185209bd35e658e4cb91265a61220e0fe5e2028674722 -3ff10dfe5e2faf127d08fde1630a94402e4f4aa5a565e90659d6be36296eeed9, 053e65f0cb912541f93fdb001f1f689b2ab11cb0141d2b158428ccd9047660e0, ae21d0c538ceae8eda2ba2c27761faac927e81c8054f80b78561c667c6486d91 -fece9432b4e3a3f337161cd10480d388b5cf71e4d421a76ac1580a3636d5cb2e, e35ce2be55c572f393d5b0bd2c89378787b6be47084c15abafc78b27d858265d, f697992bd98a65824c5b18339b4dfcb2ad1906804f4c7a4f44e64f4497ad93d2 -d700401e768d861e0f6eb548e0e90afe5e834ccabc64a3f540ee851d67efc088, fda9dc282f25676dfaa68155bd07f5aba8d979b1640f5f8c02b6eaa14fd2ee5c, 35edbc6c6a23282069702176cfe56c794c28781a26fab34709c074d681f5140a -0aed23ace6af9df090ac60dcc057f2d31c97c1dad3bb7b04c0abc606f3934e2d, e7ddb2178cd136d118cbeb5bd85990c341f8e87d366022879cbbdca6a5505926, 073231a9eb996bf94dfce558e1fffdf8b1fdff07af3a1db1d410032d22f1f1ca -560aa8dc922a41dd8e3cc9b9648d1b9223a24768e2327d3219a6730e7d67c1ca, cfce7e7cfa669a8a441acd6dfb3d21019c922e26ac4f2619dbb61d054b41f683, 77f9f8e57c1ce1aebeac0a2fa5f4ec94b3f6d6928a319df0b1a8bc98c8c6b0cc -08f04d41f4998a9b8bb5566b89f0679f350f26bc760a2b6e27a092df9ec65a3e, 1464fdcbe03d722f5feb158e599f032a703d08d9c3970a4a8f559149d8faafd7, 2148e2e2c5a1c71792426e144563c93168c9037fc7115e39f49a54ed71f86fb0 -aaf627bcf7e876fce82f683b11243042e371e173d5da731281423a1505d68fee, d8e3bf9da4524e99e7b353ad25c8b1bc9cba56e6fa237d47b6bfccff947f36b2, 785bf4eb340914e11defb77db92dbfd0e84cf32eda7e286957a2084dd7c08780 -ea48eb49e538dc8083c97d64b285f3a3efec023a075c4f1db65e52bba565827a, c6dd3b4a508b1563236af2815ca3b0ec89b6066ab11b2b1dea0af99607c11aed, 5aa72ad1f8b3247b0219536a1e6ee455a04357f57fdc34bd2acf5cb738af221e -3925165b6a1943bad2a9c08eacaaea28e6e236fb229380eb85d8af417823b0c8, 8669eccac8c32fbb433ceabbf51074c6b99e3e281eb3e94c3bf737c4713c16e4, 8fa888d92f1580b56313ff398ad2d8032223abf9581e34f5ca73749d77a15e8a -d5e160383e8972d51542f3b7d1169f41d7da9ecf0df2c27fb6953110955161ce, f116c4dd5e390d7dc7a23a83a9cfde74d59000f6ef3073d8f5865fc02b500330, 473a56538b7812ee3d2e843fc005e54561b90e539baa4e7aaa1f994f06c0c8fa -82cd7877b12df26732c2affbaf1538e87136271ef74483402168db929e23ba3e, 4ae256afda78500182391ae29cf6f38b61b53425ca917896f32d06a66e1a67a1, 70e9596787e0ecf7d6abfa128b3a2e2e78346f9de3eb05a865c1f8bbe540a914 -81f452acd6b76b80aa12d163ce246fbe2ebcd9fe163d7df98a662dae368b18e0, 77089437c230cfd062c226638dfb94825f9dd68e08ee50c88c681b3090886453, 012edd4404bfb58a6f3c310aabcef7a7bb9d126c468ae16b554bc11515f04632 -db094c5b759215b51afac9fa307cd605e9af9f25879b0ba52f1858171ef4d1ef, 5011d70454acd4d687e697f827fb6ea7ac4f2db96e81fd0805f03d4b9f2eab37, 97e97cc0c70f1a263c80f93b887e24d5b3dd0d723b9de5a4cf3e105300ca5782 -bee8a36603d7f7149181ff4bc2dc4bbde4fbc4607cada61f8efbd3585ea74689, 94579e75bbc8e507f82949490abcc17d39eca32eb38ddd872f200c1196168b91, 50e34fdcc61d485f63991a0a8b92419283e8446188657a7d2cfe4de0b8537c8c -70f7d28e45fcfcd7e263b7cd6b045cb2cbfd9a8d8c079d09794e335b2fb540e4, 6e84d6ed18cef14466e35b062a7bbeec681e609e2aa1035dbdaed8340ca5940a, 4b611f9051608181f82ae414aaa7cb60a2bf7c58cd38b4759c8f94b7ef2ef716 -e1d5a1c77624deff9b45197d10c3563acefe12e5e319b288adff8a4abeab819c, 5acb0371f55f386e7743e5f27b3fa74fcfe13d783e50c5373023e83e53f12bc2, b7b1b6123706c73819c50ae68a3cfa7c50ca2187c768e9afad009ee2a76bcb67 -58e156ddd1f5e0a3a8bfdf95eabd23ae927746a2a1a51cdbe1c255a21ae408a8, 4d0e616bcf07b310bb82b737ed27a7c06e553470e21f2be6352b3c12a3bd25fc, 183e9cc1e03ee28777473897296856d5bd4cc87f345ee99a60564c9a158b5fd3 -83a805bb7206809158ab14d2474d7eeffa032984acec25b26b88b1236732f209, 5fa4545238bd3a4a4c415b208327686595a1ce01a240b7557f8282e22a184fd7, 8e5e47d338cd003e671289b70ef6b7e858b23997a89def9de120a705046970cc -872efdee7d9f8d5052ed33a1e2e6ca0b680da11f065023481147031f2c081bbf, 757c2332241740a88d65ad04dc9383265ad031d79fedb0a387a506847464b3c0, 8589e647820345b8beb185381acd86454225c55088a74919564f39c85fb5ce6e -c020dbf2977e28ab173e60a60d09ff178e97fb049fbe18411e09bbf6cc871d52, 4940e04d02db2244b5cfae40cc6d9e058b5ab9170538dd1dcddc7de8d2b9641b, 20ac5f9cdccdfe143b2fe25427716d73de663b8dee0f99cae363ddea06a8178a -3fd262906df6eadcdf8c1cd8393b316151bda1bdb833fde47c676f9d12d158dc, 95b9b714669885f5dd90a097f7d6781e2ad8635bcf1b09e04d16435ae2c8ccc1, 98adbc1b44d6059cf2be55a89e7678ce54f265cd0a91fdbe6d7dcdea12afdcb0 -b1397957757c860a6927b1be5dd1dbb23c217a8cee70e73294846cd05dd749de, 802d41a1c3c430ccb3fd7cabf2498c798e9fb7a41980fe86de0daae3edab5e9b, 1ae98f324acb13564070fc836f0ad34a2ab4f672b86cc51bc0c675cb8ea63daf -8ba70a2a2a64d59b00ed1cda103d1d2659bf8265cbf71e670a6a7e34461c1404, b3a48c31b42d0e6dfcc0e93f5e3540adf1ca1f306fab231887f6218251239bec, 356cf270681fe79b643e6a0cc2766b35125dffdce73955263ea16962f006aa2a -83751e83060d13f3d2b02000d54e9a030c32bda71bb7466c4a0230d4c282a89d, ff4ebd14245eebcab658b0ac70d7def1c644337d511e5a9e053a17654be1d1c7, e6dce624cbcdebf71b9dcf8019a9d1210e647fe9c80d3a8077fc841bf05e8feb -df34f657103b664ed723deaa860aafa60bd11161a0a7e5cdb895139fe1b0cdac, 71c5b7635cbd0a91f41279429cecf96918948ba616799c8da4e28f09af94e48f, ef10153f3fe3da49e663e89e0b9c977a1f3e196804fdaa728ab49135af827422 -c31ea0ec4789d0ed2c9fe235daa12ff7217ecb23cf2098481e54ad7d6ce078db, cbdf18612bc01604cff9be5573916f6ffd3940ff3ea657cb1ccbb6624194453f, 7d073ce50412ebb89ee6c4543e87c0c41e49b1f6a961306283d5ea3a4d7430b9 -73905efeb6d2dde38375cecf5f6edef97b7a75db4ee209d4b72a8b56f2165674, 9f7f932c4681b66acb72271efd1812e1b38b7af146186ae3f0b3a05346620f77, 2ae882a099a95ff05c127bd8f174868c3e38870505214d24717e9481ae300589 -a260e57ebe0be57d36cfbf288e29e876b3c77a4b768702be0e615937f9ee8654, 1e341cedb9daf5c549381f9e31e0d31e2bade0156c1a1885f1281b80b2093db7, 835a36fbc7bb1b2978e289bab0ffb9ca9a60271d44e7ae4243e441b61d4fb0c1 -8c9d04ab46e794f57c8f88a28a5c73c614030bf391e77bd4528f3d4b7134b143, af76f926576bafec1333c2dee3c134e21378b99fbb4ab147dd51dda99d75dfa3, 92d66a9d34a21e4d6925fdbab7f5296ab26f38b43638ea012b332a4486777014 -1a6d9008cf1661dc2470a649549a37403c6bff3087376199b7da1597473b5dab, d844dcb2a406cea8df3665799afe89630fe4461a92ab05034ec3e6a47de0aae5, 87926a2599d415909b6b64d9f5cf149c219cdec0d1e0bf07d8ccc9381cc7f8ed -bd92613fa4175a309f2231ee01756f99e0706886b581a249b890e48a2ae2d5b2, 5ab099c98294e74b7d54d27a56c0afdc79828a1c4f43a994d2448b585a90b35c, 71b4f39d125a499c65077ebff3180c7fdc876ca3be0eb97fd667ef29a98c8a91 -6946b0c2b8d82f53bb64d1dcda7cbf8b52e74e6ac9cfb023d2d98d74061d2fb7, b1cf0f62218bd1b9de84c71da916ca7bef9c0a0ea6960bbf71ff19c2136a1eba, 5f80b8c28b752743d8ee5f03b05c8c2386c98abc48ba7120da79f39ee865589a -0e6a854d7d7aa4e3e915fa9dc7b730a1997d27af57604eead3962850b7ea8b75, 4cdcc21f62f08e07fd757f14c907253e0bb367ed1f9b5e509ac8c378563781ec, f201dab7618fd8dfebd8bd0f7b081c1ac5b795b14a5dfd702836262c8b29abbf -0cdcaf42fa6665f0d5e89b2d458701437344f9c73abb1a26a4050f5efa6a5488, 9e448707e22e616bd2bb4ab8494ae24d51e75d682f2c932eba3a8cf4893c22b3, 778e5ad0580e39c3d3fb76de4ceaac8b9bc06c75d860a7538e8a20b3866ba58d -4252fb05f613ae00409cb372649522a59de53d3b7628ad90a7a8880cf10552f6, c445a40bce2814ecda8d5abbabf5f4528fbafb94b9ee78c1f4df7ea50a2f401e, 7bf9d3ca6bdcc1484814523a72e74e5a7ae13cf92eda4f2090562d72866716c0 -6ddb97d5dddfd77103f463fb375732324aa4f595d1f197ddf7e725a85e4bd96c, f1639bc721b8edfaa15153a59491455128b348c92b94917b4cb44070b7cdb6b4, 50f9f9026dcd44c6f2c981d9d51910f01dace5fc114d2999284c7c6aeeb9fa5b -3df11b4790dd6dc133e44880ee290822d68ac5dc2b9e085c5be4dc7edf8fdf7e, d8fe5a6db1d18e091d1a16ef2ff3b5ac99a9d2784904463345ebc6ffb2478473, 353c6171165bcbd49fba22c8c87f4d4f96aa77d5d204a2249e102fa27d28b087 -ab15ffb29cda5d5c1f347e4d703ad78747e530805f47defb7cbbab2715fc5cf8, eff5847ae9dd70702f10099022a39c91bbdabbd17ab4b28bd269feb9c79b14a4, d58cd39525f10a1a979c6730800dffa97c91ddd906115ee1b4021e0ccb13a7a8 -998229cf99c975d4001e0f118f8a60204ceba9a230321256d44a1dc5febc19f7, 563e251233ddfc708d4136376472cc4de4490e80e4bace95d36561242e53eccb, b29b289b3814fafd0a9b47f1907878135b15e698fea6cc3240f3f1300cb31161 -b65cf758c7ed7ae9dfd8b80a80d8c27968cf43f241345fb593c0259165ac53d5, 984368fa27bff95c0c937de6dceca5de7be26708239913b72a6786a6f5ff8308, 60eee2a07a170b60119d967823c75f25fce27c24952247abf06a0c49ab0b9240 -65a71c43ffb27df61e6b5dace9ec7850abc9436413ac7587c31441a5d796ac16, 7c43693cdede88d2db73a90edebae21904c545fe3c187e64a0ddf18631522536, 54e6393f3bf84261b43a5af4d0a6437cb5a1d412c08bff6fc079f2b9004f3065 -faa784f7f0c9a74951993d0087d72e377bdaddec9bd44a05554d1095e56b060c, d20dcbc660b1594a39081fda43c3995a2334d80349223d6f00b2dda738d94458, a10d61ae9b419e4ae4a7c2127982c70e9df18906fc68e2458077b6b528324b7a -5e5fa685496e4f314a35e93e20a342b00ab039cd9fb0dd44934bb4637112f0e5, f09bb0cec3ef6e3efc010858e07ee4afe604d51ed89fe95df26d24b2c0dc8415, eacd872becc14ec486cd96f4b181e6827b62b0d156e5edfe8d6b0859ac71ec2c -ae847ef11455cffede0b6211483af1f3ece97696d23fc7665d79e27dd51da4cf, b7a4f58988cfaea7786d43881b02cf337f4ff596d540a19ab2718ed3cdec4e5b, 35eb57deb1e2e7580d20df75f36d7ed4757dd84392dfa2df382671d19238ad3d -7000c118fca331eeba81e0a9edc22c1043c43f33b06a9462bf59a20a51f53236, 9a77c258843bb460b147928b448782042a815fb7f0cb9e8462e94756e37cf591, 4eb1e745abbcade3748f9acdcf2581405088c580d88b5bdd78fb651b7262a481 -68dca3a3c6075b45e9c57b0545734ab162f6fc7bac52114fe6362ff5c56fd646, 509dfe7077e31f6feec0924c13eb602a67e1a17a3901f0d7bcf2fbc1980e308a, ffc5c08dfc92addc3d33da399c8438d21e808e2df608a039a2d55c9ef61b8374 -200f55f7bcb041aae8310932d1a33a7dfc6b4723ad3ba8a013e67b93442b73a2, 57d439920e035550e55fb28d85152d5bfbbecb47ff2c3212c13956a651c2c331, 8579c52d9ef9a80ed17fc205d059942090f30abfa86fa781a9c3396217ba9830 -7cfce1c4ea644eca36196e673c3b1a1775835069312748746ce84fee9db4fe16, f8bf94c70ee3d2645115318b4c21f7d9a8b08ce8792b4830b6421625d5cff9bb, 7e5fe1be4755da669d6840725fbb9429b1e0a2aa550e095690e896ac0c43dc37 -2d5372d933547adb170e9f536be1adbe9fdd054403f55afc3b7faa60362478f1, ade03c4aa13316b446463bde69928e7158603d7788be132d73f15351fbd88588, 1af14779373da23e3e01f3d10c3824236db650c732a70f987d8c53f0fd4cbeac -9101be1260bc258fb9a021d2bd8d81254fb496ef245862b6c91ac0a84d8e0f05, aaa144e41c663c736008750f688ffec6826606201471ac5d964cb90a20ffea24, 786aa890ed2f4992ddbd854d97b4f191211750dfa075ddbe35fd607841308e34 -2132abb898a91fe531e6fc962fc8203c298794b75b64819ba13abfa2dfbd0711, 0667d6dee8490c3b09ceb943637c8d09fefa648b3c28669c8e351d9640590e2f, 15cefab481f444ff8c1cc878e326b15128cd0ccd3c028da5587d87c82931af0c -c7b483f8caf3ebc9764cffdb5e196df30f5068739505b0d9405d2d5c8ae3117d, 949d629f1902936d43238440d82863db758ec5329d1f258011edb1bde7d91c9a, 39869dbaad7b13269186cfbb97802b0c476d091bcf7d2b03aaf9dd7723dce7bf -a7c3ac5d8debe3bbd788c8f6b34def5e03089aa425b43a694228b28ecb88743f, a7f9ff191c5bf7a97a464fadb2cd278e9f09ec4865b1e920ec22c09ef1eb7877, 86cb050cd5ff87b3a03f1ca9af000ef7e47e024b86d21a20968e82ad5e551e36 -4071d517c6d31513ec056ec2b7ddf7d3b65fef043c0060df1515fd421d75253d, 76445583d7c7b15e4425c14e1fb948886bfc0ea797ef74f7c406b2b127c36cb8, 7a9f296b7a466714dcf841dff13e1af9e5bd82f4ce33b80744b578c4b8e3656b -9a12c2920c65fb50bc88d84204640d54aaf10ffe30947aada59ae6d028f55b06, ee9f5dcfcd82041ae67dd71f96f11dd76b34cefca2c7e2c48638cdb8a5aed359, dc0a1cf8de38b20c22d40f1b8832226f2e945188eb9993e47b2c1c7d0dbc1270 -069201ab8f985987b08a7e6b4aadbfdd4880b4dd83067e3a1ee185451b407b4e, 6475fd163eddd8aabddb207176debb02903312b23304699b4f9a37002a53eb54, 649a97398149fb2c14ec197e49a1e6daa9d8529390ae6936aca6b0ff100c3000 -091d0fe6e3154efb4faad9d440c965b78384e2bd8a6c8233281cb982028dbe6a, 155b5a83a57ec3fb8d139c08bf75b87e7fa3ae8d7921e20b3a105cec918553cc, 022f843780b2a359270012973d1e4e6b84d36ee4700fd570b3fdb578533ddfa0 -9898b2579d8984b0434de682bcae3b8511b12da6c511852f383d76f96ed44cd8, 08c63b6dde99e2ffd5b6679b2c15f0a3166acf640de256644cdc37d344cea0ee, 5f20055a4cea3cbe2f10756e0c007d4289535060a7d69dff524f88bfd7773fc5 -e4e17f4caaf225f05222456daf01ce66ac049232b5210ec1e8314610d8e9b457, cfa46e62f02336d97910ee09e3cbc729536436d3f069a4ff7d2ec9cb949e252c, 54303b7cd5b0e2d71232782dd5b32f2f58ab69da37614567288aec97552b69e8 -8657fe5eec1be1d00769e6c6c2adac46954eb12cb479c5e857fcc196e29f6dde, ff717451eb017add3cd1315fab34308d82e03335ef121b22e28f3c6114df885e, e2e0fa739628ef01b894dcc483b6897d3e6f4b38555063ced32e53f266d4060b -f843767cbc4d8b1bcb360d8e06ad460d2666f9efb9507a6cd35f12f327d0d2c6, f2ac6e0690c08cf3fd83874cc4c120dacfeef1d80d87c05bcccd65f83d9c1e20, 07a10c67b1370f35b5f215a97ff39270000a3fef8fcd4f787e3570b56e3327b2 -4bbd1a42c2414cb6d62b0315861feb0dfaaf14d322b73dd6c5480e79c3cc7a5b, 7ebdd84fac41977f3f942aa3f4b6838c183f39370ee7373a0d0c7102ac4b245c, dc80dfd2034cfaec86c1134a91cf02b219c93c03fdcb726c70243051935d4d0e -59d06b3cb62da14dfb94a366d299c6147b9c2ee0e265d7263fa11f0ba0a22431, e0dcedd474304ed9e3fc65bade532463032c4ab0bfd3e6f2bffbff96de0326e5, 2193732d7ffa6919a86b47e55a2f03528ade05fbcc6db46dd9608201a07976f4 -6eaabde432ddcf28b7f2e7c002048be68ac28fc8934fa0f4cb92526a73280f48, aef159fe054d5bcc47a167661c2eabd11c57be11f939e8f68b300eb96916e6fe, 9ab6f79b9b8f862139e506c0c63b74e9b81df1988d47f86e95f889425dadf81f -64466cb72af9e9fb28eddbf5cc76c16b3c37688b3fb9f509178c0f1b9f1804ce, 145f78a867904448d6288ed0177201f765af7132368689ba05139356e9d5b6dd, f1e35da99a6377ae62ef1a910b3b23aa496bf22eb296ecfeb8aa0d577cf70af4 -c30cab93c5e2298f643ba99a27cf6b118236d862d14b5bfdd31ceb140c28d22e, 83c3249e07b056ca097a3ed729ed4aaf24cdcf0c7401d7cd8c92cdca4dd9833a, 5b328cf9b16b3cb9f279eb3080e52c9a936715429c9814b14bd364706150f741 -9deed1fb3165916ba539a05863690de97a8be624df86857149ee7ed23d5d1f89, f50fed49fdc11170e9c001c1893d67188505666ad599d32b15aefb22a1ea18cf, ce6915ecdcd2c88d176086b2d639d5948c23732fb8ca2d87514ea1d7d264f9e4 -5ffa28965a6cae50ec920959b6e9cd4314715bdf9beaef187b8c3245e3c5817f, 90e22c5b91059c43f614f95c880f91fdabfdbb9b4506190afe785fa61430b481, 98337bd5e21026f7525ef478b2774d36785a1ba28818d7401abfe80a828d8b72 -3a1a4a8d4f9af68ce0eb210b4b6cd483674ce85c903d69109e485087ec337999, 643866818dd04aa086cc3164492ba1c148539806dd8efb64f9cd4f671c188b2d, e2cf7d090a864c9f9d4df6d08ff86b8d4d60544a4819458b2bb4100e3aa2306d -0aa66abc63cdc0387c8d79d11ae850a5b098b918ac3350797d349415cbf48f97, 603e535baae24e76ffac90e3fe47b974ac12d352f501000f44492e2abe479f84, ef157293c8c70164530a50a8dfa5def22a5f619995240dad1068856f2c236f21 -dda3b9d3a615dfbb27ac647934da4b8185fdbb837b8d7af71ce2d8777167d6ca, 079f062d54f25dd59fc5a6b1bf323288b6e0286ec3fd328fd1ac566e3a0ef65c, f24570ca025b8161be99dd88139938f3db16d3df5bc5bc6273aa32b9429a977c -6e01bdcb7c46f37013dfd031a28dfdc0425f3a062528a20724ad128360b6e5bd, 5ebd64527deaa0cd685aaf6ede57ae718abead95e929a99cd11168c254dfe75a, 3215df9c5809677ce119c47b8a4d7b39299e276aee5ed34fd200c7195a5e918a -0a3751c99a9a4d3a6db1702da200c3c56f8344ef78ec86e4a5a95d7f0afe4eb0, aaa8d0994ae0759776e9e9ac9c8698b1c0639b5a4a1376db404fb82271d65f5d, d0372e0af249bc2fe6b5c37f7c428d8b6ffc2a39b9e3ea41fef09149f6c99c81 -a251ef1369e8ef1861ddef3ec7c8de96f416aaf2a237d9ac7c620166c7c61407, 155c546ddaeb6d8f7721b0f66c45c2ba6e3f2a48b67ee09eb5df0b484c721256, 187f05c5729dba52e1327cdc62e567b775a8aca8c0913e985f683b677c642cc7 -6be4f29e26b69d6c9de74cf3d882cff1257871850ced2869c73e036687901cf7, f2e637ed4b785907a9105a8ff3d31d07b624f02a3f54e91ae0988548be8976bd, 98b7a5eb7314e7e6ade82202603007f73c83c400ec1c0f6064ed542170f00b8e -0cf9d3cc0f6c096a8800cec75873d3b3172ebb85fbf22c967aa4fb6aa4f45624, 675abd7eaca43cac4ba4ca00a22a9893d84fe12e132562150f1a42ca20c72045, 6f45d475321bf62fbf75d654e2c6b734b28550824af791ddd48b9ec570349338 -efbd252a2ea3ba67b0999d394a482b92392cbfa09a90adfee1b20d0b294fde05, 2a0f5a3a4bc06f930000623cd3759bb1e454a7e5821cba72f393bc2260596926, 3e026412ef123575941f97ef8c15103c7819c0680e85231797c0a24063f49c9d -41ccb492cf9fe5d41b030d85afc0f608d1e2549e5f32b425f7c1ea1eb61c5c8f, fe1f05f16c43feca92b9016e8cdde1d34c67cca00c720703dc4c14ebc49c2c2a, 6025d5210ad6007d572a61ff1166d76d7462cefea91255d55c241f7c44fd52f3 -cbb83584f97886992303773e348df743f80782ca551652c2ecdb4c7a5f732997, 617436701d4f31dec317dc10effa92cac94902e8633dc713228a789454e7dbf5, 7c200b3a4e29845e56ca4cba09c7889a3ac683d5ad9d0021d9d6a374b5fe767c -151a200a475648b11e09406dc60a96296b45f77e8d9894bd5fe032a58d2963b2, ba7bbc3c5d2eea07abe147c030882259d154ff648ca1fedeb94dd4ad31367241, 331b1db32329c0904f2e52ae38ffa7774a886464bf284c9d76e89d7a422affb0 -2ae6df36890febf2004c766e02e87f185d15cda75c12a3bdb4ceb4a5242eccf9, e53031b53034f16685d00b8f0be258d09b4bf16920b5273cf3d59765ece2f2f7, 8a616cfe82bc82576b8d77bb218a9093dc482fcf99be7cb04d3004c26d45e387 -d17ab0e550421586eaa1b549a86d696e6b6da2d9e13dc65b3a8b0a2fc6900e1c, 074811fac06b4066ad18603077d178afd5dbb567556d475bbfdf1b14c4b8e4fa, fa0c91026bfe017bb69aacf72a15a0417a395d2889813a096078cf3d21de6f7f -8620a2fa85662e22237f07c4a4a85a4bf334e4e5f61a602b26a9028eef7fe25e, 3b24f4f1f2ef1b884adaac879403bd5ebc919e89eb4f9814cdd4200b9a894e30, 4270a49b75d013c657faba4503dac62aab05887600e44eef8afee3fd4f306c07 -349cc07adfe04d112cfd9a0415a74a814ba66d6e684d95899ff7f03fadbb3e1c, 06bac3e3c06c8f27e7a4961004dc0eb00f355b73b83c93c7657072125c6b1cb4, 6f37b3ccac017fa5a1e93afa5b7fe37c97840505fe63df962c8f447a373deec0 -508e957417d19bc0b2efdb9e9e8cf5525a5dd609805b19cf573d37105bd5fa10, 693e9e6ab95b431718d512a9b93ece61a051fff79ada69e0d1037d11e15eca34, a07810812337c5256cf0084aeff8dcda3e9ba07c703406296ece71c869473db2 -a20ed7af866f5b0323122d2c162efe448c3061674faed167d5adbcf5807167b2, 79e7b77b1d8c7e67de08e4f774f5a4f7cdd7723cf511b355faf975fda3c48bc4, d80bdb05d850c50725ac8169f457485d838fccdea2cdcad4ee5f316cfe189768 -665ad182fef840c6ac55acf97d0b36879e9ec4354f693fd21765e9bde69aa3fa, cb5c3f1014a616b3910d94a2bc1c660fbb51b74325ceb2adc827e9b11687ca41, b3707a1795e8be2d4ecf3890d3e2ea6bae045ac18edc5f99cb981a1de30ea0ab -b106171a9ba01c4e52c4879e0440abe36b87bc3b2c7098d067d5149e95fcdc52, d5624f868bdf564ca7511cf63e8084ea5bf2da5f528886c732ac3a721dccbd5a, cbe5e99bd4f3c645d5964d389d49bb38333a46179be6653fad3a905dfc0a399b -a79b6f4444b3b067775fa3ce7fc91160b59904a0b46d98b1a990bab034f7ba44, c878c2cd691bc3d32a3dbd437399c35c9ce19c5358394710ce752c1361d30ed4, 393cdba823ee085919f07cb05aa01a60aa3d284101efb30e863cc290b0b70801 -fca86003809701cfd997dbcbd15b9a6d3b4483c2bfa2ae2e76648aa858e94b64, f3d6ba5c609d77edb6ec4b016580b2d037495b7a7ea206f166bb88d22a9527d3, a529dbc0590e7a7d928cbde59456255bf1505adb8cef40449a2b1c8d2a2fd11a -147d0fd0b8934f8b085bf9b803cfba170146713fc3836a19aa54e6d1291e663f, 0a1c5aceef0603d066c6bc8371c9a11c2ad84db5d58b38e36dddef944fbe7243, e40c850ec82b47e5eea1cba355fa82b65d47d391c82e9fa6302082ae53567029 -19736fb6cef2a1a39487061d89718377ef5fffc483ad3d1cad6687949409ee4b, 4f9e1122949a5cf692a2f4bb80974425ec07359e7eb19ee802725af6ec48ac11, c3bb4b1012b16c1a58b83a6dcf4112002ac3f102b7b4a189fe9168ecee002679 -6c7228cb7f4b6217c4b041d3ebc198c6d75e78bce69cf5650d736be98f60839e, 7d2405a9d1d331a452b77b8ec2d36e3de103bb4fb0f8126baa40d1b574b6cdec, 64eac758e8da2de7000fc28866e5b18d10d1506f45259f0bd5e061be83143129 -a4e89ed3a3496ab2553989f0144de66306a39e12344984d63b6cedf8a746b83c, 407664b7d43997d7a0b139f9ff02836a73ccf52329e951ba75a6d46aba57a5d0, 68b29d6a02cc9c061f4eb07a76ce2093951a90663375d0239880ed075d771c0e -3f5f40ffaf6136af7a4d99c53f7fe4b7d6e2f342960b65d7fb781dfaf4609905, f7bf70246bc8f0894c42aa65a122eb3b69566ce06d5e5a4cc27af3eb82e4890e, 0fbb971ad3277251112b301c13ba7bc00220c18d07e1673cc1049b09b0ec12ba -20e329fb2a007b13b68991a691834fe03b9566945c1a60ca453a0a123d0628f0, 1f7a22c4b45f690d8e11485644a49b6c82a67e32e92af0c6af163fd3e14a2aa9, 4fbbd89ab252700656256d3214f07e04255de23c85fb57e3643a2b88f9a975c6 -1305625a8ab12df2eb2468d80630bb0db4f0abca34dc452c2a3875e8a5584c7b, b807f5f2d40efb69e8803d57c8d3c60e85968edd3740dac873cac066f1231944, 9ab65083e8062fef9e912bb94aadcba6dde2f73c160251e41e452217e7363a3a -1f152d2d777805f728f876f85690e4b1d02cc764c9b808a6527e246365497610, 9cb23e7643cb8f6e28e5e285893cc9f1a68ef75117cd6d247cf7dbc7950dd265, 56172d1571b693399f8a79a638e6bb8106f04e7833715b5ad0b6b6c4cb9d059e -d02d2e09bad1f8ab4219a0a6971fb17be619681f0ace36526eb5a5c09f90d534, 1769d4bcf522ba8531f13f5a16d7eda24393066a2d9a34f63bbb01a0feb7cad2, f105401a4cea6464bf6b0fbca57c29317e998f12b90ddaedff827a0d8d79954f -4dfebf1d8d1cc9f33baf4535d5eb623529804688449d043e22c7328a88645894, 862e9d4df4835ce524de4449f2c5b587f1bc653334cfd876c2af942c07680a4a, 75257587d873ab4b08fd8d5b9dd52b509a4d34edd73d1742ae3c4c7613ed4032 -c0c76cd91861f18fba205b543d1a300a372064730679dd9ebb4576a14b9d75de, fbe8e5d57a40c5392099b278828e1f594919c8c6d9fd401f01c93572347a9680, 06134b9f03953a8e3749ef58c1294d93e2892a3561867b35f844ccecca53cf8a -a0b7ec515fba5adf96a6bc666b864b30b3549d6ec8e759ef831717257fbbe05d, 1d8de86c071f2b37b57027b082f11bb4bab5efe3f79ca558d9a98b3d1686ba26, 37b8a5004528429aae57cb0a4e68de8398d00b9c60110415faa29bba49722cb8 -7546800689066609cddacf607d855ee3fa48fcf3e555ca841a583b6a3e7536a3, f46ce8d660d35b7b9282b359c2eefc521cf818b120d2c2d70163dfbe2b9e9cdb, 933e12bff3473e3b5a76c54371f88c82851380318a4c3f7527a15e2eb0241cad -a4de543258a6485e2412678c1f54eca3b33c9b596f283d1768e61501320cc0b5, 58201e3ee015ab507a0bf6530d94913db838fd8695aac8d0b45b90c8ced052ea, 9634f56d59825ecdfe665bb91f1066edec13aad194ff8bc5c97707f7e5eec8e6 -2832d5d056c3251cc58585e1ec46fee83b7066422a7e48a00471462e21551d6a, 1b0f74bcdc25c92e0d10753d33f124f8c2cb0c52e09936702ef9536a8bd6a4fe, 3af3f48534e741fe77128a6c1eaef02fac0f2d97aecae4cee890f38e9f83b042 -f7f68849ede71cf678063f66f6e1ae38f1854f3084f4f4cc2846ae2e69450cf4, 201cf906865c23154017b987461a4600ce30b48f640d465ca676868e4f8db586, eb75fcafc15af498c1a7e6110fc1e60246f0737742fd37d8e2a0ffa954ac911c -48570d90ce59afe7590fb6ce1a28d8dffcd9432bb6ec3cd13d96a05c102d3268, 08309d4085c5a9e3efa5b8a238213b9bc173de61deec4922b9aaf9e3b5e8b52f, 750335c4c77e10cce1edb328aad6d427156f9ce891d459cf03ebc983549e60f0 -56c9c53dd5365d08624e42718e5834347b6ae6ad61f267a2da17f2909e8389b5, 5f6092f086457e6f51895a120c472cb159e25ee6be5b3daf04a68ed88e82688f, 1ce95bc347c64191ced6b4e59ae73f19069edd3d512bdcb42891e3a9fca7cb52 -cf5c5e18485c927dcdb928fab02504b425b066f442d39e9a31d12e2aa0adb39a, ab46f54ffc2ee8f82e6cd465bd48d7a3a58b660afec9a0be44a7c891ec613d4f, 07a7c88301a55efcdbc0e4122007d0a3ad466b235832fa62c28254448b978d25 -f95cd1f41b27d17bee8ddf1503ab29b44111e04e2ea6f5e28fc80133b23effed, e5da5d1c08a796a0d94ab838dcc1dcd8bb58a32e33c6c9a248fa68c496437c98, f1cd2fc8f434cef688212488f1142b1672dd680f859770cbf2f18b9cf232aa12 -8e5242c6d5869682e16a733dc5d46d9603b2e933c4819429c3a8a313fc63835b, 418798d1e1d1aced31830c818f088907bb918fb6a6faeb31bdcc16d0919446ee, ab5425bbf430ab987e7b4a245a2e79a020327cfadfbc3adcc3279cb9f7cf16c6 -7324ab063d6c3db9b79ca8f3cede21cf779a7525caaac3cfd122de33e2a53ec1, ceca8b0bce0579317dc3e61b308f78b51137b22e9ebce2b24fbfb288425b141a, a555fac95b96e94dc1b589f9a40368a960e95ba8780fd71b6af98131b92e9139 -69fb18860f208e77402b79981f1c9ecc2339f73214beb06a3e7e1d95b58d2c55, 11520f2f262818c0931ce835bff1bc1db40b08ec07ea536a16cf79f12a9490c0, 2515a753064788efa6edf3019961b6257055917912f11cac7bbacf3cb74effa3 -b4fb8ce3b698ef092144e266168a4873298bf49e43f9f1c896ab720c730a2f91, f4c9f3f764a5fe8ef0a7dfedd6763feed4af6c8265c33dd02062cb59f8e527e0, 6123820e051507abba4155d1ff8c6a531289961e58e74828a0025865fb8b3e64 -a81a3de5f01243f42463e807c7640f01dbfdb7249ee853b1120299f76e796ff8, 13ccf9b8d14989143d6db39f78db7f2241502b55db845364122c2cdef1c02c24, abd7e2f0ec93eb83a3ff315cdf73ef975c976357df6c17532cec90b23ddb955a -4fa5bf35337d77fb387f5ba80230ba3388a35fc9217764961a2e3ef1475e7f17, 0a90abf612976ff3e2d57ba66024d8bd9870a6d1c1cb3a32d7f8a0874ed952cd, fc730a27f018d85aaad887038fafeef9300437f147d1465cb74fe2b729cb4165 -611111f9230f1bff602760c2d8b97ba244817efc00b803fc3e44a0d5f1431eca, 0fc383507c6e9fb6399872cdd7bd47fe7aa15b784f5d8ab64d27988792d02fe7, c94f2ef299ede9440fbf526a80359d06b6bae337273ce10b30bc5729934f984f -af06b165653d9e73b8c21e15f7f17c784c111086b0c712375b891f651503bc20, 33bd3fd02d2147d810c517497335f2898750357853be3cb23577694e6b0e0127, db4dad3e40d74553602a36cb2d0a6288b901149ad982eedf4e7b9b75d6c19d48 -58d3eba71ee7f0313aad4286cbc68967d028e607be9f1e649fc6462ba94b1367, 9b4068458a4c70b018fe35af2ee1224297f4e7cfa1ba67d02c2531dae564f523, e57f7ffbcb5ed8638f25b7bf6d2002498718d4b37146695216104a5fa9398fce -89081fd95382c0dab6fd97f8b55c4751ec6b8b6933509b046ed511ae4f55e241, 20d605e541f551908322b7abb4fe9e93e730ed4aa5074b51562fa1a64d700868, 2e8c50b88914937dd99ece2bee0d9cb28dabb0dc287a88f9793dfd6ce0d0e16f -a49cd8fb9f54b998946229dd4522e2c4882ed9c9dbc8a6c34265b92128a62cd6, c2f20d42fbdb15348cd28693ff84c4dd724ce88e53791b0cf34a08f4570c4290, fc0eb2a5b418bf22d6fe9383bda5c50d00cf5683f15312852c14c39686ae99e4 -a843a18c135bbd5e357494024e74fe3f78b8acea215adf1ebfbc1394ebc315bf, fb830c864f7c0f44277231e8f90b9fad6b18a9ed93e6f22ccb47b994c871fcfb, 39273c2e51527db3373e7a9f1c12dcb481fe96b0f7e3aba0a25ac3e9bfa9bf6e -788513cda60fb4d4d79978770fa2a0e668fc2bdde5b31438caf7904005d9c4f9, e039f2dd844f4fa15bcb58434c638b7581b78417d8f194fa892159de37629671, 3012515bc6e96cbe12b39ee682ae820c5f0ef28000e0a6115eaf999398be9946 -f6b5caafc27c1480e815f83efae6d59036e43b5ed3461825b8ed1a97c9035c90, 1c018c9dc29540525fd6839fa612fdc653267788a1d348bc5fcaed67b4786337, f7e454a08bbba49131f2a0b92a186fa206ce34a89e7611e30ac71e985ecefc54 -b6c664bbfd57bbc11dc06b9f3386d0fba9d9365ed3b355566a84df527bb476fe, 9e00a499d535c74ec6f9b5518c10d89666eea7278fd136fbd8698ece3fa121ae, 5af73b067b082937d8da429bc79c8deff9516bc8ecbdddbbd86cc093c71fd483 -7a2f60dfce65bb8b5e1da528dab2f1d9afddf1a4eed7c42444c46c6d375333b1, b56379bd6ba7d12adcd5475cbd187c2ccb3d04a9812612f10bcc55b0255ef48e, 9d7f6aada8dd42d4a77c3cb72a4c3a154f52d0bf521984f95c7d7dc7ea5effd4 -020a3e31fbcbdb141d331f58b22de82caeeec8e45e10d24d9cee6abdf3cb4c97, d9f5d5d3688c461939f8543bda3f1d7f08d8633c837edda6fad460f786409fa4, 44bc0645c7f018fdd9637db6d6081ce0f05345c1224a77778b1269895cedb81c -deb8723fdce92cc7560ff7b86a9e8cfb9c4ad5e230bf6851867eb6dd20907aef, 470c325f0d8462c6ecb62027ab95d8167cc14f82c7a3c37a4bc955e53671f4e6, c31cc2dc1ae2faf13fcf42f243948e0296a7bcfd3028c67b768f09ec713d4c48 -effdf94de930978c17bafd1aa739af3a6a7f2a41bdb4d8bd1d7d1da617318803, f07d705f17ea414d4175f677b5418a2f8fa932d59056d8a6f3a1f1e0ab137fe9, 24dec37c04fae5490dfc019f27018d84bcd469e869c05e1d3200829acf96fd88 -390316e9f19808a5c455f0f12bc60da93761fbae37e7a888929a09692439d233, d58f6ee143efb1e63a5b5a57539ed4c72e6a3af509d28c01bfd731c2b4f74e4f, ae112962be47b89064e049fb0a1ad7938124b9aa8904934b45531cc218da6428 -85fc8c83b8ac38baee564bdcfb38bb6ec3e0374310eb1433bd4173af0cecf445, 94e122de3b8e3305a50723e9f99820c4b65040fcff75116e1a874fa4476c673d, 06b5b168e9ac774393119103235b1713942fba1267597c7f5d7988f5a726f1c8 -7360664603aed1ad9c65602983eb7b67f1590cc82ef7f25ff66274d222365c10, 2850a81dc5e02a6065944e06e309bdab0fb3fc9c36684eb2b188b91b77519f12, 991c3eadbe12f3b62417f233b88bc9c01ebd72ab813ddc46bd8d8fb4b14f20fe -92b694a02c91655d09b2009da20d815a117ffc5e961f60ff0979a7d546159bd3, 0bd57ad96104235083fc28cace48a1172e24bbaae22e1703ae850b156767459d, ff6a6857937ec0d28cf136de502205feecc5adc5292d97faaacf43267b57a498 -ff76be3da28265d978343c1dae12e5280590dc8bc1104d5ea426dbed3e9c9b79, 8c8273008011b2b8db1f3ed0fc4f6170796488063fae7c9a3799db11b76bb792, fa891cfd0ae9b18f70cd88524f60f9f21f67e99ba7fa82eb7b773f1d60af684a -3ca75a3b38fc38f828284018b766cfc550893804a7f02768e57f75b9108c6318, e4d8f5e4968860c267b46333130a6e715f06c7076b0645bcd8632143be6c966e, 9d4e67d85d2b4a9151e0c39ab09b7bab4b30def80ae842d76f4ddeea7a4e6f4c -aa3792702d9ea1b9bd856d7e219f8bd60e7f592a9f18272a1755045311cecac7, b298a23a308b66bc6c47081e54d16edc0df2a62b5414c8dbddc4f3bbe89996de, 2fa29c00fc00eee115ae40792c57f12cb0d67ba1817c4bc62e2de41ac09ae641 -66edc4b15c50309e1ff00e4149c7b866bd87bfb37955ef72154d6e0b11a98123, 125d810863a4fa29d4ee15dbb0de9cde10d1ddcc92bd428a160e62e348f2f0d2, 95ae367420d47ad63c0c8203a1f823610c4053e4aa5d98e0bf6bf3363b92b157 -96c44d7adb091c8964eee8b7a9d027f5aabeb870d65175f603a23b3a99bcdfad, bc0d26ee4446e2c476e3ae722d458b5bd7bd80f0c6269b0bebf357f41b9e2ee1, 12d0fa7baa38fc1fccc1fa803cd34a30d2915650f3964302d922f3328142c1ae -51c31c928fb63d8cc0356e037ae08058e5c78013caff5e4cb7e4b6bef669e1e6, 7b0190fdb37e5fbbaf448aee5cd57fdcafd4f948bfabc7aa77bbb6b4500e8e2b, 8cc11e1166a8807e462e435b6592d2ef711318d791ff04a02e874041ad889222 -4db1dc26a268bcb20965a09aa5c4e8785480cffebfdedf66769b278e4e22a692, e5d570c6c4544657bbc76e3ce6908628a110d00a20bccbc78cde0eb1b07ad4ce, 2ca991e45e050246410bbff55e0d5a114d299a4277ef76538e2a56a67a421e80 -3eb7c5d1e8c19e8160439c39c7fe85ae2790b98c29751894c06dda33d4a99736, 64117d02c50e1ef08652aeeb3401766c0e4e98214effdff6eceea3ccc4ad4f4e, 3c52582e25419fc856f3d610782f65d40812c3b0e032d18cfac388a2f93c32fb -c1f2e34040c58af0a2afe512eefd2344ef8d5340472c07c2248f9a93d9df5eed, 36fb5649129d2c95856382e6e79c32fb962035a2693b84a9204c972af0e6d471, 63e9067d8be99e43041abbc6892a34c71e40410801e9d8184173647e44210a24 -abe91214cdf98c6c091716c5234b6335b0b705291ca1dbe815de4362e86ac7b2, 2872d3ba5db72cc40e6d5301ebd18f6163987028f2a6adec51c1eebb36632813, 3e551c41429b64929ab5a4be7b4efa71f19f16e0f876300ff9aa24aa0efe15f5 -8573f710b185747626aa59f01f595849c816f16a7938f6d355149e20b6ed9bb3, 38ad06dd657ad5c3cc02f23178a6f9e945c9f9feff4c243693191170d95aea4b, e7eacbb5c2ac9f4a478f7ba94c440694c834bbc2711f7bbd3138ece7a9476c05 -6859690dbcc8faed26c4277f319649fca59139bc79deaaf08a1eedbabaecc09e, b1e94848bd60cbe662179b56b12a70ded1fcb700de35832cf060396366cac545, d3747f9c833081bddd127bda78a4566595e49a931d104f53e096b3df564d0270 -6b94598d0aa9f5bdbe420b2e70c924b88214c7bcb0eba8129d23732d486d5033, fa3ec579bc83c6e0ec586b0aa832e2658f58f214d5a79f39c51e73db2ba10dbe, 937750fa1403876c8fc285b7a69d16e4278d7688c8d6e2662892882d4b56644f -44b93a544a5faa697309fbad41a6e504336fc938f6b41e2ed9d8eb0e65617d3f, 05953dafaaa79abb5173ace90a589393fe5c31115f17f1149964d4d1ebbdaeeb, 4a15b0dc684dde8363dc3445cdc1802044dd3adc3476f544ba584ee548a69ea0 -5ed0fe551f0e63a22c57e24b87d1608d58b51b6b883faba8d57c0d6eb3bf2bcc, 67832e184ef95d184a49f72a026fc5eef5cd4b0d9f67c7ace51e3ab4caf342db, ac552bca1361ba9a6b2ec1d613b54b3fd6245ea3bdf53752d3def4c7d05bbf11 -fc6c408947870901ffdc04994342a51be8baad4717c24e7130f957126b4d3194, eec931e012e37518236012e7001f9b503ed3aa82c0af99c540de1cc3fa127102, 92ada1eb41a87ed9566006f715d10e35dc61c928863334872c04bec573d3260b -af83730fed2141036542390d74124765d7dcf8c4320f1e439f96c1622540c7c3, ab658dd4ebcf765a913793083de0960156e0e2ab876ba7cbc716d619f232473d, 4f2f54d7da8f3d160151d9d57d2806aee4a87855741a9d3dd56c0cd5c3feb843 -9e92ee5cda38cc8bcc8ec45b8ccbbd9348785a7d0b7de96552aab78e4387d2a9, 390b33b7313349c342d9446e9b33e8333011c5fcf80fb4b4db3122003b90c0fd, c706c6c47ab7d407bcb52219d5be9d0a60bdb7b74cbe63fb78275d385dad9989 -b2983032db9a48d43448b85a1009109377c6a62e69012e0985ea79af7b03357d, 03999281a51d106e484ff3ebc0a54b5e40eddd0347c4ce98ac49e26627775a74, d2985f1e64cef76aad360d600e8a808087c774bcd26ea15a89ba80ec03e6b00e -7f2c1c290c3d1d9278fdbc9455048eb45cbc3c68dae466db096e60ecce1f817b, 039cbdf935942e700ee5acb97b8c2c3d7cdffea610d0ccde886e10229270d884, 6acb9b35c1c5849529c548930fca6b1cdb28ba83ee847381607309c61ff93714 -d6e43186c0a2f76e08f6b1f4b0f6e32b5fd07f26beda8ec4cd43e3c1d3cc754c, a934e9b151cf89247414b14e0c33cf1753b701427a0c2f88398ac623e6f71602, 93183156167ef2993da1b3f8f020cd33650965d30d05648593d841b8ae1f5c0b -9bace85b82dc043a9238b47ae091456a7435ec62f9ab03e8127d74a0ce127849, 17c12597f778b881def7ac31c350a4a97657fc495f0caf3198f84b6cbf2ce565, 130b2d55976439f64ddac690bf93a465072bad00f11b6f110a56ff39dab93ee4 -5aadcfb02c1776a0442e5fb4ba4e71f0b1679aaef89426364b4421a7485cca8e, 6b428e5dc62a462e736c608a668cfe6160148456176075716502abc5bc5c47d4, 0541a9a2d1260e8f5eb5781cfc96e330f3c496980faf4d8a4b933d8a6e1fdf17 -54a04fce072d2de00a2c096c2ac3f9240a958b8a20b9f42c72e0a054494233df, 9d529150abbfad05a43c31a7f4cba6100b4fc7f996e6eb5cae20684e05b557af, 6f06cb5b3b2bb2eff37f4cca922b9002ab305ae3c4da9df734590accf9b8fd37 -ca3cf1f77cbb6aecf5c8960996cc069c3161a8c66f3085e1df233a6cdc341a7c, febceefa10a344254785d7ec3450a23bf48cf9d99c6fcefeb5a336c859444535, 88ca45323379921f9a21960b6c669e8747ab483bd8902100ea18526f138de5c4 -02692909c9207dea17c4f2c75772f05e88a7a5834922caf2ea2817c9e96e6c96, b6ee9e5a13d3ed89fa496dcade61765a4833656aa5a37b8b954ac821870a3cdd, 388d76d08f2f705655c9ca601be6e3b4818e23f4ae3fcc2c672f71853e709644 -d19de2015d79061bf45c3d6ce36315064fa8f0b0f12c457cc2036ead743ca5ee, 77cb5a56cd07fec4b9a2a7ef620974abbebe6f5d171285670a52a94f14cbe1a1, a60ec3d2e3b8449535c235f96708863c66e90a12e2ecfe2b4eb8e2439501091a -745fc3714d4049c8160096f88e5664c685e5636a9ce4fc8d2dde99dafcb05b86, c64462d58638704e6bfea5c47887b1a89b596ff03de81676fa138bef590cfe45, 2af32888a2b52de4fa1a2569ad293544e8a92c23b5304d81c799aa641185757e -19811bad7098c30704db46dff71f3d2600a41d9512e8007d367502fcef4a575a, 9a86356c29de8bf2ca7b3807176fafe1342754dad9c12fb4f608f9af566c8d83, a8bf8823b0fa34beaa74ad830962f4a2cd63b1c1cda52db88d2f471548b93ffb -4c3e87b4f0e70a828295308df6e33f2da59f87e12040ed9cf3609bf8cc377fea, 776e66a38d6c096f633502ea0208883b9ca5edfbb0a36f7278d53a5c5758cb85, eee223451d0fb73eb538c899f3e6fb45dfd7b242cd1212815cf4086dcf8b3f82 -98d1589d25b81cf76bfef6fa10d05ee0abba04dde6adda866981691d711bc4cd, 17cdecf22ddca6cc6da3f07812dff0864a46c9a78ca4de3cae649673e394866f, 950e5ccbe6ecd040bd7caa264540e2d2c6b034e95c66ca81016edf16fe50bed5 -a0aa0b1e81f5e34c4ba997caa725a588538c0cf7a5b91a119a3b8512a0a9e672, 1a429e26ed67bdecc373dab6c1987c0a131283f0285080a401e1e1a49d4fdc22, 9b8e55a914092654217343df0e2223adf39b376609e74a9b43ed760c21f88cfc -97fb9d5c6106e38ae1337818ab84e67f413a22bb8dd7d27ed5b35284400cd4af, 5a89fb5692832964aa04b3d69234191c6a539d33e292e01a16c16482f49c8619, 2c9222d6a68621812e11e150a3cf67eafe9b6013b48c1d5d368381b0574bf95e -cecd182286b3b4ef1d134a3f3a5984397937d43c077d2868855f022d661366d8, a8e33b796496a702ba70788dc56f80affa3023763191c05d31911a37af6b6b14, 0b240c7b3a2e84d112864d3bc36841eaf82dfda633a625f9ee5320e4c37c9dcb -b181d274cdb8d6b2e941929f84c8d1ebefdfb8473b2941855367bba11f498ad9, 62c9c29a2c54a3c45cb7d685005fa4cb4fbd428f6c1194644533b47f0fd8ec8c, d2b348c04fbf426fbd9b45c4387a5cbcb3fc55006e3f30524f17f5c881d0e734 -4d7d3efbe571b5e5a28d43b4062108f8dd7c7173b2820c447d5faeda40ec6aa3, c79f6a527481aea82674625809e2767859b85469d3fe061ebae1f3a37f214867, a4b33fc3de7c7da0145fd004118daebcc4a532b01026ec2df5dde32b790a6a8f -89eadb841a962cc2804bfbb3c5596d91bf23268946278bfddb0d8a131691b5c2, d659fb2b0a5c909fa5b8a5d330880781f8cdfe501c6fb1f61d909f84ef94d759, b6744bc23d838611d0d62bee78445e8f42c6f07ea488c446c3809dc0dc65bc57 -f2654acab4d56dd356c627a417f32eae94710fdf1743f5c4094e64280613340d, 49b460cce8b5332bd591e0fd802138c906739507564011c5cc46ce75114f4863, 563c6fbda56e4b632a20e67e29bc6cc1a8b0abc185de1d42ef894a83a11d03c2 -d60e3903e877eacede78214cab8a4f438ad325a431f5546d974d17fa59d37325, c8bed219ac91cf3b5cd37d64aa3e97a0ccad0088f62179375473d1bdb344ef35, b4ab1740d21ad0d0cdefa7242c0e6d24e2f937a20ea30dbb614a0f165423f12a -91ee7a0a76142aa4a90093dd8438421425b423f3edfc6c9edf9baf88cf812736, 718ea9fd12b79855aea360d687749d10e6bbd83f0808f45e641cea9485c9c59f, c52d911a47bb2a1221b9b99db010fc2f32902acf64093649b6eefc718a83033e -4e43c451a1da9c7a9ff3e647e1ff3d482dfb2f8c0896c72b1cc5e3c6aae6d768, 76c6fb46bd0908122886866e3100ad0d66beadbe17d09b4a106bd43f72cb4448, 79b8279c949c450e963cdafbca8d17065593ba08b01f29f7f0660a717f712bc6 -e7587ed1a28c3b21130cebce8b18a68449c3c60dc199ec409803d26748b392c5, 871bea98c3ccd580b5adc276fdc0965826ef498453989cb8bb24fcc7a6ddb7c4, 8b28a9ad40a13ce70191a129eb4a91caa6ae569fe42dba2b50f03f1bd5e3230b -66a6fe30b321e974e0187c8f581855efe7226e01720df11977415a61d486feff, ad62901f7644a7d589f709bba23596965920632d7944ef7dc4af844976d5ee82, 8897daaa3fb648625b18aad6ca8d4354c1c759a65998a1711232b004ae08afc0 -6cc65cc5e89922379b531a4a75a87335cf8ff6d7192cde374778434b8de24fa5, f54c1211f48d85f7612d825eb7c441bc1ba234cd1a1f0208a2d39ff6b19a303d, 9a09a532c49c62fa9ab15ee03307824c3cfaf6c9ee8f8ee48d34ca4612793fff -0f3aae2b990b876a38ea684fa6ba1d9b70115875c0f13f88e25e63ecfe625481, fb1d76732fade77381001cdec91a7c61194713afd317ba0e870eed3e55d00d90, 119d558a2e9fa5be2c7e4a72873fe4b29492a796b9373dccdf904e96afbc6a77 -c02224448aaa7d172804e33b03b2a3b02e308d2e5092fd5782a0ec7a00a50686, b9517b83535695e2206e4f5db978fcb6221b49091ebb1fdfd2ec8cfcffb4493d, 75c18deb5d37fab1160de90ccec02cab3012a5e849ca81b2fbad7016305c905f -904e1a981e43fe8dc6d9d44399b98922ab9a171a7183cf9aaaa8731d0ebd78e4, 6840e17f4c709a2093f2b6a2bc439d7b778c0556c0fad0be4766934604c50f99, d020979be777c2b461ec097effc87fbe8985b5b62e4bc9f5d6af62feb2e7da52 -d8ee43c866887c55eb8097ebf8b1d21e32a67bd26f6cdde52b4d707b5e6ec9b1, c1e4f0125eb89db1b7bd733b359ab6fb7be47ed9adb3c4528666e0958c89874e, f5ce32c074e33d30eb3ebca68d7b8be3f335f3597460f7980c25c87d3ef5620d -9b136b40b2d8c82e652add35f8ddd6f235eb6c21c86d98cc8575a1afee821661, 97782c2da4b4d54936668e7fc1c07d425219256a2c504a7bdb0d742f1506a8e9, 8cffabcba8b0e5e85d1d148ee9d20540672e84cc22270c9193c9d6d0a1c9f6e8 -09097139e45f4bf12f8d51ef4ccdfe35709c57b9dcb293460441c65dc4d5664f, 824c00b3d3977e9abc4117b755053a42348cf403f9e45fa53c383f0c666dbf61, cf1934545531f11bf2ab60bf96567df83a2e35609e20af3861abe6de733b6046 -eb45f24e78505df5fa40ee4fd53ced00a2089b3c447014d052d5c3730d0cab2f, 3029e6025c04d2fea31ac9a959bc5ba91826490d5254d61b720f0d16068578a2, d6bd17b8a7303cd3dd68f814571332ee82a7848627d2606434a159692a2f5bff -8ffebd740fabdd74b0f2995e2ba6f3555dd93d0143baab454e57a923e5072c4c, 85ebab13178cd1bdac0dc50e2875e27b5bf81d0e49bf1a2164fd936ce4491ec9, 20ca606a6f8302d667a01781a14dc69a67dd1e1104f3101c54ba4427e64634a2 -7e9f1954328e942a8e97da5db18c9973fe5aa4a6ba2d5d398d34cd10461de8f8, 3393db3ff5d2b08794d55b63017bd165266baa62da584c418145ac60a9692a7e, 6537c6b49d21cbe2c967a6bf18907b509a1c0125b4a686abb53673789381656d -b94fa02e3c6530273a8f59f25213dd60ce28f3f62f586de1796b99411c59e785, f45f6c6bb06178ea7e60a77a405e92d2524acb4ef51562057dd851fa70e88d0c, cba0cb6c27370c4408458bb54cd0c5dd8d7bd800f1fad926e3d4b13a865f7200 -31fccb251a7b4e3be1a5105d3c8d10d28accff42bece10cb8eed77f1342073c1, b42a564170326ce44045284843ba9093d885b9e2c8de774eea543050a018da81, 73db78c2227d51031673e4eb648204fb104a8380dccbba1a162d6806dace9201 -2264bbf7772edc5f8ae4a6b6afb96f70c3a651e42cf04c85dcca0de761005f4e, d84c9867d2cabb75feb8e4f48045281bec0d1ccb5c277f97d49d77a20339cb56, 8099cc346a4a1fbd59194cc0cb64b6041ea7e462cedbfe3e23e8eb893ed3161a -ceda63d7a796cf2088086095a1584881e98dc3e6eab8b29ae9ad2b73740d4008, 82da86b1e4a17cf1373b85510560a89ddfb7752b5c7d074f1405601a868a57f7, be295496181533f9cdafdc6a052fc37b7eb0fdeaa92b20efef749a43d0e9319d -058f37dcb55d1fef2a2c80285fbfa7d5b3a0c26e4cbea092275304a3e23b4a8f, 61e05614ce75d0f043759d552f39d8d628926c9721ba9a67908efdd444af97fc, 3346b12499be05109d819632734792c02dcc622e25fc34fcba1694765117dce0 -31a8d330844cbd5232678db507c8c307b52a5a3cd8fe99727d1bf5ab8ff593c4, c268b2e75ea2b48fd3404702ae7bd695dc0394f04e3ac959070b7f1ab5b36616, 4dda5fe28a0ca361f8b57bbe042f6494d076810005ff574414e5773b11871e77 -43c27087df6f54a419d8708612902a7524575d6aa04427613449576c74b6d4c4, 7f0b4d51cb88b39ea277efad0ea1fd131633a0c3a7ee3bd19daf316e9cd6e4dc, 9297f658efa46f178c44b2467fdd8af261bc465b53d2b4a3316ffe5c13402388 -d5322f6e70a8307827d09f44ccd2a518351c968f5e2e91866cfee3646ad99b0a, a3e874dad0db055379339ab1f1c99e79aba6412713378c92daab5202984eaec4, 9710434e6ce188337015eeceb71c8de11709c0e9a5e03a16651d704b8f1f5f46 -cfccb0369ad5c38a6245666c789c20f01dcd27acbed6d7f4497eabb5c4a58711, 4d7a0eff04cdae99d5d40dfa883db5b466d115a62a9cfe55708e7f152bb080a1, 278a587e83fa55f222d3d0a79295fffe01d44a95b38784a3273daac33265340a -612143b81a9eb94bfc5d2b957873e2068f63494254d1383aa53ebfb7cc1a7b19, bb08e225e2dd4dda24d825af88a137b92f318a2388e27453b152c05c63b47343, cdaa899e021198880e3504a44a8a9f890a8878d3bcddf7ab51cda12c4977ee99 -d5b8ba4bed1f33b7f6972dbd4cd4fee4f08ac51483ece715a9a48a5489213e9b, f9f3b06271697ebd61cc49c7bc156a80addb9fa7396066f165c0eafd23396b26, 9d82f95e4bdf527573f5829b511a8bffccce02d3a376b26a708b05e38266bb00 -5b6b322dbf49d62af13c01e17688bea3db573a37084b9c9d01ff88282bdd3ffe, d402a37a3bc76e9bda73a9f52d1f87cd230927a71e050c5547763e8af093ffe7, c06a005b669559343aba233e632bcca19c575dcefd163556896414a84576630c -f752c7fd5a03ac805af9fb31b34323806621bd73ea0fa6845268c90d7361c677, 40830d095f0cbad61ecf8547f48c4a716fe4502afb6f2ae81a757e40713c95ce, ff00935ced29a609fa7a42d48aa902b582f2ab0ec09c443c3750236731096c7f -fed6482ff749ab302d3c327ccb5770d04896c51fc690361d97c745703a7a542c, bff03901f4c92938aa6fd0926aba318f33102b89c3a7b56c0c856640119c706c, 075cc18e4c818ae6b4ca70a49643c381a61d5bb06062fbfeea7aa19cb90e1c52 -603f14ce233ac8216b87f61198aaa2ef184e2cd603b9018e338f12b9464838c3, df72ee92ed57bcca5d578ace54f2ffbd6a8b4ab3b3e73f9ce2208bb83a060d7e, be6f836d82fdf6bfb06c048ac4d655d091c700dd2c9dba4e27ebe9282d5d8f35 -249467fe22a33afbccc531ae379e1503b47ce92b2e8d6109f1ed7c81f1713e0b, c682a6a7f46f2153243dc263900db374d8370a3087547eaa09cf551da05ad5f1, a39da45a187aa264b7fc2c42804467923154294df8aa4396cf90029fb3a0eee6 -95e54a50b13bf09fa0fdeec9528216cb1427532cf047a9b71314ec1ffc508daf, 8afbf1ec6a224e501caeeb33def3b74d25cffab29aa28db324c20eea452ebc30, cdd13c1d83277945e07369c1a705e3e2b3bbc17fa5ee21748e79ba8ced006904 -6008ec9d1e3ae1cb6ef51976bc90b697b72013801863ab2a5baa940d3f96f0f4, 2415d537ee5b154c6bf6e92d0ab6595f6d95f8c70de40093497a71ac4ee0d493, b165640e4bf5a78840912a5e2602b590a3354b4cd973adbf81b997e91423e265 -17a161e669326737a55ddf40e623fa1c23fa9d2e015ccdb8ff1ead398fb25b4f, 69a3b9757d57e4fbbde90cef0e2bacecb35b6e037d7882c0a8a4f05fe783e051, 223c61f9f5a6e6f2930f04662ed52392692a69ee4b1ed787e799219ac838b397 -4f6cf1acd616b993e3861e2b1256d46a68432628718d04a6c9b480f738127d39, 9a63d472e2bc8f0ab20433ed359e4c1bf54dcd143d2613117685e959bcae0166, 43b8d4cf6cad67fb21efb1a829762bb8b1f4490313b020fb20e1cf9e602733d8 -a2dc382409b5ab60c359bd2a3f3b4aa64aaf63bb2215be73584a323e9ef4d019, eaf5bdbae8d7b8fa838bc6b7f8dde33b109e1cc5bc390f7284020af2016c21a1, c46c7514a655468aae7f423db0ff9d5070948101ba823194bdadbf7bfaa4c9f6 -6d3048a4cb1bee61c59cfbb2150ae9e31b5383080f34b2f3b4b5b61341df895a, afe2f43a2132977a9af02660beefc0c309a0f21e6c0961b91d25f8ffa9fdadac, beec92796c197ec46d7d9b3bf1f971800cd947c80d76d0be3c11cb48387e812b -8c20618bda061e9b54e2e4d844eb56af7537e83be01ff3e444d2855ab66308ce, b3d2335f0513e55fdfe3ae7200a69c12db9d9ab114c11bb298cdbc566cf262f0, b3374e60b1f11ed1aeb0b4740a531b6ed0bb1e37564d28ed523455d2825c09d2 -aad27f9d4c23118b0fb42de4b28b703e271ec6ad0bf043b7000874a8bf65700e, 48f5167467a999536da8ad289f95095daf91b057a53ece8d8e6a41a75bffaa15, 335db0143e7ddaf57223436641af6965193971373bbbcc403a3f7ada2b570113 -1799d17002eb69b537c0a894420e6942feeca7adb5ae0a56d6a5cc416e1d341a, 331e0331d348519b0014c3fcbbcef17953c9e2f06402c3d58cd10e3ecc18dffd, cd566686f72bd69576d577d24e243153ed844322e269141ac639633424bfc358 -e20d14b0c4004b884070ca20743d0984b02e24798d6265f9bffc091260f64738, b6510f4c57cb2b678bee9b70afdf9f03843bd59c32ad1ab1a80f7d8bc00a33ce, 966bea8315231695f11a787345421100ec7a8c8bf84651e6c6dd750fb94f2d8f -86a7f5426ab0fb2cafe482a4582ed448682609405527c0a71f40de6031e987d2, 4164c512b62d2fbfa08a63636e4cbb73f84941167829d9ec6c5c586a6002a0f8, c982582a6e2d77990ff22b2c9b460b6e1a66e2b7e1a8c21fba519755629ddbc1 -22767701d4863cf0fdafaaf78afab72c831a343ec7c636e2eac60cd88f57f8ec, cf2af935b972b0304c58707752dcb2fde55b9124549fd297649596e3c0b69be4, c28f1428fc8a1ffe9dea6ca9eb91447b91f928a1bde711c8e7f6b922c78ca7c5 -f5c9d2e5bd9587cc4be70a2683a5687718ca348fe20bcc4c55e96caf48f9dcf9, 7dfe6042c3f9ad9aea16f9cdda05a654fe3140a4a5eceaa81aae9fee6f42b951, 1871f11d451889c14d51e7f50a19a4c133141d60e126a7081b21d1a953005be3 -8ad3cd007ab2fc86d461b3f1c944f3dc414f3566fd1e39e5a7d481bd40ce1f82, c84cae4e234d399479f0b8a8b7f68ba3d2a18542b9be722a3856535f133cac80, ad54773788217d5779cf4a43548e11b359b2655b76ec51296807b6e358c97189 -2dc3f4e11460d9cc059fc528ba16f5c912c7dba4ee0920f1dd62d7810bc4cf8b, 371b322efa2f3f2e41827f8ced2ede62dee30940cf59ec9aaa089619785f4896, d61f6fc6af169debce3c1737baa22a2c24e2d0b85b3e0108092786de8b40ad2d -4eb8e114820a0eeeb1463ddb160f28671180417e537690cc54b25cbff2469884, 9bb83ce53d5b7439d074c6a6a59e70be74cc2e2abde708e1f823604be79f87b3, f0806997dcf99e81d7b0ff7468c9bea42913a6226837f13217e8347309fd70ab -25831dd8ea723cc0de8ff13793c7aff994b55aedf70e3bedea92c68696b88104, 550f8f0444f6384dff070d8543f643911b3d499443cc4874b4c353665f8196f9, 8f2648aa0b7b9725f6dfa2680050e51cdbbcd4310c1e54599325bd529de80457 -ae1b77168acd5792bac9ae8dac20a530224a9ced758ed77e25e31f507c39dac0, b5561c1d2ede6105a75bc1df5f5e46edcdf5b52af5063498b6cf395b412775c1, 506493f3a0b2665bad6b2536afde39c4653b753cb9ac327fba696ed5a278fc47 -cac226c1ac4249ba001c2c2059e3fa9760e786bd17af6aa26f9558af8fb43db1, 36a7a810a754805a4a34f2ef7879d25907e5ced9a9b581db466f9d309505a070, 62ed9397cbdf33ca7a73d5bcff8b4a8500115a3d914e2a4c9a15033060d4053a -d2f495a37e41595697edeea98decbe760a7228c26aa7cd4a7333eb695f04401f, f510c11a29560f16c210de88d0cda9e91ffed267cd070db76e21a59f6d99be84, 58b5591ab3c8b592af69e7a0467bdcbbc49e8cd17e44ffb31ef46f4f26018949 -f9b8449534dd8170fbf55e5326cb0c35429ae7eb49fe906c1fe1a63f840ec4e8, b7a0e2238215e017a4bfa36bf0e5f645e48253d2d243455c8a3c23cdfbcbbc58, 5681e23138eee67bf2daa8da093d9aaf7c86ba1677a83989e9a084488e1cab3f -6be36aa617a09f3d0f9b48fedbc83c1405e25a4ee23e5c2bb9f50fe78abe7462, 14add37465d8766be8292effed9720498501fa7c046c89f89a5778def66e2d0b, 01d511618422d4c360b9a39171fa26a0be319f0f0fe2f172e3db46d4a76833d1 -070e7a11e4c9dfdef4634bdcc5d5e21802c36a94fb3fa5944a5773ef933af336, 4922f59bb9a7e4aed28742fea242040eb7a3a1150e15eda0a1bb9dc2da2a18a3, 07a69c912d4b5e706df9f4144ac93c9531508957fa13b4d74499b46b093e1bc3 -1a23402c59d348eeceab3925a42a67a3360c36a904ccbca3d07bbe57038ad20f, 288afbdbfad8ca6d000584084034d973511e22ebe5cf5640dcbeb4fb93def5ef, d47371643f5a1dbeb84c1479aaaac26014af402678330188ba518de87252fcd5 -a6c22219274b18940c6f246876c11796ab5881089cb2389044d170c9b4db5ee6, 81a4613d8ef278a0a14b9f4e5af013e194ce463879167d8ccb91f530b3edea6d, 3fbde245ba96b25a24682d8e911fc4cf7e735ac2148dc1bec90ba59c5bd0a880 -01510551379faaf967e15daed5eee59f32f14a4882e1cdfaad1c5176185909af, d1e65e5a326f051b574f18c243d87049416b662f5379dcfc198678d85e859887, b4421641b225f0037190cef2f7f6bb521b5cde8480d00a0375db9de402944cdb -83331ee5a9d94eed4953996028bce779a2a1df5683e6c65c8a91918891f952e4, b9ac9dbc3a086fcb58dc14d01659709b354ba85f114ee7acf53f3f733c5a7587, d41bea4599b2e71e37900cef8a6d69a21bc40df1b7039825fb10c9eef8bfe387 -21ae89ba63499eb5413051241c023280807001afc799d9dc6fe56789d8f21324, 50da7ab02243c410659a351fb304f8ccc90249e648d8d7e495ca4e613007729a, 66e7d4399218e701af0dae04f88211c0b30143ed2add6548ce76e2d5555785f4 -b86bad6dad1a31f660e2d0458ff7203da69bfe85afebd2197f5769a77728263e, aaa66d5b663e8ccc509c2dddf03be1f8ad85144b88f270d6c5250d31dc39d1ff, 067fc0e2d7188bee01459c13fbbfbe8881058d66fe4942086f07a17dd18c39ca -ec67dd6168d63c6c34fcc0cceeac8d31038f1b46b0f953773afcff281a72db8f, 3a7bc0292fb683646bb7800a8e7cac89f6704691b0646901df9fe1f10e3907c3, 55d1f51ba65333e23d75c9299652ae606a4225a0781e65fd64948f25d99f6c38 -97b8b9a1f7c8ba6be10d8cc3280d9111bf6c55bf09a5c32bdcaf926fc72ca016, c645a298eb170088da7601c90f7d1d8843b14ad6a697ba0a1414c92eeff18b6c, 235f648eee5c00b40a0d97abb75087dc4ef94274f52fcf9d4928b11badae1e7f -5e422707099b03963eff74cd9f4c96c6690b0e5df2dcb631933dd15a4db1217b, 6041899fb1e2048a9115c6b2333a3ccc176ff4dfdefc3f3d30ddbda547275502, eaafdc4f50c181254196cb7c7ca9265019285e99817b48edc3dc86c7634178b9 -c05cefd7f48ac78803221f790f17f38a401960033ca9ecd6f90d7b568f61d236, 9df66accf9486ef15d5c5a4ed20e2f786742ed429878fd17c7b34d831e938cea, 6f080c05f8304847f5d24789f2d1de22e6da0dc8cc3f75c62f50638af574270e -47966afce233da40c6f0389a064be3aa084a4446582e6e31b1d6510d2cf5575b, 34bc6dd6e02f42e23e568620df49759641c0da270ca493be022112fa7e178790, 071fe44543334c6b3dc651a256cc0d8c733d9ba990913c4cea170e7e45ba0993 -65167976723e1794b0a71d5fd44f4c81957e59772f6a48c69342ecc4c30ed8a2, c88b179402b5c402c60ae2cec80c994052a1e93d4d3424d39da05d96f9d5a32a, 034fe3b28a9cd41bc42d048587b400798e1235b3839a2875f3a50c327adb35f7 -08ef4507229fc0a765cebe4ebd7834dab51dbec0dc01a0447a117744561999b3, cebc3b273453c4d0505754b364ae20dab9119ef45ac7b0bdd442ca167883e97a, 5f1026649f7111debc076c4aeccc1c80c2361f769c0c7571667168e293bd1e7c -1519c8a194bc36acd78d93b45a8642b24405f3601c0735b138eebc6f77f1bfff, dc4156e9d38078aaba1e1cdf69018d76450f15881fa860873686fbe51186b300, 72e0dd2b6b21b0c19b7db17b2ab1a62a23b49474651a78b7b072a0e64fe89375 -667ce1ec401d2f63611aea5fa20c7505763366ec692b425629b5aa1924abd725, ea3bcbc67f9260bb38757326e846b5715f9832eb29d2447faab854e15cb42f6e, 5920bd03c80a16a5f4bc9578747950352cb1fd4b5d7c90002cbd4a6c667f2d90 -a198b3f483f580117b8f5e756f63437cc1f1893ea523d38ca10cbc29f3febc2f, 4f0eca2ea748c94d56fa99991e158ef7d0fa201762d3e66f27b34c40b53e18aa, 551a9c1a56a530920a35b3d4e045ca5fb8d319f363eab410287c7781c1755808 -8c0cc6bb9ff332845f1ad854d8ce984ecdf3d18bd8d1d8e5e89f6f18af61aa80, 62f92a1aeec61f45acbb48f397e7fbbf8f67b1c3fb51794369375af471995c9d, 7ec5b11027d042c7953512e3e74e3611563858f466d52463865197168c1b66d8 -d20b721c97a1e5661ff2dab50f3021e73a27c76602448f5abc82b32ad6bf242d, 232063d17132a9b42cb2c6abd23a8d0c508fb5b9d7a518e8ff29960ffc2ad320, 7b18fce578779f36b71f866cdce2c70801888c6335d0d525a69867375cb6eaab -1d9b2b2349ac4723897ea54baa6f03bb52c1c185642bd0bc5c08958eda37c416, 40e50853dee913b7fd3bbf8b6b41ca4a803ed2fbc397a758e50c42a2e2f7ac18, d0f9cc11f7bd27361b2887f1e3411caacb4badd4bde88930cd78117c3623337b -d9956a8b24e646400f9886fcc5e73a4e4b09230c9d93b451e98adbedc13b8d8c, f757b2376d1a43a30aa4413cb3134782873a7df4656fd2deaa7b8affaefab79c, 5b736f489c5331b81b0ce5acaf0f188f0dde186958a11a97a57b381e3854c34a -265cdf7c953d85aaf65ab42f3d02bf5a455108851c6f040d0a49d2dda424f54f, 903e9483f84808fe2fede8f956af7f58245a7e0c5f04c0afba1eafa717f6c646, 2fdf21c547f1cfea97395a938d966b07e9e2e1e9bd7fc701433b095af4e09b7d -bb652d54f2b2b4dc2a832a125ed692a8614be973bd61c91ac5bff04cb5d2adaf, 0aa241b1de7f800ea73620f4b7beea20f0ec3568e7cf1c45dda720d4ff0da6a8, 68d04065863bf519df9709732bc1b7010b0a568dc47f5e8b371ee4a681832ad9 -2b92119cf8e35d165c89157ba365e3de1c3ce2413cd523bccfc62fc2bad8b5d5, b048c5b80184fd0f47d5a6616e11bcaf2a10c29287289ca377fb0b130f534316, adc4c711dba12f5198222896302e9a0086b00d3d6b8324cefb3da57bdf65c1b2 -3b5daa7f94445754deb91f6b20ff78ffd45e779ac26b723fa1ae3d0f69e6d5ce, 7fe349e4f7866d7dac6e67a431f3d36b5c71e39ec01da66adde657792659b6c6, dbc348a4e5a87e7683a1a0ea45a5f0dcc583c66cceb16b0075747be02434bef7 -69c91b51e34d889f8dd12347772fc4ec4c2958c9a6e99980bc9fed7d14dccc2c, 6807cc036d7a3d7307a1f5af2e2b46b90fb3a355f64c51f5b07e559d414a5db3, fb00f80fd33c06ff4b465f1f51f4dd34b0323e12f68e100f4e245a2e7e7ae16e -1ef349d1a77790a0ef89683c43b8e1ae349a3b16702798cfe465ce3562dc735b, 57b94df75deaedefdadad0038ca65dabf5d821ccc7ac3c008877e62f16e48755, 032c5cbc00c7045291f9478e01f4ea808ef65732f9ae898a8357d123881c5c77 -d7c7d05f98559a96837ec0586593d9cf6331600d375b3b2183278d0b5a481bbe, 63a5073ada2c71423665ba174f09685e956ee49cc2076667c1d09e0cda932f52, df74471882a5896affad3954aec119b321977eea87bde9981d2398b091103c2e -671d7863e5c38fdcb0949df4be78f63b9a3e5c033a4cc438f31c868b01fb4969, 06a4bf45fd91204cb63adef90ca63e1b34ea09d4330ff636594171299e4c2cf0, 944766f1b9c7db67f4dca1b70eb77bbe655134b4f5a6d783061323ffce0179a1 -d48b819124a6b6b63ed10eeb83230837e03af9f933860495b87d2e12da85a058, 76bf22593dc5f04bf829f3298944f88abc1cc4eec7800273ec36f0dbf927f4ed, 01bd711c9e07496c13c52847cc16881b323051c40a6fd4a1512471401b56c6a3 -c85560c1647756f46f51d9d84f2e435f62b969f78514084ff68444e5aaacf708, 9bc4aaea63e993911ecf80a3571cb045c43d6f87fb36c71064000a4df1d40cc5, 9087bae7d500f72f3fe3ef6bcb86cc72695ab1f1d4f49d83be2f4d38a9ca4b1b -f1997d4dfb24e6a9590a2f8a4e7d2f32bebb33194a15d8de0a18fad104b66073, b57b5dbe4ea3df9c25408accb70189046d701e05190b3f682c8de71e46b596c0, abbd0a285d7083b14cd9c096372812d73ae68e0b31468deb0139ce1423739cff -3b442740da33573c78096bd7910d1b4798085ed9af804472028638192ca558a9, 83ac8a0845fce2c8e1063e4d3116a545ba41246d045dcd9fab4c3ce3c4a757dd, bfbdf733d27f0d0909ff0970947a407519ef22819813e8c0be4705db105fc6bb -66c25f8dc223447ac9ff9ab177674b943c5b1047addfc4b1170380f36ad62161, 68cd1a1d070e331c7c2b2c2d3c8a05e98ffe80f82e66a3308ba582e32aad2e85, e52bf82629746aed5469a4b4800d8172b3839cad1e2dd070220b748779f15e50 -6437cd6386f22defbdf98181441c312007b16301c914a2084e32d240fabf1056, e1a4ce711e89a4af7ebb494098f8a262e0c97974bee31d13f1302d061a4d018c, 9372804bf22743984c8bee5ea58fdbfb2a829ebe5c458b96ab02eb8d297abc7e -3e8c474f99c9bcbdc8dc869772fc4644c64c3bbe6e152966d82fd1a67d45a432, 41cfd6289c52e4b6c7e8115502d69fcaee12c9eabcdefa607f02de4017871d76, cbfdb22bffee2068ffa2d8582399d00985e19039b7bee50b9409dab32feacacc -2190d82a7613b6e08bf5931b2c7e8bbd75df264ff3adc5a6310513e635ac88ac, a6e99d41b00344a468aa73fdff6b06923ca288d56ef05d8c649975ab019aa2cd, c4e3c57101957e0fc445545e1ab5f931ec2fa96cfd6cb107eff230f2af6eba9a -a985de9c06d73a7d4c0394dca3ba6816cbb9e6ee8e623c9160ef2be3a7423b46, 8e8edde2b6915209619beed37a1b68b5b6a4ab8c21c1b4846666db63ee66a750, b8c9de97cd1176e34e38468b4e68ed92573b6ee886dc427fa544b6ee79336045 -8d237e125f86941f76285d170dadb07e99f94f4d8a98b249ef58a7b3e8d7c32b, 014412134645b1af15457e08fef9a0c55b8464ea0f6c291cbbbc86613d7f8e3f, 9beb3186f23e4882d9a83589e44671add3b827699ec3c199f4daa17159dd4a66 -2c19f29b5cacabe28421c225d9b3bb579d41572d3f59d308cc3815803e540033, e46cfb0e0fa60f8d3ed03b6e50135d2fafb3725bf7ca4f801c3497017c7a1cf6, a71a475611d24d09cf08165c7dc31a87b7d382b40ee0e1cf13793fb740fddad2 -38135a8e98165436bd461aafb3d437109dca6434ca21769ee02974a36508c169, d0798ce03fa9dbe41a130f746e87bd554dd0267dc7408ca995768b14cb86cc52, fd1ac22a8a8b984fb78f4eb2dc4c93e4a6994dc7b4ee82e5a7994faa947a8942 -40e04951b7f274e0d0399fa13223228a55a7f93edd1e83bedbc23f9a8f7ca4bd, 1a1c4baeabfa07fa59ac5b6c4a6ba02ff3ab045123ab3828f803f322b458ad64, fd89278cfd7a612e5b833e683862b39e74addc6913101b7dec7fecb2434c73d9 -16ee42b64c00acc08eda8ace95c0a3b176a27c7267da4bd2d4b46ae8d9253576, 04b544136e1d651a22e7036fbea9cf04d74ca5b5319fd1dfcec20001e605da2b, 2beb8c0a4b7d8f3ab9ba071542da69ce5bac46058993be28620c628b4242c4ea -edfc59d3b550d57989ed565414251d1a1b05414125941c97d0d03a79c6f4430f, 27e742a9a544bfe0178001e6f7b3edf820b7dbda4bad2f73e5bfb525ad1b4752, 04e238047d2308e1a73a7308fe10b6a642b1a20d8ee5cd6adc805dc2730b2b93 -ffae0dd033c6aa4be74ef3c4e6b04a71acfc14bb540d421a16bbbf059449db74, 04457f05b2fc3ba713e8bd1f76ca526267cab6d2ec9522d0047a3090310568d9, c6b95f8581f767f611f970c03d9fa85a0c45b4d63933679491dae06af8823918 -7ba3f93b8ca7e9e1b6ab6d984db7c0a05dad1619e4fb91850c4cce8bb950900f, a1d1811c98f06f3f0e75d8aabfc1eb7abdcc2a71ba6a32ee749b6a316b346e3c, e10ef3f5fec9cc9561c86b1e93a58f604429b14c1ee523250b98c49dec558e56 -6ed99eb8402c44f3d5a03f4ce6e3543fba71cfe42f8f652fd68b4515e9f629a6, ea19acf000f2413b850bb7d5c8e6005b91c4dcf3700bc7df032928fd9416661a, 4bae703e5dbf4c2bcddc1d6c5547c71436516b04f976e58a2bf30510ba96749b -4e132ddd4373f9e0eae8ec4ed5bac6fc2f43b93a2c6c0aea4ac66174cdb021fb, ca5c5bde6c95fc79aed8da6883bec403ce0cbfcf08363d1a59101ec4641b4b6f, 6f8cd98c30f94c5b455ed2a3ad975bc13649b755030186ee6dd9c64c99ef0bf2 -d5fcefc5c435f4e1e70227eb34bb5af1953e71cabc3f646b192979dc03a41085, db055a0af87a9c703ba827b3f8c64736c112f4ea108f6c35205848c579a4adb2, e1423d2385914c0a4ddecb70b97bdd9b843bc9effbaf64481cac738d3ed4b7df -612009446f4c20312fe0df3637e3072cdd90a065fb4aeac4a3d86c6f0f4fb67a, 8c67ebb73154e252a6b77a9a471fa8a9352c2903e34053cd083a3e4c9fb4c5b1, 9098b300e33613643b837a558bfca72550e0d1133822dcf23508bb59e7224c54 -d91fb416ea1f1197bb728b32692fd645e5dc8bec41d360e9761d914d99490ea0, 32291a6e92e9126a6782e2c6e2d0b8223b4bdb0fdd3598dea08bbe29b9e586a5, 8fe51dfa1f8120f7da7ba7b406431f4a2a1639851fef36946d1a6cf7e0daa2f2 -ddd8791ea954db6680be03b6a089f572fb8b22065ac0870e18299b9552cf5ca3, c412cef8fadd10b4f0cd832219a22c79a743aba82e26e90e06a9b511264985e2, 387a605553e78493eb71afd9450bb1547303c6b1e38452b83d0900a7c8c238bb -18d915aaa40de409ec6a74ffc968bb9a41b97b90961e7cb53cea87ae0a1b5d24, 7c98d539771e9a04b5b4434b0dffebb2dc7e8e23a07e29ef96ab0f7402812f55, ee3a087741191640edb4f69ad329ecb77eded29c571eca82fe34837e2821174b -3e42df1404b84fedb7dd5029b87bc1c7a5c3427d0fd3e90a35f15891e6bb451f, 30e99ff1734f01d021f21e8c5106e4ef3f92a56d02bdb33a997ea5b4a82f224e, f0006fa01d1892f9d7fc5f785d540beee1def097f24ec306d28fd15a63f5b358 -fffb261df2fde26b9d2cfcaef870b9f9799add62e4cfaa790799ee867a624c05, d7712465a51ab82ee3fe67022efd142e1bbfb819bf4c3ded2e756a752b52d25e, 67c49ff22e5a9cb3839a7d7760280bb8c3219c1409b3978232b2cb04920c243f -e5db5055f53f58721535749a7eb3fcb5b6e2c48feeae0b8ed2d93e09e2e22b1b, 3774183b8854462f1e911a6d8da910bf2ffb91d7f8af7c11db66c95b58dd9cd3, 0570c9425fee18e5609b6a39b589f7998c4770e4a453a4364a5369fb58f90d2a -b206953348b8785c450534310c5cfc351c4ecdc02ec408b425e248efaffa475b, cbe474ae2cad669b24d10225972248b4ffe0be3a39186df10bb9e9ec25cad21f, c88a4f10b22973528ce7f6e24c9f04936f43d6c5d84fbe8b1ef7086421ce8745 -aea0fa925794d1eb2a21f7b84a556f76837d6046db11a9a855325521c70173d3, 5c33ba82050d2e51684616df8c5bb82d4908819fd3aab874bbdceb3071af0cc3, fff2c20907d32486f39b08fda0cde331cb2dc6793214cc48b5f09a27960e0c44 -e7320a78f3a447e0b409669136fcd937eb6478d953729c951d2cc7919506be6b, 0253a18d19f02ac5d2bff024f7dd814e3c38ae629bda0a98a5ed91713ccf05de, d657ae399033a5273c785bdb664ab970787b4206e55d31fb7ac3796624a6114d -7b726637913482546d96aa8d4556c523020c5b4d9e9e45d0197459a07a24d11a, 688f378b72cb6073687248f4e86701373ea156b861a15feda7c6503c625193e9, 9c01baaf226d072cbcbe8c61dce11129a7845015c09e403ef1cb4200cfbad970 -00c9659344c2e04979805a7d7dc4b4be7f7273a2f86a07052328bf3da8ccfc77, f527084c3fd7c25d8d59a2989ec7b707dae9941d73fe08ca06c436770bfea677, e3edfd20273a4c5fff5ea7e2613a1146b2415c8f57e1268b0a91d2b7e87e658d -a286f98a00ee27b018bda51a8f15930d36c6f01f8508de26e84e0ee99c736fef, 74c74307b22b9202be81a19e9ee8035b77e8353b650c91fa3bc7952c575f7fed, 8b3ea1be92c9400ecb0b7dbc72cea9a89ad338570b8110e3f874a13dca020516 -2d95aeda6886174094a284e5947a354c219db0974e4d10e03c04d9a13fb58080, ba2ce98ebab47879f4ab38bc0969be400e347a546f547124bb1c96908edb587d, 90b89d99b9c1b38258a5822cbce1f1bf199c5289f0a745b804ca02567e600ce7 -8b28956d345aae6794463f52550b5f0ec19dcea544534acfb78f507f3b1cac9e, 10516002620df4f9bcb0cb2ef7ff765a6c2a31cb5a0d93714bfa54df62a47421, 1e7062e9ca9d24d23cd44fdd8084a9588daed6458b31031e2e9fd135167f823b -91a2634c96dc25ffc0c20849ec0279c6aa4f8548f663444d5ec03b669801b51e, 2033f553159fb40034982fcb57b502767ba892f204fbf4e63205315f9e0a45b0, f2d1acd248dff0dc24f866644f6018eec6527c43829fa9bfea46b23e60a47865 -8fb38e48e61fc9b25da9cf85aa7e5059d97b1f260d2a4bbb3c104b20048ef8d9, 3f31635048b0548c87d5de5f1b3559e3a5c78baf833fea04deedb1ee22e17170, 59ecfa0f6260f8c56f56a126f37c39fd63ef4e5e163d65711536221cccbadfbc -a3fba6fdd1c9e2f8c5d2b688884a042edd375a83176a1f088f55349197eba526, cfb79b5bc201d36b2b1d9db43b6573be4bddef16da25416af66703cf41779a28, c1b27fa7b78e9e7e0f7ce4a0436b1d0e582138ac545a286bb6b53b1fb046b419 -08a4b12dde32a0fa7b723e68babda5b72e52bd4f97c2a18d7a49254484b0f589, da5b1bed232e9b3b03743ccf2d1af84ff92c5301e10ed90efcc41c9552ff3b32, dddd736b6fb4b5d53b4b05d3536ff43736f0e93240ef1bf6a90635a8a3f9a83e -8b63e70d4b3f2d44929134ab520da543571ac993e61c3f63107555af7b2b6f7d, 7a09c7295134255198c1aa0380444f08079248eb9592ddf165f3d75f7d4a1126, 5ac21c27b4ceebe8da1eb63a30636251423f62c5b5a46f17348ece9c4582d1e3 -f33701ab7e14a617f519ec8e08aa59c9e62e3e1fa36d869786ee8394beca0438, 3e1c7893e55763c300c565cdb972a3aaa3de19e0fb2a5c413493d58f0341a9c3, c3af42a0bcb94e141add35c6a92ef46aa8635f04120f1b30e0e26e964d4d197a -031ac5ef9d2514076686cf8f5ab6b4c1f0fb3d8ab56a6ce8fa7c9bb51c07e397, 400fcf5de07d6ba01004413ec81e10bec19e61966dc72221d367e14b350f2960, 1e2488587be279dfdab5b98717565c6f8848661360ee6bcc41f8dd82c2f1bf89 -0233ae87eb9cd9ee0d4a0acc8a9de4c90046cb8aaf69d53d40e852dec95a1b2e, 46da515069d2fa2c3e246e28fe7babaa0b9f41ce883055fea18bfae830ee37bd, a336b9d5787d5fae904cb804b295c2b4a331cd7df64235d59dbd612a21eb5adb -74fc94d08dffd453b33b010f685347cd40d49ba055e6508721c5298dca22118b, 194ee590a21f7d5cf13f2e49944d10bc3b1da098b85cab1720afcbb52dfb137f, d6563ea2f94f7f7dfd6b5dbb258e18c1935efc8ba89403317f51ccc60effcafe -d95fa74737926cf8615fe4866afc82603beb91681d36994da75f25e518a0f8e7, 50815b9992ae5f18c90e049c86b5669ade316950c2c8f29c2e5b591f0af34171, 45d3e704861f7d3d701bfd6a5b9c1d6c4f1cf2d7fd0d56f7b6652a04675010f5 -f754b45b3e40c1f69f20c916d1ec18db5c214b3d4aa8d0a214e87573d240d790, 167c5e41814fdca93c06c442fc816b86c9962f9d6bb82e7970aa301d486b3797, def836380127d3989d414f6b25aef0fe570baceccaf644aefb44e5760c120381 -33c69f3f444bc7e6cba597068b0e47aca608dd0dfc83afbeba54df250e979488, c56f6d0f92aca6e1b05aca5c80f51fb5a89f0eecc7f239297eb2bfed13ace515, 09e0f8f19d307848de26f9397774fdb3b8d8f0fbf54735f4838e2a031f09dfc5 -c4c7252ba51cb819e3fe6ff60db6386389bad50a7b7372bc47b13f9c6dff8c25, 4d0d06381cccf27f4f64ace2c9213c01dc2841759ec632be68a543353e910a5b, 1ec9b0d4be3a7048323855b4ecce1a4b38737faffbdb8f6bfc839e9b6de18e94 -4e2df28699e7a75681dbc8aee002996ce6b23d600c8ed95895b5443345000343, 7a63cbee05611cf86e0daef7fa8140dff8dc930f3ed021602a9a9dfaa94895e1, edfca78db50f73ec0c3c94e678e9407416fb940ce2fa349c1ce6d2bab652e8fd -365a8cf690154f641fec749ea161f072e418df8373d547d13455abc5d42edc55, 7b19592d64a49687042a2bac0aac5f529c1edddd1a8be2af73a47dc61478c9b3, 6c817990d837cb18d105dddf31fba923f5e71d6302e6cb7fda6b120fbc643ca8 -2e085510c9534106cd71515797f8e54177bcb6bb74cd0623076372300f4d53d2, 0bab06f6367729d80a6e1c788b8c85302388486b00af55b7c055a616b77997d6, 0094443b044a0037cb7ff68a29f8a6b3597550383b705d003fb511de585caced -fc1888612220e847af32302b27bb24a8ca2995329dd3aeeee069bfb27bdcbad8, 8f7098230b7c6384887dde532e1c86c91c0251f5de24c8f646ee45486bd1ad4b, 072bf5952577ccd64ab47b4521d9a8f25e034042f61ce2439c0e2bc5fb097940 -9586350d9c716fd4cc7c0aac521b6c7e9c1cfbb1876bc6d0674bbbd3db32b4d3, d420d9cd010e1e4bf108d7cfe2162b7b4966b3546f100df3798b078ede59c14f, 8066e889751490cc108ef9b30067fb76639f1a7d4651952d15f2e6217675380a -2d492c4043080ba92fb9260027562cb63bbb30da2b68fdcf40b7cd362fe50298, e4a129a536832eb203df1d5270d0660cab78444fbc4ab1246d305b6b0887301a, 5adcdac69d8391d9f83e88c08965588775dd84593c3f438906ea7518b61097ab -caf617923211e211a42b6457c91e33bdf8bf01c6136de35cc60d2132af47f238, 8e890ac754bdb3f31ad67081517c898c55a264fe50bb2ec1e12be20330437115, 42369f8e87421fa50a2fa2bba84e6bdd95c33e31a82f3d6a197baca290d8d0dc -ebd4b6ec53b0319f8b4a6eeac2cd3bc69ec2a9d49f82b2a8714fc5883b2c1846, b81d9d3f8ec1c23a688c9eefe0bc1ffcff2ed9a3386620e3f15d45167c649b8d, 145daee3fb0d6512367a74aeaee1e3ec155d6e4b545bc95f2eb06a428702bab7 -394e6d7dd8fc91efbe4989186c82561d26ef9b84d585aaaeec34bfbadb678b45, 492bdc4b8159ee576bcdb6d6aafc790a29b767579f9f6b078b235574dc316ede, db6134e5ecc765c7c8d28ae4a275b562183d7395c694257db718019d3b5a20a3 -fbc2a815abe0d3355870d460e2a27565bfbedb5c6037bc2b20fbd77d2accecef, ca6f6e9c6a77a1291ed524036486acf0a692fbced8cccac50ed9640bf8b091e8, bbebafb312ccc5166cfd2610b17cf77de6a60f15507afe4a23189e46fc04a950 -426eda11cd697b540e07e51a7091d5fc43a37a495c1d79cb9a859184472ba210, 3e60aebf040a74627827ed5e73f38a165b5d23195c5db020b4b8c11b78a31b81, 538aa5488839ee0a84947a92b353e0bae367980a61fa8556a7d391e9cc4dab1b -6fa4c7f84fed8b4cc1f329b53ffa9e334ec9bc904b7baa8f98b18bc940c39b54, 5851419774b1b646dc7bddb747af46e7065cb861aa983c0619326b79eb3889d7, f276c4c2ad411f52338986b8fb552bb7aa015a92053be69bea41859e28e53bb9 -c4ed29415a85afa311cc83d8bcb1ee5ae546c43375ad87163feaea22efdb3e99, 7ac48e14f3761a7d4af8397390f4981d9a17e689f675dab087589747d7bdfd5e, 63a8970c1db4b5eca0b90bc514aef207eee55f4edd2545a72b03a0f444f03bac -a1a2200e6c911d082a01edfdf93fa6454124b65d7ed1d11caf2764f09917122d, 58bad1f03326675b8c2a9988cc8b1fd01a9bc82e750f164faf4c61aa71f6b5af, 4e6fc5eedce6a7f5e73d20b416ca7f90e463775f5c5d3b4c2124874203590da9 -2e34a3b67f5e3e9759a40bfc3fa6a7f25a356b9437d94327e3f392f5600d0829, 0bc71cc04d418ea25fa0fe038e02c124ab9123a7bffe9cc182e0be29859a8a03, def995f773281ad36f9b3949f2c16f196e4d1d062408ee8d8bd5cc1d9ce4588e -0735c897bf79cbd78f922938e7814e8d102f3051e4600addf528b7730bb0d03f, e28da2625cf1c60be14c81eb258058a8b64c5fd7b19c8e4e533fabbfe21fd0c2, d1aa80b8ab1b9283c9b377925468f3044928df788b798ef9e52a85accf403b35 -619d6a9225ff1c06d8a4ae9501a4c719b315c312a753d42c9b84bcc612ec6ef6, 1b1ec878207e9c2b6535def957f940c66b34c5b01766f9ee88ade9c6702371d7, 4cbc70775126793bc4b445e5f3a76042be177e532ffc5dee69f6e42e9587d540 -334938005d6a2ba88ca49cdb767a70c596429479b01935f293b5467b3c7c3812, 12debba46841b8d84510b32921b94f8b9d3cc9380078b1df32d92e0b9239e8fa, d8e9743f12962443d2b5e0dfbca6b6298b21b265b6130c59f22f400226802fd5 -282b0f2f37adc23dd63cd37938f733246debcca0641f04469767c6bf0783de99, ba868bcb4cc13853cf67f55bd285ef51d3ff9e27fc679c00c13c411a62786b58, 6423e3b0c6432c46c2880ca9fc349e155d14119e8472a1c746de8a0bda44f02a -4ef5d58ddb8467b2223f340b700735676c44586ae21172de8ce62f2854c1ae2f, 2d354728f94b8dbe2fe4474c01643c32ea16fe3bd81a62fe8e81c4488c35a2a1, ea189caec57aab58f304a1a4bb473ff050ee8d37645e6d5e1ba4de9797447122 -61181e9d743e120931aaea370c74593c11826e1021df69bc2a985b4f51f4aedc, 6ef33875bdc4961ec0ba9ad35f392389f3e7e8e0af9cabd7bbb13b7bce503303, e39ea27dab83cea1487f805f3c6d2032521da772968f4a21c983718cafc2327c -e46978dc9010dedff0958148f248342238bded7c671e4220595d1bfa7db40722, 23d9a35aa98ab12beab8b1ffd4ac43fb9858e27e5cc7869ab8b5be136a715adf, c720d6ed1bd6f98c8c87b05aeeff7021b3f9bfb531e0b64f8380a5271a2fd5d8 -8f2434a73c24ddda7ee5d721edb4811bfff781d837f0909200cac02cb0d2ad84, b9afaedefbeb7bdd494e23ff9079277c8942c864d9bed73fea6e0d6fc1724a3a, e0e7a989816ea07ebd38776e5cb32e34e912f81f8668e9a47d5e569cf182f87f -4eca7a6981024e68b7b30ad6c4e370a38975407a38eae1ebc5b674c7c5f085d7, 3753dd2f21e6e0b1967f5aa515ca272f26f005067f8057dec34b72868df79b11, d9a197d2aaa037b2355717d5a971d44ea50259e6194dd0135e4caa95c73c244c -c7a43367e10eb1489ecbcb370ca91f50e8ee388713886cec0ca990ceb1a69dc8, 1f5535267274d828ffcad61fd177e36cef4e35e03ad0e2a5cd48a9cf32b6e48d, d1b1bc00523ca871f860489bff5151e8800fdd46fbafcb0a72e2c18bba001ea6 -f07544436356d2714033361761462cf5f6a07701b987e5829c3f848c9c270f8f, 660743be83554fe25e825ed150ab71206b912175b841328f74b0ff1dfe7d52b1, 062db1aac0c015093a1e71a865ba4bb1e3d3383f1fbe32961cb39e609e39b021 -300425c842629b528f788c86bacef74951f08277500ed12a5c9b028f075ac87b, b9ca7ea7be2ed8f2f4077b1ace6870d11d012d3328ae370d6dc86c2ccfdd8dfa, bd066cdbeed5fe770cb14af422d6bc7ed6354692d51a23581bce5930d0895192 -5296cb953b06dd5394a5394a1ec32ae04e820ffba7b9f112cca22089dff6ec4e, d9b93138516a4bd4535af2fb3557e9f30d0e0b3bf72157c7a5a9f1f23ef5ba33, 31ceadc59ceffdeb4ca02e4f50d65032e9ce0411eeab9151c854ab87c3adf562 -8014700aa3976c4ecfb592807a1aa538f50f275f597a5c6380f0a45826b0dfd7, 90f1bba00230dda4e05b1238ded5fac83dc8f27f46ea2d576829645e6775be91, 2e2e0178cd1e98d8ef76fa6b569523ebde0247396b8c7d1d707fe3281874bebb -92095bb4e83ce23d1c987de87f37744b562a57f080b0886db445cab7b3a243d3, 7818d7c8ba25f5d982657e3b3ec2b6a95011d6fc9bea96199d5f60edd0e230d3, c40d9fe26a6f80aaf4fb405f9bd69a52c6838831f1e81fece20c73edc04969fa -eb79a1a035e42015526b1c10670f88acf1adb4d96d6a34d7319a196639810a31, f457d88eee0121a8ad6c5084ab625b7ec1a9f2db7aff8a1893a29c7d32d55c14, 959e92622a733c5ba24b32da65eeea2e28728b7d24cc66f9dbb2bcc00b9cb106 -e84aea96d2f72ed7b8c1ecf8d95e1f4a5d4fb04874809e544ecf011cb7f333f7, d8d73ce6d2aaf6cd263f45da60172bf4360651b245d4ad46e105d6bc1effc756, 14db2c8cb9fc3dbac49ef7c77ad85f2516e3049c8fd3de6ed7a96f9406fd59d9 -0ba5e93577da4bb0898e3f922484385828a1688232c4eead3b5be632654e76aa, 9ee5e7fcea86045fe38154f531098f4953ad26aa4fb0e74e3617ceb7f42754de, 64069e17ef1671d9a5d50f61ca2737b113d97ec32c2c763a26784bf8d90e6ce5 -30673a3dba01a0c1ccd57dfadae3bbbdb5c5496348759b30c11c379741d5ef06, 650a84518dabc60266ad5a14cefa7db34f0e6a694c71e38b1ce55b980fd8e1cc, dc9720b617ae9529ac18e00f0262949d5186f218637924949c96afcf73e5ec35 -f4c4affbb72ffe03852d08064cc4f3e2e9e826af25e930973dc7019d11550ba1, 63b250af7abb6062c2385a025ec57eb929091204eb419adfce7030d33bee3859, d9e4ee29a00bf4dad7250f7739dee68d2bc2ee3d8302ff0c94f0fba976c4e08c -9ca9ff652ab43f98f5050af83b54ed94faf9ece57a82486c5dd1cea0f97a0e90, 965eccddbcc7a1f732c5f9018ae7b8cf281b8e72b14f71a964a90118c1e65026, 5f140e5f14dcbe12ff6283a4f7d0408ab8e87342f29c8848b488579060690039 -5be1693cf213ac3f0229a5de0ede913193ebf283e368f932eb52ba377c82ab18, cb55e9d16acb1195f63d40d496f9dc90642043667ae33958b2f6ea10510f3c75, ffb296ab8c16631e569afeeddb48f9ccbd5d2377ec3b95d8e0211d3df09ca452 -505d86f6a370e6631dc1bb86da9345205181f76cdf3d47b5905dfc503c4449de, 84f6f8094cd4e6d9fdbff34ba2eebe4c47b20d9a728412d4867e3c6b0027ebfa, c537f91cf2176785e6a687324e3c73dbfca5db70696432a0aebe07428ba475d6 -820cebe4b605852fccad49e89ccc86a352570ca1a324dd9a78631d237652fe7d, cb4583b7e20ba096706ce67a245f5ac29a83b706bc50715bc42c72f629da02ff, e2943d4d3c4672aa25750179e21c62ceb6a9314834d77753f65b6ffdd46c86b5 -1fee6c610b44df77cf8557deb5b7944248077411b23f38b40049fb7e8a60f438, e3ae538520c127315e45fb0a9d252f5fd10beae05df12fca6f7a779986e4710c, f2a6b6d10e82fe2eb9ad71164cf4003be2e34510c45a977349ee3600c387ea00 -d3949770c54f874687fe99aeabb170a4c60f15ab7297b0b21a0580767765cd09, 125cf82db1ad008c7bf212ae95d01cff58d6bf856a4c3e6e873919ebda8a290b, b5754e94d10c7636ab82f58e9af98b1c4206f3bf1baf27aee758103f4b04bc8e -2e72e73a96550d8c6ff0d2dd7e0a1ace0519922e15accfb7828cde87bc7e6fd0, fdd4ea21f55350e245590dab339942ce95ea2278e7988ab27a4c66e531f0e5b3, e328156a094fb1bce191e2e04100c161789195a5cfa536c9df68d5532ab07b21 -f6779fee18551108f61f108022353c51017b0b3898deb802482f21f9e717e6d7, 1f68db435c25f06aa2ca44ce670b249a9eec5ce43b69988bcbc28247a558a6ff, 23d1ef85687bb78eb9491b28a7fb1a5709f85fcdd61295058a4ad5ccb45a87d2 -5d19a19603f2f7b676ddc0572233b4659a2831facfedd1857f9d89144f26bc83, 44a7dd57190a7f74184a126a9de6c8c698497b6ebb2f70d75f99ba22aab55764, 2cbff5d0967655b69e49d6117616511a833c531c56017d58e816a6c919fd34ed -a6164d2605d3c5a76c17a81faf9e716c9c718ad6ec7f224d74d1a9842790158d, 79f51c516fa7e73e52b1c5baaf84c201a3f548ad99e0c3c6780459e4f8787871, c379e470fb7badef23e576709314201a025eaab49e97cf8f19d9a9caaec5f874 -0480bffd2334432f65da56d975189947a1c889028307b32fba08b2da06361fa1, 60f6b69bcd44e5ff31e3cf000fd9b80bcba9d255db4c39718412e62fcaf9f79a, 6511de28d7c070e4d547d27f0020298695101cce37b1c3119af1f23ca6f9ffb2 -57fbfe1dd44f954520cd38a301e77426b963536f39ea25ef9ffb0f924b9bb38d, 3a46a05690b880a09e1a5e8ce809abae3a66faba9fec589308469acc2aa81b7d, 9ff4d970b784f47145659ac1dc0be1a22ec5269355aa74e9a3e0f0f83737ee47 -a3371a9a82c3fa055458b73e789f40991207abbbd8d710aa96ad39c035fd4f39, 2d12223e30b9807e897ff984c9118031ced0e6632b98392eee1203c7925b1f14, 193ff8cdb817dc377ceb2f05ba9824013a403b00bbcd9d1b85fed8eeda452cba -3179ae84d6f8b35d796823e07570fd8b249246772fa6de00d79da7ca805ed2a2, 182f49bf739db7099f7271dede0814cd3a31e687313294ff028e73546cd5f126, e72eea743dfd933012b266da9ababdded34b8133998b0eb18e6177ef1750f6c9 -0f033754c8db71856995feb6266fd7b87260ed8b62f90479a9aaff496416e7a4, 3e1fb24dcfdc0c0771bc064312cb423a17ce0973ffa36445bac23974baf01ad8, aab125ef16c876eebb2e4dc1227fefd841baf14937294a29f4d3e9487acfdd0c -5d22ed58ac04aac43dd29a9b1b6bcf01abcdce46c0478b4c8219b1152fa952c0, a26d7e934d663fc01b21a7f2a87f9d6c4e37e5622a0518eaf3f74d20d9396dbf, c334d41a01ac9b3c89058e824b3d9a7f73ba622b6ab6da12be50afe8e5aad161 -d42190e6a46905ead29989e2846b39711770078bff4267442a5478272385ffd7, a4a1273f5d1daeaab49748a20a32b20a4df406952c36c5feabee53b2803559b4, f61584f3994c0ecfd0cd6692802dab68285f145e5562db2033202f1c24ab81a0 -6244042b8241aca12b0713b6b4f6130363f3339a292241723b05661da58e38bf, b6b8e9c79ed467e88523ece1a033ab3e9ab588014488154a0eec7af8d82778e3, 1905edcd02554e04aa7d982d933f138b257115ff2d11f9e53eef8ecdb8969839 -a8c9b906d8181f93990be961c3794980da9f43f2be2091e6a802e5b362579ef3, 7aae2bd149ede04c6d51d7b170850d62b7f0b7af8347497d0a33473bbeab2d18, e30711b0855f76550177348546f3f320e5760037bbf47e87d682dc9e907f47de -96ed0fcd5efee98b48c8c693e6e90f060454486b3827b7172ee59aeded9e97a4, 896152b00d3bd047a0a3a035b96c9874e26633243e945b0bfa42d1a88b4f400a, 087c7113ecfa43ab960fa760f0c8fcc699bac16438e8857853c6669260bde102 -25914744539f4633d221009f44f790bad23d021d72405a8ca981d82cdae429e2, b2ce79118037d0e96cb0265ea70556632fe23e6cf45d01c572b02216c6b2cc49, fa3f12aeca52f0862799ae46b30875ab44e155987f00e59b27ad6a17b2e56a20 -c43caf10826ff2cbd37ef25343041e36654d97071f7408f5952aa31a691265d1, b9540d1ecd88515a8815414f757e1d8cdd144242fa336daaa48b97044d629982, cc63a6b375092c9325bfb5ef7595b73861a5a714a5621f8e2cb76ac5d6a3aaf3 -28acc4acfa7861447dc0172ee7865058e4b0650e807ccd5d90619021221e145a, aade4c93c4cf893f05d0001af8e838d1460b9b2d731d097aafe8a03cae16bbca, 915950e205d758bbc7db92506f403a60f1cb01f3c4eec356540f5358a02389d5 -9a0f49950cba7712df8a4bdd8af656f057595d1cea80724501dbcdf2178b8fa4, 3eb4293787d40f7eea2bfaeafb79b7d73afe8c525a24d24faa0cfdebb815d0d0, aca77831c277c838b91b707ad4f1fa7e944608254529d69a798667d9d57e3ea4 -acd7984fa5578709b8defcd8624e0437fc1e4556ed6e635cd39dca8e070981aa, 6869ccc989fabb82421ea3f2a9f3fc3d84483d423545d348640cee1be4ae286a, 218ea5570f3d1e72000a8ce5d5b1572c576510717a86c2dfd7de6b5ba9f7a906 -17556c0da80581de3d4aaf527088400993bcab8c433b969fdb50ae00429f2cf5, 3cebb5421cc94cd9188cf2946fc8dab13e0397d6c94d3376c2c672422d9b064a, cf47b7cf47c06204ef6036e1ed28a26ae4303cbddc236bddbde8547743bf91a0 -9ad27082884403fa12889c89287c267cef0dc3553c7106d937931dbeafa23314, 3538160306662046f4e8e3761ffd4fde7b08e4859785d2e7462ce8bce14428f0, 3eb2996200c4fbbf206a09f28fc1b030e637e15979d72be6c154ac2685b43b10 -0050831b490db7d800f6974d8ee02ed8c01f5d495bd63b7503b2ea66adc4713f, 16da58aebe62b23b54e9dd3c7fecf6d88cfb44d61bf0e4a6128dd3c837b48218, 8d833c0a0da01bcc0c83131fe43695b0fc22e19446e20adac7a0270b6ce791f3 -459a6689f07312c67bf19df5f6e8e17ba7fdea8766f47ee651ed7e8bbe942891, 7902dc6c43e72d60d83ac6e5da6cd46d06f43a52cf77fc62545d3274d66f89e9, 1812476f801fdd1844c8a695827edd4473eee13b39bf5b1bd23b598d6e3cd8c2 -6fae52ad40bb44f65c26098628b0c4e07e380fb915d85ed144d4c37d224cf141, eee63ae8310ca61e6dd1bc62af1bbe6229c62de719a381e02738dbaf3cc04824, fdeab6e0f003e72a0106412f3129c3af09e22a6611c5c39424838e01fb82f309 -37bb97cbb35e3c62b36256213e6167afd484070605affe61a8c2ca4e7401a791, 79ab02eb919cc2b23c9ea35d4f2ed50f9773b3ae02e819ee449a5e97ca1bc21c, d165917daeec366fa2d104db9d3e59d3ace9eb6288b23a9dcec55221b4f4d8eb -543e09d6ae60717230f38c190779bff9b42eff4a183e4b6cd36c5a58396489f9, 2b6a52c794e84b57f07d8015c8a937116caf78d5817337d3a01e6088183694e9, de7b34b6fa7581b2cf90e48be298e477f709ce6ba995318a3892016deddbaaad -db4ac20bb0287e4942286836cfd5dfa5b623a1f8e2ed2e5eec4012898823e63f, 1173ec2d291f894d35f917a8a09c61a61440acc562ad9e35fbdb7744dd52c093, 4858e75294b2721078443ac0f41948962d48625dde7ee7687dec1b4d5a269229 -5ffeea10d9b35db9fcd56a588008bc092d3ef4bcccfdc69a2019605bc06d2115, c46005eba6d2764bfef3cd9d06ba6d2c6609008598cc0d09d2cc8568e55a3d05, 106e9bb7b9b3564073bd68a54d0a71e23f7c647c4c7d832df3eefa02b542fa08 -fa65ad48a9378d16306123be50084f02221088612e8459abc5643f8729e9641a, c54e7b26ac578a37840d43b075811f21ac752222a3fa97887de9e04c5b166594, a008d22b131e9ac1e2f68f65704ddef0ab70177eaa219ccf964c64efe1c4e99d -df5422b3bb4b3273a5ee44fa48423e27a04fd2ef3cdc9fd66c11c89175040efb, 35029d7e88839176ff40e0c25d1a9de4e8792944b4cab1475aa669f19f26f658, 467f2aa7cd3d46c88cf0f1a7aa81e6c7dec5ae43bdc840104854936e5a6a2059 -23d568dd7db2dc313122ddd5e05b025e28993b61a70aae704ee4ca16a62b71d3, 2ea09e6143aa43842b46b4721951475489199ed0b261f7aa677760a3a39cc95c, 1458ecdd053115c2a08902800d1071172d778a8d77cfb0b62674548cee862df1 -362f98ecad56c72c71eeb27d6ed5100e89901c7430b5c1fcffe81d536de56df8, 18637d9c4d3a77ff610cbf1b646c5b249f167d2f2db08129384c163b236af18d, f925e7881f601b4294edb9a94eed85ddc463553bd850a07a20c8768fbfbeacc6 -0d18259fbbd74b59f0300a9499e6cb920b88dd3d628a683ee0a7fe6bac475437, 08fc6feec5ac0dc2e3d8b5ba6154de176f53e818984485f1b1f4e96c426e6cfa, d29af198c241ae391fb5fe7b83bf168031010c030202ca76996ae17298487aef -bbedd62627b4dc692e6139ff02457ba44a7fb7b315143de2308be310b3e14929, ffa7b456057eae291af3ac33bad6f9079bdb73c69b534d811b952adc9feee727, 3e05c7bb1142e632659fdeb3133b9f8c48a02de7f4d01ca3087d75714aec929f -fdb058f869ba0e617a4f8ae00df5fc195e4441c6e1f0476d787a2f61235dc696, 059b892d62476b217215065d991969420f81579cee7f20ee3a54dcc695a45a99, c066cd670bc01d28236247f0a81146c5788e6ce38dc0e2de1e1ecf23c62c0b5f -74394aec20b53636f4eed20c95b9ccaeea3fa38b89d61f137c90e17b56407d5e, 58a866a04d00e8d2be7bf2af338efdad24483265452185d34d91e212ca16dcd2, 109ee356cea3cfaeab4d905712cb16aba419cfa778b3bb4ec6bda923a0a19e6f -e55f5763b84825bc507851a76ddb07974c7b87b1fc247377eb27f711ceb12b95, 512ce80786d31722630dacf8632179718f453cb572b84ce93548469f985b9f79, ba090360bf533cf299ef28297d03ac13465fb190f879298e93dd8850bbcc1c72 -cc4c0b04af33a92da24b75ffb5876aa31e09661f7011010ea5da3f57383784d9, 9a143db95872f1eff4d179da4f34e388e003d394d1709eea734221188fed1bd1, dc3199b00057d182b651acde2d4209827ad9203a20f2a05003eb7ef8dc0f0709 -0436f5fe5dcf4b0723d214f775552a22bf4ac8c1b2f1e05bef50f75619539c89, 68c48fb0bd0eb7c3252bc31caad79969329c0c8eba00959338d487ca699fd9bf, 771388fc04391956bf3e555f67a897fc68b1325767c40e2ec88632f922285705 -7e4f27cdfd257a307791d88791d6f40fabbf40c68e5ac43dc0d102ad6bdf0583, a152b4338d5ab02ab4be35b4f76632b40d78baf5fbcad74af0bae67f8ca5eaa1, ee52e9149fc7496429068e626ede82a070440be8151ddfeac1cca337910bf3da -4a717a0baf634d9e011d1b3506d0333101c251e23de82225251a966d8290cce4, 671c757537f9e35b1185caefb5be6e75f85cb0d94c57b764da322694c69f4a5f, 07d52a4ca4f881bbabc03005036466e987524f6689f44fc00e1cae7f8620492c -b7eb84805ba9dbb7652dcd9037f676b18d6e5f696d3e69cfae3f92a3a94ba37a, 2314292a9a65d9c6abe6621263464409418117c41927b0b58784b0616803087e, f0a59c979b872ca273d5df9368153a7c269833a0508439d63e2fe55755947a57 -6d5ae4f99b5aa7fe284e9faef6d306d14810398a1a307e72077b8a40c3e14169, fde52d5706dc4066b0dcf410823f36454cb71700873eaaab7267dbf2567e643e, 89b807868f5b3d798a583d53d816e50520ba333da9b0cf59e8a0aeb8126360a6 -3181d9ce692aa1cd028ff711a86240895076831022984aed9dd0e54fe1cb5fdc, 3eb9a0e03709290661a546003616719667619f6612e8ae319bbacc63dc90e4c9, bdb06f0b8a95c0f532241798246c714d947f5d66aae44ca9d27d5071eee3dbe1 -00cca082ef77854cd01e7687359e0d8af296b8af92992c71f8cbcfeed8429639, 5a56716bd294095a953cefeae30dafab67b6342a44f9d6d06072e4ab7079351e, 20482d37f805f68590a24fe14aeae6c24c3cc6bd350ce9846f9fb6d531e0f63c -b9eb6cf2624dbfcda78d693418aafac6d9439dc98248edaab979ac4369cdcd1e, c933c6d92a6f8b4d3019f484d80cc7ab502eb124b7e85a979e62fe1e0721a216, 11299696075019cc2a16a63eb0d3097a875f1c1a7b580bebb16e33a87aab2bc1 -9673cb86a894f8c043e5ea88e752263cc026a02e363f2e9560e1dd5a19586a41, f2f7d5154b35e5bba24c62d36a68f0c56b74bce9e21d0b0dcc1af0e7cfb4f6ba, bf48b95295d2b7be354ce6216514a79b80a993f2a050a9c487e1ba1e0eca972f -70fb81619914506fcb9e79c6c15c9bdd578005aa5d307f3844bf46ffb48402a1, f69aa89ef1630a21335a41d640346bad29f450bdfe626c0fb6944040fc0c88bd, 304b48ce10c3e4a0c4723d3230f966472bd22df8425b741248d709a9d4655dff -e94430ec7a2c7a2eb256c57035bc1a6172928041879c1423c1f472fd85e39e7a, 8d65ac79f5755be70a88f9b365a925bb0c591dbba0c2364d730cf9d60a7e38f5, 954287497ec1ca9cd665798a4020c92acb6a534c3a7f01a8d67d3539c8907ef4 -fbaf30dae64eb14b90abbed3ceb853456bc1935f70c37fded5af1b4d2ec0c8b4, 184a2ad94decbde0c8dabae37957083794e3f31efaed1c44cdd0cdb5330071c3, aecd0f48a508f6dd22145445d359e43f656bdf7c6ec3f5f8feb23a2369a604ab -597d315206d204dcdc04cec0edd9c45530c901d6e5b1b033173c6e3bd16b581a, a83a8304f560c18011eef0d8a970b3264adb9969e1e952bb0007dbdf213fe9a4, fc89bc3565e09c26a963a8bf455a5d8398dc279e1a85bad8e2096ea966ee8586 -3a53f1d1de15ea116e4cfdee39a98ef1fc62354a6952f9d9323507db428c94cc, 402b2f36fffa3097f0e25dd7c22edaf12e23e062a3748b134c687b07fa1c5f1b, 7fe1721bebebd91877e4ff6447d76373f0e56558c39b09fce693c93d2ad165ca -51a7f31dbc1c9f6475310c93cb87fb44070b11490d691b9ce2546d755c59d5d0, 8ba264bec75d258f59c5e33b7bade85413b805db2adb615be2e742f06f2a298b, a0a8d9e7c46d8cc264e210ae2eef80307bd8cf9be8f54e625055a8bd3de49929 -8e460d24e705803fd9f068cdca2c5c472267b7b8bd3f2011ef0de00baf530008, 7c41c41b83da1779f343ff352f9eecc59caa5d50eaf2cd6957eb454d5ed7ddcd, 161a369f09c75b050b0d017ade6cf7b6417c6bc60d34f5b63b810ce73598be06 -9f23ea4bb7ceebd23b8ffa5987d983496980a2e497b6b132c0fe6a7d05c3ec9f, 4921b76c555d6ba02e67136e3a5f32d3c8cf6996a27f8f02a21b72d346d7dfd5, 088b37e720faaa60dba3e3fc8c0d8de060ef52b3c20fafbd95e4483cf3a7e909 -5968c4de23160b1b58fd045f46d7fdf13f946244bd8fca57b8f185e50911f1ba, 0303d31561b4eee05b9486663c0c85bd45c95b0597002b317c660a1bd7f48b9e, 378e0fe1948d67ed4266326ba7946f4801dacd390c9cbb09570aa8d0a78bbfdc -52507d1d8290d10e321fe2d751a254200b87340f37b6dd01f3c312f421ae223f, 622af0aaf462767c4d97bc07761a184a97bcb60f266983bb931962e9819bce99, 70257b70d9b0f3d688134d36ae07f5a7247eee70ebd8847baa8c49044a476184 -1b5f0076a48028ae80bd4a54001fbac6f65568cbb4e86928e0aa99e4336de6df, f61329b71e74b509257c15e5e242524ca6933badfec64eb46fb4956032d9ee05, 79cd24534899c582e0590513adbd60a2cfda2041ec0a436fe4d0c89e45f37376 -633e0186b3d0ff9f7090cca02ff3844d0b07028db1d3bdc71ff10b2f876ee6d6, b0fd8d8340b9e93c7098d9251b014882bd005d85fce2e57e0a906d191b5c3fba, e0da694a1f763ed64288cd1f014b09c8bf130cd9b08f86010fb505c0b1cdb328 -5418b360d1f69a66b38c482f3335e6bc75d36f44ec03976f47982162feddbcdc, c3e03bcb3aab0aee5c434a85e32c4a203e8b3eef9e8c14c685d9696d67d2bf6d, ab565d731ef678b03ec8719c062cffc7acbb0124d9e2c7550ec3113c05133f6d -d89c082eb4ca0357a2d6c818a9ff7c68b40c4614ccd963ef4ac3c7f34021c190, 591f500c9f0dea33b70dd77593b4f8dfc9b57e4045b48d6f7ecd62c239fd63d4, 8c823ff17948c6d6fcf5d0b8fb6294a23971753e5e45c412c16a08e23fb93095 -c1beaca255712399a2900641e9fef783e5b75bbb8252c9a07753baaf11130835, 9db95b5e09e42936ceb9549455c42c5867da36c0c39d5a5b68583d1f83cbca75, be5a980ababeb83decc6fc6f2acf90eddcab9080f281c7ef5a5cd4c128458738 -8070a828a7fb1a5430fc7bd17849a47a52cf917bb8655cff43a4cf9b681f25ca, 8da403682f842a8dde66c43b9466881ce0ef375efae37e61d36ff161049c04a0, 3b842aa990430ae92544ab042e525e9724c2dfa4aa6c309239c39b3e09b9bfe9 -28e0f690aabccb40fc1645d1bc823afafc6cf3ffa8799e091942448f0827c697, abd3118b4562c414ebbb17bb9394c7c8a64211d488a1e03bf7c9889988a5a850, 0322c9335d98556a01cc687ef4a35f843f491428a5195b70253dc7d928c9e8e8 -eeea29e26e8e80f5f3a56d920f9425c3ebd9c67a947c34bf6a5901d7da89675a, f68f26b6a17c309f5bf24c0b94eb200bd9de1820728fb27dbebe9fe3bf88ecd9, 95cb406f329aca9fa08d3c3ad6a24d29b25ded3386b54eac132f9622d14103b3 -72c016f8a5f4a51e9712c0484c855e3741096c432913df3993a0c801003da600, b574ad0be01921963d95e6393f5912a3de5e6a4902b08bc60aab3c8ea2a6e6f0, 3c77be98844c0e84cd4100fd3013478c0bd86c5276a3efdf6926b821beac98fd -7275afef0649a86a05ed4712bf0a1dc41dfdb72cb6bd6bd938e52ccb90c968e2, 3bbf2f9cab4b20d80a045badd49594dbcc0580d18f2e20f74a9fdd31cae50c4e, 49ef88621a690536dceb8d6c90b2ca638388286172684c20d6ef2f81b9747ad6 -c24edd02b9b0a6ebef234552cee4edf71581904054cca7f9643eb09aa9480e69, 9e645ed997fc2e5a7bec0649f493d40bce63cd1d0af78fb0bf696cf9b5a3fb78, 7a1ad377fddcf0cbb689496348f3c17ccfcb4f1117b5bcee2bd22ad0dd104fdb -4ea58c8dc3a2bb21d73b9d6abe248e4a448e7d537e3ceee22e13c7c155e3eb61, 17a2988a2c62a1bf9da60ffdfdb02e8824bd8e8ec0f7f47bc5b3c10e3c7d2f7d, bb75c34d78616c3b196fc2dc2306567b9d1fc17fa4b73b224791fbaaf2bb061e -68add6b03a43d32f0e8145792c82e0d9373cef6731555932b19540edbd674282, 0518eb13d2701b13a02ce0f3dbbf2c06129a45bc1fac2c4c2ed9af380b0ed48d, 6234d3729d42108beafe14e3e22ff155927e50cb45cfc6981bbd478758ac8726 -de7d84ac20b9390a746e8c3776da618fbe2338796bba1743523c4e95a27e83d8, 4bf2b17772f32d1ba39affe17aaba0b7af0d6b41e0be94e225a95567075b5280, 701e88c41146ad9a0b1ff20cbf69e20630fd47d6e656076fac9148675ef6a8c5 -1472e2f4e39d3c02bf9e460b532654bc9045d3a27b5714bb63a03ec7ead1a429, 19cab0cb64b0ee7b43e2db9649e9409ae50a5fb613b26e5d1f89ba9fc2793e70, 97851c49701d1cd707f3b406435c2eeebcbd69d56e42d421ead0482c95506b83 -41bc084940ca6d380c0d47ddf6c1421e22d665ebd8f7af08e6a99319f4661800, d44e20af955a477c1bc1d56a1a0132ca17fbf71003ef02f181b03cb38915e5d3, 85a1530a267851886b2abbe3ac9e40c7a6ba23d350e9de5342cd84fd2682edad -b980de4e495af58c8dd16a7cf12aa0e5063dfdf87ae9ce183a0ee26eb7e47385, e9c2dc046e8b9ac75a48f30eaecee5dfc86588b402073a44b69226c3fac90f7a, 197082fdc651c3876b8f1869f41df380a1300a369973037329a093a4a41baa8a -a01f586bf35ad81e20a8e47bb476ddc33af6c0dc823457dda5ce7a8d137b39e5, aa754362197b1c40d915d6ed7f828a39caa6db842a149235cdc3153cdf01f578, b7a32f7e268c7cb56249334650e7eb54f48ce9a5e4e83df1dcf3f7e053fe5886 -485556be8cd49267519524b0d83a44b970275b528fbf793dca8d256da3f2ca41, 2827c58a110854fd9006831d1200aadbb340efea30e447ede7157edc2badd9e7, 7dd2ffef503101c626177e0debe07a42ceba568e1695d5fcd71ecbff64fae7e4 -abf6921a45b2b582e3734fcdbf0d62c149761d62854404405c917b46f73ee90d, 6153d42edd3cee9fe3a53065ce88abf2ef6fc0e108f759dd65b2f55ecc2f2d7c, d45a45030b8b0e40a4dc74194dfb552ec37c4e783d52d7f630b845f349abeaf2 -88669b27048cf175e2cabd100e9ff8dde9670b9ec37446467e4f2a580c47826b, 6a4e1d1478069b7680365a8a9bd85b814bca16a8d5c6f865d740f5ececc8185f, eb59d311bf23419874a202b54c46461bfffe4e8bd6c15926f89628c2e490d84d -b326d1bd335cf51d518e89442539b1d7a318f3df383d53f9a82a7292c19cb02e, 6e688c2e0394f442036cc20ba28ff013f2b476f62e927ec7dc7fbefbeba10ba4, 9c69db61203fcd59faf74ae95d827e072f719cd3038e3702eda266b534be61ed -5e41838874ef79cb7de85b9b48ec99605af65213cfc82d7b80ec9c647ac1b10d, 5c330a16b81124e37aee8357226ed1d0fdedff9162f2377dbde03863720a4657, 44f67e4c63abe2ed6ded455f47778924333dca0f69b0e1de95879a930956b411 -af24c594268434537d3767ab8f67cc37af95c6ad5021860948c6713b925f1660, 79b2fb407a16787945ea8e1a5524dbbec192bd834e799b994bdf1b6d07191d33, d660eadc2c18768bed26d927c914b72064c072e07e4b8e8779928a3f2c6a2926 -ea7b4e6354b01488e3258a6982ef2feb9c12070b39d8f2edbaccc9d536ea80cc, 3a2b57ab6202f81a9c07773e3958ab9d70436a6f732f3c9579e9f59b8c70341f, deba5b6859bfa26569ee62d0eeb6ffa1449ba31683d974e2f7f7c6c72a28aca1 -8cf852a30ab9cd45d94ca420a9fa02204e1b5c2494b0976df92b1fbd8f8516bb, 0643a54ca470436c07ae3d67e236dc6fe31efc83cc4721673b5491c18b924e04, 92fb3eb56464a6078f4eaf5dbcfbed038ba472d37e6f75bd485fe8a0dc8d37d5 -fcbfb7d7cdaf676e18ee4f843817d293419cdedaa77efbe03fd62a2999c2d76a, 76d988ac4082afac59f5265ca3f706fafbeefe0658e143aa5f2b15bcdacd876b, e4ed43fe179677ad6d4c0d32e2d85b3906b5ec9076fb7feeb5bca8ba5b993cd2 -402a3f285db283d0e3c59e4450486eb64d1238e0af589f4e4c8ebacdbd1a56b5, f87e645b9ff1b1632c6b7548c04dd5afbea1f2f560521b2139f4dc1b10005179, 7380bf93c84196d154dddde22240ceb09d98ffbe113f6bb738728216bbdaed64 -ae1cfffe8bc4d282cea653dab13201a04f4698572b8a02d326828d3e06dd166d, 929935e23bd41e40aae8895f245033d16e5729fa3fdce74d9d174d263c79bac5, 3540ade4e9f5bf9ad7f60e17a89991e42e4233d2ba6e994287b822497096b6ba -eadbd91d5d9d9f096df439cca2e6f172518eb2b5d5060ef71690aee7e3223b2e, 50561ea3a6d31c2eb779c178611bd293e34eb23b7db895dfef60e9549581b4eb, e03b13c51e3250be5c2e0e55bf8b6ec6359aabc0789516db14549976a5071c5c -7fdb47965480997476070af1db270a87d60d55569296e1a1e421719b45b74768, d46586be6e875fb6bcedbfc8327310cd7a6c0b56e956d32dbf9a742c61f01f0e, 75a8737f8b80f267368c2cca4e20a48e9530abb9fcf115e6dd6d9f94916b3c07 -08bbff7c8ef00d2e076501be16d0922d3b129f3ab0b8d741c261c7dd6c35b2b8, 025b8716b09a1719573f9c97ffe06f041268665b133926570727389e1440ad03, f91cb9ef6c9982ab77c5b646fc60bbf8d49edf00f5bf53605691ca935a79ade8 -ddae3c06cb4c8ccf3ca262858ac4481a529d686fee05169d8799fc0e8ec17a3f, 9bf2a2b6aec3e5fc0783be7bbe5361f601550e08ed29df6b7da13773b44dce50, b775cfa3e317eedf933763421ff5dea62f40f60cb9520687a1c978acf24095e7 -21acab552441b39702c6a89b91aff56eeb42b9931b63e70e194f52ad8be0ae66, d2bd1210079a9a666267576677d08625f63c939b982f898f7fd55d8903caac57, 1bda1aa998fef34758dcfd66e2495dce73ad29f7fd2b4d9aa8160e1c9ed1e240 -cb2f3f06acb1c4bbcf841d724da705b87e81023a91827feaba22f7404fc6cf47, e2500d3ed028060b6e6db2688420d8e31e1ff3b60f9b385af95ea9add5a8b46f, e3b1d14331f60c2016a5de295c93b8932f67fdfe2132c17006168286b6a42b0b -30a47f9bf46dcb8e0fdcab70e5c5cedb6ab27a64b08e8fb226447d9f44d7936a, 2db59f34a4102fa62ec265e0d5eeec0c141f3cde6474a9c290049581f7d37764, 40298e44cc533e7610435aa258c52c20239f95f220c0cae9c4e5dc461eb308d8 -8a888a86c294557d369453b021c31f35f5543d2a9a3769385bc9417e640b1099, 656681f41b1fe353bf9398511ddb01130e05c37e3ddaaa6b2953a74a2a5001a7, fda8d0cb52a75334956333e0b8e0185e76cdd953079b3fcf21e71e23329753f9 -5f1fdd6e66c926c48c33a5984e0a5412e07e417007a039b330a36fb0f57daea0, c1ca35147ca722f34d0cff157932ba3fe91f5b9e5677bf485910874d33ea88b0, 5bc0ae2453ffd52cc3b5a4b810f88c1ed3741e98aef2d467c55c98135235b35c -26b53d01748c35a4758ff07268f0dace6cfc414db546521956427715e7c8f895, f2287fdf0f58fdfadb0b9e8e0dc07354a35e9bc15f502e16d6260eca8f381bef, 13bdd36cfe7ce512b4c1a00ba18267bb3c475e50b21f9219cc4433f1c7080317 -c8f629fffb6b62c9cf01f34821686ea71095223c3bcdb01f80c30206829f33ec, 7be24ba6a3d06718a4bbf85cd751452d1253a2b95e1299e3a537ba41fbe0b32d, c7b93428fe69fd53b550320e25d397f2477d1e98d1a405d897f699c894ac1df2 -b506a4156b669500c6a7c99baf289cf2abd1ca3c446afbf74c63a49e5974f4bd, 3b1e3d59d41a06adb865ae0b37fd367fb88e743a72f45e7f60a03e169271d2a7, 14442bc5416630672a45f11fbb8931502bb8764266d62c13169e1668aea8bc3d -45bef5607684b17ca33e72bd74996f077b2fba8e7d17e17a2a80af5aff341420, e397e3a0c5b237184c948165f843ec7a1143c9ec9a58a8c31c4043425051ab55, 48b4b38563f2cf8da6e8143e2b48350c7ad8206569585b88e28b673e0e9ab5db -d4ade68696428e5c813cef1f12118127e4b1c43506bc4e6c0d8f9d4f7e0d4f00, e25e504ea4b59a5c351cec908d6b3b32ecc3ba65bca5656d91ae98c02e0b14a7, 7dec9645b0e486d05039baa69d90230cfc4abd6f9c471b2e45a6da2fef5909a3 -1d945dbdc26b23e1f8a1fb7390d8e4dbf8e130e05505fbc975d11c43ecf5085f, 528970e1cc786d2a488f72f9c9b88797cbcd6d28479061fe55d89e79c5926e52, 002759cf969e823688f1150378b3527da6ed0e84cbb47e33c428d3c3d2bad754 -647ffb6a245794fef2940302e06c0760059e5198b51b034001241df3e98d2fc0, 0bc92a5f83cab25ead777bcfeb6a247da60b785fde2d18d3b96a145490a28656, 8aea9c10776e3a341dc0fcf989286ff6fd2e7fc4057e72d27ca330e961892a5a -0d05d5871dd29f66fa48bb69e2bb3e797a0789413298ae4eedad1f0a6cba5019, a404e449148cecae4a9014baa8e2cd772e96dc61c13484eb285f0f7b25125609, 1d4c59bd377a2c8fe24e556a081c646b69fef8f86b862b1113d8f007f67d16de -0a019a626d26a5902c0d9c160746c2ec341355c341b7bcf84d269ebf420f570b, a4b996e3cb88403e0725db0b7925b5ffbd857fb016fa9b73081d540c3f94de01, 015c84622eb5cc4124f97541c5322aa8aced7739bc234aaf946a6f010157ed9e -f121c2798a64d11d3cc546d30c0e2ccd8e25ae9e3d03132dc7866e8953ff00df, fd835e3a5ae70e02a2da07d093776faf2f89f8de900eafad384232d09f968aac, b1f80e8e88caf08d0ebac44e239b3cc765a040040be5c0be38e6a1fed714659b -7ebffa3c2ca187bdb028c7ade214697a16078399f2629e172cf7d8db560a92da, 55331b2a8cbde1132ed4959279f47df2b13c8697a9b8282cd8cdb93124c0f2b4, 4329bb9ee05dd211f371d83f7a3e74c0b0ca8846addcaaa877aacd976b3bdfdf -04f9e8e5665f3ac7c1ed17c5d0ca1ee660d1b92f8c6d9dc5c458c08724be9a5e, 14e1d71ca8a81e11c8e9d6ebfe5b36e3ffe3a082f4b6cd0a14b54b26eb0487e4, 3c7da7e8557ad6d49092b94f55be5dbf57966ec7835c5094b43b81657a5f5f25 -4a221e734f55a12d0e0bf837462843f52710529dd521017c693f16a70efa7a49, 038911cf62c3351d83df54cf036ff44b5b301c724f8b540e7ab489aed493d2bc, 1a933afc814259feecd12f3b4cc919610fc859cf1fe040456c8818c8062b3b9b -c47bec7c3c28c1b8ef39236129d9df48ec2feb8a6a97a382a6c05a9ce2ca5afc, fe1bb9e15fe99593611559be9b4fb06177d3f38b882901c67aff17c6279e44ec, 05b5a996fc1c9646891bcf8ca38e4e5f18f1192930da42d4fd44903095d593ed -d9f46859b1e4168e1ea16047143c662006b566e7d9d69e3068c2caf591ca4de2, 8b4467e140a7bd5b24ee9afa571f331dccd0f586d5849b5dd5a744833b3582d6, 58ea2a30109226a40f69ba211556bbf990cbac1d921519eecc183e22fe5cb0a1 -afbb27c301368fc286eed61e12db2b085d0e9abff37d4aaa4793347807f7ede5, 1b2a19219f371a487de07e7b87118e6a9d46257728d18fe0ea062dd4ec2f05cc, 5229c4f0f0747b5bf44a0da6524b6e4ee46f0e3e65e811d78f8063f6c16a84ed -75e73a56dfca1b7e590dab1765183c9f061daa2e91926abbb85e3268b0db3398, 984b7a4af0e7a3058018e9f284789386d51fa9ab33bd98fa1123f681878c4f40, 2e8e9e38bd0c2bbac69b9465327117cac7a6d443db6d24669f93f096f5708687 -32bc273001d41d08c88d6829d26fb8b3de339862ae66f6a480051a8b01792eb4, e1677f2b2869073be9f1f42a07a9140d4937da4bfdbfbc3357a0f145e3e6777b, a372c1a8bd277616d11caa60ca1986592c82a95ba963fc508d91d568cb5a3566 -fba46c806acb376cc06fcc03569e7bbd0e68105fd217041803984bdc1aa48ee2, 65073bdc69465bf2f352b4db5b5950d259e8c48b6a616fa5d2f4b98bf51fda4e, 6d6ef46f0b6901aa2c360d8f67ff97fbc437fa0bf934d5e54e0fa33c336a6358 -7b2017d23ee485e9bfc6fa11f4fc05c7d8653409e58c354ab3b8dd591a0829e7, 9471160b96737f09d61c91dfac0f7059715ab559a5ca35604ac2f380a6e553bf, f5cd520f7c868d06709234327aefed65e127a48b52e8c3cca2b3664acd6634ba -faa0abe2fa27a4c4f45e66e0ffa80754d86a44d80f339ba9496525aaec89bd1a, 41bd4e444c083a86b8571a079ab78736be3e73083768dd60e67a4ebf9a35703f, f44153ac1b3d5b18c3755b005aee159494b77304a573ccce67daccc6db6af52a -934e209c9b090c63e544dc1f421f89cd2c87d8fd44f8d2a4b5da090db9ae2da8, ff6d4903a2edc1ab341a90e7fb12c1b5a0731d1f5bd7d26b214b10dd7167c169, a530d1ed0261408195a198f7ab7ee534c77b033600c6245ad3f73afab7ae34b5 -cdaf09fa4a5c39098137cffc002dd62a2e7167216eb527192642e03192413480, c9841498395cb0a73b31daa8fd538c17ea3e2d448c0806288e7f85226e2931be, 660ad4f6668f71eb6c4ac0f485156c0459538a7858211f92688f4b5393fc7a8c -c8321157921042d07f86def117732e1c1c4c9ade6e81a2a833ed06f3a28fb7b5, 2ae274f4ae84a36cf834e112eea1db380d852005cf2875dd29876db3e5842f32, 0590563557deabb7e918ed0b7277d83a1d41bdd9a91c4fa42136e624df374280 -bda3f35f8255cff405a43623f4af37c683bb4dcb6660c0a218de967211d7cd40, 7030ac7b653b2fe141a283ae08f43594c74c0752788085376eeb2b13529fb64c, a83cf90e3fdb6093e4f57631365f7b160bfbf7551f892c7f9b16d0cb70e885d8 -a2c9d44d02c933ce70ddeea7b035e13c7577deea00586fd9125baa3178376efd, 929049ae9b3c94deeef4814404bda33fc43c1935b51b58dc514eabee987d7288, c3d34c90c3756f36edc1ff16da16d00ae55b539b3602c664abb1a6c46069c482 -59513dc4562f73a23506495d6b71742989fc8f50415632136209680fce45c97a, 033f77f25fc1602990a554b5509d9ca1b58de612369dd605785074ca3c3b95ed, a5da3dfeddbb716a54cbf1f90f1c60ae6f572f57c545ae599101c6baff14964d -f4588b9ed1a03e37fbbc53fa0fdfcabc639bb0ed67d4583b428a4bab44e56a40, 305d8e4e74ba94c879cf93d31743ccc047d8163e9c772c63565a934f9957cad7, a96805e7ec9981d92d8cc6180a664d22651fdc763de2dcf311f7b02f3622972e -529f51fba8275d3fb729c5ee13ba7a8fd3943f8d075c0c54022c973c7cc6e8da, 1561ef8152b029b7531ea1d72bfae9bd1572f60878703dea5288880294bd6744, a72f86d5f63191b879ca027dcb3dfe3cb00878ebc2d1836416e6ec1660fbf1cf -712d3dc0fa21ece04b67392d797a985785efa04c2adffe9787572e44a7588098, ca558bf1e7915a4855419b9719add4f1302850c857e01459145464137848ac3d, 1a155a4483c84117c80aa1fc7b541ea33b660e639a62cd398cd7aede03728d3f -0b21dae0f056a1d550a020d30e621cf68034580a2ba252ea150fe1d23a64623f, bdb5cf1cb2ad7f8a0eeb83262afb346d741b84f993b612b6246b8c7b996931f5, b793a41dee59858518be64568ed1574eb10dd742d4811ad42a8c4802f8787dd1 -5afee848741c114856c5f62b84d33448e154d05c0ea7394e3fdb694cecd4267a, 7f9994df6767bf87afac9831dcd5797463c1af6b9264ed6e16323e6db4da6e44, 20e996a751075201d9e63f9cc9fff39102d94f60d13752ee44c0ad19c57bddd9 -6ad392694652213ea251a74577957397ec692252cca1a6ecd9c24adf528d85f0, d3b4dd153b5c9b6045c1dba99008f8822a7c8da983a7538415872aff9a066861, 3b111a97e43f1fe8d1e29897b2e6d2cb365499b8447c8bea14a69f94a31713fe -93f40d01475ce24aab5a323b5613a1cf3bda486bb5841e969ca783e6ba0e8eac, 7934556b461cd8a7618b64e5678c29d30cd08a547b4f445294288c7749b77239, 808979ed4c982abbce7064d55cea1edbf8fc407d3b2ab38ceaea5113e550e4a8 -d5aceed6dc04d050af40ede0d7649509d2b8bc5e1fa07506c1adf4b54ba33508, 664e653fe696b8b0d84bd72b74db422142af300b6e5dfd935d7d4b4b4e7a7c00, 471880e2b194dcc470c8dcfd11a39fa436653e7711dec6f3d93ba7849719998a -d26158582e47ee60bcff376841cbbc7a12895531de747d3f7484dd198d9f4903, dc652b53b9ed346aa1d37615cd494ff4c0375211884e48b37c8eafc1a8d3cdac, d23ac8ac2d8adfc332003764e33c17c8391d549f41d381276e06859f6ce7f57d -63c1e043bfb2903d82d974f14d9618dbc79edc0ded2a45eee119229ba6cda4b0, 87bedeee778ced1b24c0286c9e55dcb8e8a83176cd27b5c7e7a8d14bd209f09d, 58283210754ba4b8845f8def8f3938baa0f99e4da07c18d5721feafd2a0edd56 -d1736712ddb4ad99435e6f1f38a59ca727f28293a72391f03e3ab119b3f15fa5, acafb7f163d33cd127efbba1df6763ed8ec75e6036627cf35e30aa7cce6cabaa, 11ba2ccc8682506c4bf9d9028ba95c3e21ea7648374e7d5a277379d3266446b5 -702f3c165706158695c2dc3057a4e56f8475943b9f687380f4468bfbf36adcad, 34999a7e30d40e22c51d167c5faa1ff9b7828614c41acc46aa2d7354ef59a3d4, e50cde49232d7e2912467f426ed026678e8d76e31fd4dcdca0db8014946568c0 -0872942614e024e06ba3a560635252f7b840e1b4a471d17483bdb85bc9b51610, aeb6647d58a3b0fed04a84b3183e295ceefd92be7519e856b18a914c8f4e6cdf, bb03b0aee1d24c7570c42e41a732b1793527331b9a00d2d77038a8e24d2072cc -ae218ec74cd1feae5f41be0cf1673995ff3e58a1b9c767b0356288b2bb101ff4, ccce0a708f4b9d898ee2b9e31bad941489323c5b149979c2bc8a0e7a05794ed9, 194cb6340e4d0072bb6311b391b53e7933b1d808c59285e237b1e9746eb2e8be -ce913c952a3703439082c9af9e2da37d102b9845b2aa5f9d5544a41b7b0e26cc, d3201d98f0dc164a38f3c3f7e9d9d5bb3a3768ed84f91512881e33997cb0a089, bb8a9baf082cf8fa2b88b104ea923f270b1e5a37c58929f7f4c2f55c4a55c3ee -699a97c1d9c554d5cdb5bb5671e70c7c84d5223af3de7494e70b1e8464264789, d8e4209067cf0c302c68145260fc710290b131e8d5914ffd22372d8fb0ca8e52, c00f6d0afba9a3e5fe6f4751f58cf2898a28a79052ef2dd3c876b65cfbb3b167 -aaee6338871c4b68bcdb864648eab6b8927ad94662a309d102df8927dbce8797, 5e4721304d5e27be7f955b804da52b10d7dd9d0912576bf9b196ccf1d2a83a2c, 310a225d5107f8ba226356c95fa3011d1a5ec6f19395cf3be9bc0b436f67d96c -6dec773d0f39256e3c92d51fe097bf9d3d5d8d33c8972b3e943f2bbfda41573c, 1b1a895d75f98fa046f8abf29dbb26ec8825375e90894e112674d269d8f32977, c8666e340b055c73a7f350dcce9b674630b7aa809079867e7c38928e77787edb -1b4624c833cdbab11a04045ba8860e51eb0d56bf98776152c7d710cc5a96b321, 2b22b27f0e34d599d01b41101bc482bc64958e49a4679f00b0fcb287833c3baf, 2b633a7917f819b3ac8eb8b1819bf71293b58ee8a0436a4f73b74c3149d7eab0 -15fe677b3bcd2bbc844a808b853323dfa3fc207d00c550872d67e2b5c046e4e8, 35959c9f4c5904232b99f137db33b015b7b04808e7cc63917ea25bf38b52b517, 01fb46d1dcccfe68a7b85674354cf9af91a05396dcdce34ef4ade7fc4f029c3d -1375e0388b861db01088cf568e435af7a4be600677267a8e4ef2f914e85c2dca, 2ae22d36c7e32d2d5d8150fcf62e24b8f13b1ea2b443c5d5ffa03ee443edfeb3, 52ad6e58a7bd775ae53e69bba1a1b1c8fd76141d28e8f63791e23e2d9f04efdb -e4dda0c911809d1a87e734692f482b0915d2c642f31b4ce4a1d2babacd7f659b, d433ea97d94cf2ee1b360302d91d8214371ee4b5639ced44086c4476161b9a4f, 2c8086188b87927fcd37484ead1b1c764f6654d67d0d6e60eeaa6e25266a018c -5f7d5a266058495da66ccd00053ff54fce52099885406241aba7acef59e61c1b, d396ddb6c43276f9462ed525d5e20c9e2b971affa71e3c545458cfd4d186e540, 508102349054305b0499344d618573bb659426f020fe56cf4ab9c05ba0fa075f -2df2d80d35549baa645f1d00b7c807703b7d674a87b42eb5947ec53c4326f2f2, 9c13de37e48a44862fcb674030a4e583aa063227fd69e733a7fea85d2fe79a76, 526649e7572b39929703331c20ce2f7ab4a573815aa87c9af549e482afc65169 -942e59c122c3e9eb78a89116c4af3334b30783ed6f2208428bb28dfc5b884ef6, f41dea01f312b14e8696b09f3c992f848e376bfcac02e3eb81e7a6837d7619d7, 7926013dce33ad48d8ce88a0515ff85ed5be96fb0fcc507ec0487e334d211394 -77d7582120c340dd37ece7e3266abd92cb911769b28c7df5b4947e2e9a476d42, 5104463706807551de63be1f625a9dd563e2ce05cf778cd33c7731c8025861f8, 98cf6291477bf62c2e1c2e983d0b533941040090809ffbd25eff48e70e8e2452 -90ece7a45a0f68e7f7940beb2f4bb372cefe67e26976500647f67bfb725fe454, afd3b99deeb04dcbfcfa336d34c9305cd7ddd7a08fd262a5ba0c3cf795f3f8ac, 04651e1544a0bb6f60e01a29aafcf8def53468a9dd9b616316ea814ac2a9b1b9 -15276c0a5c6d087a02dc9014d20c49a94299e3e604f2eb1f9ce45646fc6d29f0, c798aa61370b5c450605c159d0e16054009b0a8c2fdaa3cf422d862671766844, c31fc1c9b6c0dcf68a33b6928fed0e477d3cf85d4d32eac3d707234375a7683b -431d1b7fb7dbc4a505ddfa9e5c5dfdcef474ab916503b42c7b477e86cbc31303, a761ab23fc207f6bf9c160f10bc7fcf343093023f8e9f6560b84dd198cebcf43, 68b1b778e7975b97ee9dd32bd733f00ce1a1d59206b7077bb0ea3d3aeb4345f6 -226112a180634d338fbcce761c3d23aa7a1e9fb2db41bc2123baf2f281ba8ea7, 6ddcc6d743d6ad877c8b89b8c5b3f572f971a1a7e3b2fefc12ca06994640e565, f65f2dc844e9a40f737459d46bdd48792572668a201da68b2b8503491db874fb -59da1469827eb0424a2d52e9e36328a508cc603fc80b588f97768d249155d991, ace02e2ddf2c426d4ca68fe516aa9d20d99e3af50687c1f5860a0b657cd2923f, 795eabdc88ea5dda82e1ed929a33910df0b6dc258b196c828d4aad998e90f193 -f125e767c6de6496b07ac7792d60d966316d9eb7750a182764b98be22c94e8af, 20ec39dee134a628f8f1cf5d58e1393bb3872ad05178f103b0a64accd2180300, 0f3881d4449a85fd1e036a5139c26de960d74c4e7f5888d3c07edceeb9303e82 -ae6f4124a53d6d8ada514ec9e0910aa41d1dbcbbbb69be0fbe7dcc53f8f05e36, 16a33169d5c5141f06b64d2017e8c4b7ee2d4114ee2e9db5252d8f6cd857d9af, 62722507184889c50f789b6f50620a5d3bdb747d01fd07e050b84b18eac0e876 -57c4f2308e837d39e49eac0cd011ede8a81093cb259d3e31cccc6f3d2ab0de2e, 03747aa040ccbbcbdbdda10dcf821a220718a3365a31ff2845a5888869c54497, 189c1a6e9a665cbc230998731911380bd2b16530049d0d54a63ab31a985a89b8 -614f8b913d12bcb6330da78c448ed7a75d6c9c09a81693c07aa37694cfe784d4, 7e0385f510d6aed52d8e6790a2051b9f2d457109171cebfb2674101190749436, 732019d9c8c2b0d4da2c15d90d88823e93a4b77c2f6cb34a1840afbf75995e35 -b7ed20307effe2a799824969bc1af0d1623d7c6758625764e36c93d0efec986e, 40f8d7f774b13b773ff070a05bca8fc516a4e3ee17cb300c4b9485d472cd8b91, d289e375fdbd885baced7f614f4f38cdd76a3b2a63f04f06b3ff782cc5367cb9 -e7d1f25b6033b28e11b821b53535d9bdbe996863d11d4bb5ff81895b1a7b8963, 4a41b8405a40c24847e2fe6bef93c5fa74b980db4d377387dd6ce5e44ffce940, b23e0d5bbf83643e8f3d4ebb7db897b000e11ba427d086253e5699a3b412ccfb -435b9115f6114e76ad27936cdcecf4780c9918a1b228ec8e4fd400b6697f0f7e, bf7ef95b4d2b3e15f7b7df0f23cba86dfc9ced13d0aeef60da80fad691385d9d, a7f9b7dc3c1ec2d7f0892d1eee31427bcff90846dc2835bf847fcabfce9413c0 -ad64da2e4d7cd5752abf10e5998f794abf3119a5ba23413f3f1c410404eec112, 546733cd48808d70efdb6cba3b6cf22bcf2b69540b88d476c62c8d24b23a0a6c, ce6714cfe02b5182571e1a8d0a2828ea83e76ed30b1f142e3a6b7076de7aec81 -63ed580717ad3ece2864cce83b89cffc3dba36f09444f3591eb956e03af4f735, f0c5ba7102bbf8f653452c318d80ffc4a67786985510ae748548e1988d384fa9, 0654eef367138319e462389a0c6004c1843564c5df6ae190f4c3eaee501919bc -f048210201ad9aed711f0a1006d978b7c02ede5c6fb120fa789605806e77e4cf, 92e913399ef60e9fb6c4ada6500f1f3ab607430dbb59638ead8be7b6ddf5c20a, e60504021df667a5262431c87e322028c9dbc078433c82b3184e3835f061a693 -c52b1d309e76e8c0c498f29fc2462c5721cd928b21ea91766700be50a5dfca46, ea6e05ee310348d53b5171361808e01919b62966775bc18abf87652bdd158c4e, 70a44c4f5decdf2571650d72c198ff26da48fec964adc3bfee8730916a848617 -cfa39508004f34ee02cb8956e89f29b0870ed6cf0ee9c60ac818501305c3fe9c, 08511cd8c898513c1cdd4438bb0c13564f0a8c21bd19268f6943153b49e4b2c3, 02d8854c5f98f4e329ae6dbc0d47f2a09fa722947e26438bb7ff73725d399112 -92b51bde822a1a47ad423e9a7ccaa698bdc844920cc53eefb4f7488907daeeab, 5b32f0f96445a16227056ce45d81181ef82dab56427a844eca1afc024065aafa, 8719a1a17ca6d492a4742984fe4ba5b22e3e6751e68db1d40aecf67065974fc5 -46bf55d5bfd3924fc0f57c1401a0a4914aa767aabdd2c508997f5a549ca283a7, 4f1ef2f2b1376dc8bd9ff172688c94efd886961c731cc09929b6ef6d7958576a, 16beea8e33de5e4302cfbcba4ee3e1583e2ee4257944d60e05907cbfa7999e9a -074393e2a53f05b3c78cc354ab8aa126d8d33fb42f56b2c0a7d906e9cecbf3d8, 0676d1ee7e9a57fcd1c0eb8b1e41e22e125198a26b889b741666b8f28cc36f98, cc769e2e1a6bb2c67b28deb9c4e63a043527d25a87b4215e086080d1f7128b17 -33bf1970c979f8b2a4dcb2da1940a8446744b6f437f040f09a265cb223222824, fcc438a1125293d832336b4ad930c09756c6973c0ce0b9150e2ae64c8b4993dd, a7f90e72a1e0e17d6b1fac9750bb3b698c03daa0d04bf435bbf4f20fcec3b297 -196525eb836c5c1d68d25e2b830fe1a24ca90ae0dabf756f220c1b47b650de20, 2d35e12c4c624a6e1d0076b2b0aa31657bcf8d422d2600de5023e388f87ae106, 6eb718910473f987a22295aef0716fd62a3cdccbd05e26ef5c9c094093f70e8e -907e1977cd95f8765cbf336fb180f5e44cd0122198c5509e59c0fc0742fa58c8, 09bd5ce91a75d3e834783d2da2790df12c554a3060a12454289d57c5ffacf05c, 0dea3ebebf0ed186e93fdcfacf8284c5ec81b81c777d2ac6b3ff5c658cb46591 -c7b4e43d43e017bfca5017fe5ae58b111ef0ac65a35a735265fbd9ac611fe7a6, 8c9fcf4d9b47e9ed373b3780c799b781f145056443eec02cd8016ed4e3e37ca1, fe54a854da38077b58ed135d35ad0a4d084f9f897c22cd3679351d38c85364b1 -d285577438b2c0dd98a908a1b848a7edfeb2db39aefb82ecd0ff1c69e4dd17f1, 6a7ab0c70140117f4b84034c7d3b89e87ab8e3132431da34c7004dd03ba4bccb, cdb3f8db507d477d57dc9781ea066ccd056483d412968f51552bfedeac15aa24 -fa9af55676d31480b640bbbf7b3aa89620c2b4de11ddc1c0b7ce680f0a092670, f410ed90b3fc2d62a8b83bfd4bb9dcad74d67c19c8a4e440ff7f1ac0d6661cb4, 59452e5853a5f274fba657b35a709394d4baf73a6f37d1c320b4bc368f051af9 -b638de5ff00b1fe372db0597173aeeffa2ecfadad390a6cd3dcf3c43a8a27d96, 03f6c00e240d22e2073535e61e5d8521dbfe7fce342065a418d51745f32c1363, c7436381489438729b475feb463bca8b5b27a2fe65faa05a59f586dc9987ea06 -fd6a262c224f6cf54ceef450937905f3cfba2633481d4ac7f45d25b7bb6939fa, e3778a541393b0af5e7698e810ac0059d6f5b6b4012ecaacc07357357cb6bd80, 89533680da34c5b0a0fcecf00f730b21ce6bc60d788f3d024ab8cbf270544b92 -66123db9b938d6e506a8298511487f1b90630a716fbab99792a83d20108edcfe, 68eaa64117ab06680030dbbe443daa1cc974d865ed874758244de693379efdae, dc58d31a46e441ae5caf48d8c711e785f8d57f89e3d8b81eb4018cde0e652b00 -f1daa52d44f55bbe1c2cd42a3dbaf16799b397e3b1cf631849362de4e1e490ac, 6351dd93864580aeadb4561b7fb4309d03d8c1ba3a401194b58995758c52cae1, d60341e754bcc15c5e2849ca7947dacbb9084b4971308493f5f5cef0d2642e83 -c6d2518f5bd2b1c8b1f7db101950ed5876763afe68ec09ab803857c80da1723c, 429319b259156e707bc6cca16e1986f85bd68d9493c2fbdbb189bd318c42456e, 0ccfeb890714e599bd5702d4b15acf32a62d83e015732bbe303da32a35e909ba -8fed604776e7de59e95b529c591e6b46808ccb3cdb903debc36bd58b39e9a935, e0ef77a70289440e3a2bd3776c362541ce14177df48a36af94a4c7a5476d86bf, c0ed578d481dd3591ffa6512a61b0d9d218e48a7cdb3037a064e43944b2f50ca -5b218a99dd9395f36df96a9b9afe1f912113458b63977e96cf3044a405b75c56, 63ae448796c07370e17b2e3367e672866538b589828b4f91255c612422f60724, 640d23feb36f123215f03917f2d10b39aa7f5fd571efd47899dbad0e3181173a -f66e816bc83468cdd134e5965c4cfaee514d9344b68e814f1de5363f53377caf, 6e5fcfa132be502a85f303b3d6682ea545eaa29b3d339a49182c30117d51176e, e5ec79687bf8020b22ec5b5637ec1618bd47fba0ad441e72aaf33d32579bde2f -655ce06048945bd81d6e7b79eca3f34c5c99483d35e86bda2ebf153005422f94, 040314cd7f80fd5decabf959515af6c35af8324eed5aa7b5a1d5dc885f69a71b, db85a39f6370a42c2f0553147be09963c648f703069d5b01c6e076cbbffd8a61 -2b473bdbcfbc91147b8eb523110638ad4b0b236c3b741a52a0fd76e250af9d9e, 0747f52738ba188c4d914cede9e78e4a74c31306ed80065fcf433fb43ed82801, 8ae14ec0313c8b6abc3fe93201e9dd12335871a44ead03f839d7f5274fe1d1e4 -d18d7694c82d3fcc908fc13819386ba1c843425fbed5da3f864e1cdf6a476dce, 864482741f1d122fc06330462552336de13d9aad41594ec7c8fc1a8c4a1ca510, 9d0387697c669cc239e4c0e8ebf112b13b6214d704fc3aa8b7bfbb95b9505598 -c6dc04c901afe199dddfb731b1e78d0c35ac6d02be7aaeab38ec2bb181c08832, 5607ed80437b3f68f7631299d2b4a34675a19bf375680677e636135cb08d4921, 8d44a4f0ef5476d18dc25670b1b862e57c3eb726b8c3f387c7adef39383e6294 -c03cab306872cabfa52c499cdcc1fe1f2366a9f4b4ddffeda4ec64f69dbf9403, c1192d6a82212edc4f47502e1363d6465bcdf7ebc61727b1bbebdfac35d5eabd, 53f63dfef4b1298643df488bde21a5fb2e1d67d8433812791507abed2da2cabb -b359d7e920f1879fb92406ea416a6e30b6d0ea0c8afa28b21c6a730d4d32a07b, 160749431526f814194216c21d344ee69e340c1d7c0d1dc72f519587b91dbf84, c87703816c90d17d3e7e0cc173389b110830a00a4e5ef6ac810ae44e1cccbf28 -fd1661342b5468be77d13c81b23fa895f367ff905abfefdd4301902eae3f2a86, 96fdf4a2d570ac89189cd3f33bf19bdde98511d5327011c461e9afe552272139, 63ae7c6a8dd63f5c2305a2ac6cadb0eeeadaba97a1692c3431109c92c275e516 -6a29e50db5692f13ee3fa89d3b6be22f4de212548744b92ff9c4939ed07b708c, 3f5a130b9cff2344e164b672214b2b7c9cb743fdf5929f446bea5ca7dcd65037, 78e9bff304547a8c293ec888fd5d5402569f12218fd40ecab98d43faf128dbb6 -c8499efede52f1d9d691f765dc968aaa1d0a46426b4371af98f0b088dabf8b1a, 49255d84bc9925626a8634c7a87d50981c4ef30b03d2937a8fc36282f4fbd57b, b733286f05e26b8a69bdaface83a98c776437904aff747999d03dbcb1ea7b2df -895392a14cf8807bcd35cbd77201d21469d558fab43945dbee0b4e017b646fc6, a1c30958914108f814e45797d21b5df5aee55f30298828eeea95baffcbf076bf, 3eb54b480efafaa0d88fc8d592def96ee6cbd25e05c655991d1ab39421b2bd95 -8f9e9790d4812b5f8c340b76dc2f4abbadf036e5c281d9d9b107b5da88fa6e3c, 98fae71777f31b6aa81992c58efdaca55b76fcd9ec7fa74969af3ef32d8f54a0, 058084f0110ccada030ea0f33a88163fa29865ef5fff71ad1d54d34f7f34410a -298e3afe813478aa29f189326e92fe7cf4ea38fcb4fc76e39f1074a0f0567d3d, a9da4de1b352b852ac3890f5fae059f20ccf7ebdb815e0a82c7a465d177b917b, 03bd119db55d53a588f5f10287d220359469bf455b84c32d731918e18fb009b6 -9afa5427298c00fe6351271ba51ed7bd7b7d8be88e10f832158c7a3f7fc0297a, e7fba61662dca347217877d039b85f3672c0b1f32b080a8f56cc5ec8d5698c4f, 3005ba08279f36d05d5f9b4599748e07d5a91b331463d60576ddc80c07f4c6fe -fee59f1566363bdbb68d791c008508a026dc44e7fbb32d29b384c139028a9d8a, d85f6f4c9b58c96c8649eaf32a4400bdfc0eea4fa8b42d22ea559ef91293d6ee, fd57c49c5eef4c07540fade485247ba7dfed669eac257a8d9bd3cd21ce199611 -d3ca209fee80aae05f589c8977647ca52e97f6d00a0423e019d988296ed95aa8, 49cea8fd63affccc17e6063922b363eec390898bf74e2dda825b790d1af61487, b2a97943834c6facdddd2713f40e963d78aedde2c4423627bafefcd49899b057 -34e06033f8495bfe9d67792a68242731ebef1cf6e8c7c068dd28ffbb2aa6c7e6, 26e30853ca889dbe1dc1e33a2e31d1958b988ff3e3147b18b0f54e5dc5b92911, 3d9925ce20fdbf78dca6e52aee9a68c399eedc6e67fba0e944a5a17c6fb0a5a9 -ea1318a2c593e4ae22d01d05d89301f2d96b55a8290f21c9209f11de1aa4a63b, 3b3b44ffac4b680f231d7da4dda260789093e556d9301c22c4d401b719bc1be3, c5267d552e6b6b9c4928cc1ab3e2e9bbc199fb100d030cc0112e76a1850976ce -e7a621d49c438049c360edb084f9da053a04a042889a42cb735975108dbed3d0, 058173575412bdc44d98c966766c74526533af47067bcacff11c109eefb7871c, a65d42856569492a31d0602fae1eff1c64071d616820f6dadebca69e8ab71b23 -fb3be171a6f9e3b2b012228e63958f7e2eeaafbac739ea3b724f11b04cea6802, a59229676a2546c4c2a842bb115de97d8e8eb2dc7701868f22ecdbfb5da91515, 1ab6659e0dadc397f84a6ea14d1c2886b41746ceb9ae99dc992a5cd4112a199d -1ba6eaafe8dfc8a8defdc1a9aafaad14072a6375cf1f97408c0e2c91f9b913ce, 6e9871c9a3f9a59c72ab52b8a8ba1a136b0662a08c60f6a45fbec76b1ea6ceab, a553c4b49176b42f862a009514b85ce2af961d942ae33af2f449e3d664ccb230 -f17d38980c5aa79e9298057547f98b72d69bc6731985d530256a8e50f7900d16, e019ca45cc1f355804075f65d08f0d7b911101f73e6187b28aaed218e25e66c2, da73177005b812481395f4b7419a30bdc7a3d4f560ba816e3e19162c3fa4651d -b665a20bb2dea0f1ed9aa77236ddad2f02a516ccddb9f0e91089f7a9d885470d, 70320c8ebfd02c78fb068515e1646b9581c4c3e158fddc1cd9b88807c6bd04be, 772fe3a6f3f9c10d23493afd549024839484f665e1f4a78b5b90a01f2809a116 -f6e954cb33d57a130769d9f2e1d5da53b76ba844b25da132bba8425615d5f2be, 98dc157d2d6d6e2e8de48bd766365441062a6d6983cfcabf5aa67bd6df64ec42, d360bfb08f09378245f9a5eb93d847a26c78f845b5966ae4d6b5c4c0f9a44c27 -5193ed440c4b628152a46842d1e2e3f5287892f34b1d907183064d303fec5fae, 7c8050bdba8a3a7c91cf52aad68426d4b6e28a10a3650440c8902a823f60c34b, c8d2d72686003f83606efc60d7bcd86eba7659df766289590872752810132342 -8603f0ffff4416ea8b4bb0068efbef2eecc896204d7569ca78813a71f924520f, 593bcd1b1415478832cfb30f7c1a33a306a3d1ab7f5ea14e429593e1068a0cfb, d9f17776b907d94aff6e4b1ca5c86494739787065a61addb73b784cc98cdc42b -b17eb0cf55fe1d5fbfe19ec43f73c819de46fcb862f577b775d4ab3bde726563, dc5973e70ed28dfdcdb556d794f73f45d101b421dc99f6541ddaee1928cedc65, bca48020a8af744e247dcd2309b65044f8e23ee19f69745ef35eb432c962d206 -7f1d7cf72220a062a3eee77b27146eb1369394b312e8c66b2c0556fbeaad5e7e, 3ee7a3f55b51184fe7fae2e0b7020e3e0be6671335ac6c570b820fe5b002b7ac, b3da8eafdaae4402677fb492d2cc68d1aea78916c3b7b5ab0001c810ac493b34 -2ce772a743573f232e668af9da977ccf050d8274402c03aa9973d26366c5b2c9, ef5f0ab5f8f9c844b049f376b68dbd7f8761c5591b5cdb808c1c47c5da127bc3, e8f38889b62d030e7893aa2c06713349a8f85bd496b38ae56bda0e032311c3c4 -26242ae52d1ef96ad42f71c29fe8872eac885f7acece150877cd199419d3f367, 0102da202bc67396ce74067653fba176d60bdceda07b1c0adfab614ec2ff40d3, ada8567e2d64308ff7f72ac2ba16b5134e04e9f7ef047afa7dcdc1df75cdb683 -4175ee2c90926e07927792c96000c3c133f502f5f85a204e5f156939eacf8f41, 5106a729a70b2c3a73c7f1a3f79d2e66912cfe365fcc5167b5a7d789fee024c6, 4599d90f8c47661096e1aaa3829ade04455f60bc9b2faa46c5fec3ddff986fbe -ed315221b8b4086f2a1132235eab7331c0fb7cda01e756457d67bbb6d15d37da, 3e6ea549e66dc7e233b81beb2af9881768979c24139e1106917c5256920cbdb5, f7b058f43e04debfb6a38a0ea17be350dc8a7562decad90da4ce728e30855fd5 -b33870fea08a896ff4e387dac04bacbdb0206bd4e54281d8678b0788b92541fb, 88ea42e0bfc97bf10677c1bdf8370b4389f263c4b145bd7013bddcd6c3fe4418, 68d06cd3869b2653319117f167d36699cc8edd24475dc135282ede536fa9f7b3 -99e0395b298baddfad60d7ed9e5904f4bb3b521142f955e1b4713e5db8b6b569, d1c6b5266376956eaecb043636c3f7b388932c5ef3c979940a05f02124a2719f, ccf47788ce187b19b13f3c6865ba7f442ee55b7105cddf2c17feef39f2f877fa -ada2943265ecbc90e3c2a10a8bc011ed42ace94f8f008c243fbb4515759addfe, 0e0a01c783ef2b82b9562cac4836217aae3077d41ffe7c0da5d9bf3b0e7d7ae0, f6c58f113637d1086b756cb821f72a53607933627f1489911fcce1eb349fe930 -fd5b142b80d2817d3360cd190233d7093b3d89bc5e3eba4bce60c9652d64f3d9, 9a424259d4504c4542d76be5a6239d6d9876cb9d89c2c0889b9a5df698392bc1, 857e61c10bba505e8b9728bc052169b0e561f63c9d1b8c5ca2a946a8f8e1da31 -3a8a0562e3b95c28fbf95ae1bd2e7d961fcc0780f24303f68c37dc2e533db21e, e965d302ca135a5fee8260a04eed9f58eab04ad01589d23e1fb95d25f55b788d, cea8065ed50ba40964d890745b0bdbfa22543ab2ea5864715264503675b89ca7 -c47304b059fdb79c406d12df4f840b2a0f063bd4c984786e73da61c14e459907, f39e5bf575b4c7bdb95abac15b0972012f955c2f5c60eba7610e8589c59a033a, db60eeee94c1227be8074819f4fb5be25fb45401da6df5f93e1181d336af54cb -83ed5549737709a2e3d3eec9d5a5fcd1baaa2669a3022a987723f2caabc41de1, 4ffca285f2a03644598d6fbb8063151de23e5d1a3a261a088dec3fc36381a1b5, 0a9f25a51835bde58936f3161876d0d0ec5e74c24d7bb45bcf88e8d9be8add49 -c32ef02d6237d8ff75e541eedfe43c5559b7eb7bb9a54bd5b214924ee210ea96, 4ba59fd17832df0f140cf291f331129ed0cb672508419b4187882c4cf6a122c4, c40d29791d60252920ad378f81de67afe13b7ae86c46428fa3f139a7ef299751 -743c92f32616f6a6ba6057175ca286fcada696fae8d99fef18a590caaa4c33b8, 119a2db9b9849ec898e6b734dc5df745e691e5d53949e5a29befe5706f1a1173, 39dd4dbe9df46b63af3b98168308a5e63629221594faf917ff7d812467b4c839 -7650b9d39844034ae820141e9066b11620efe969a2e5da015b044614e928bd47, 5c56fddf88d68d23fcc0b13d1f7b823db5b9d1bc5bc133f54cc5751582d80a7a, ac6dc120ef62bc4efc5f4947a38dda80baf56c5d91bfe2d9ceea85f28b061788 -db1e0428a7b891ed3a1e57b79a9acea64b2362232e353a4cdb5263cf9660d966, 660b0f9f204b3f10e9572abec82a8f365abe5c31242547e9c766d09c13e25677, a15ec0f89255e4d63436d0a636de650f4c387cf50b9421e84a87da2d2a686548 -d1e36d6175b88d0bdb9dd3a01490cf3722f7c795c86051436befcc0ef9b63bac, 3bbf0662375d2083bce3d1eb9f2ddac1f0f3499dcc89a7f5242f3af0d038a498, c0c0bd8a0dcf89f6705db78660dafbc1a5d968c997eebebf79bb7ed814c883a9 -41057151c15e07fcbcb71121ec64043019ebf1ecfed3fd945d08c716bbbc0646, de858375b5a60ec6530b21a2caf9654618cf5c6d67b8046497e030e3dfc80ee1, 418ef2d7a7c500bd0c7edcb44718afa5f070afc19ee7f6a4c90762ef9e5cfe44 -7264e44a766a36f969b1ee0ff1dd76033ed116c066713ddde7e20beea7252a6e, 9fc6aeae2b2ee304dd218ea2fab801a8109e56d1e69836095e08ae9d677e03f4, 21d93c0b10bed0442043195796fa241ae2c34510ed69eeb450967bd2ccb69d04 -94d4e80296adf1d2457a8e8eba7ca0f9b04af7a60963dd1c8d194c5b21dc11c5, 1c5408c7dde98aef66f11c96749434ed257ba61583421f5cf4cc6d534a6992d0, 3431c6c768640eb3cdfa7469d65051932aa7409bb5191c82b68ecf4e48eb3b29 -9c357152ff9bfd5e7af96fdf76a989cf65b0305cb483b8ffe0e598081d2b6e3d, edae309894cf38ea4b1ad23b9d7b89bd60ac0e4a76951dbb3207aa00705dac56, 2b54c30e0432666dbf47316dc660394dbff5566c5813354eef1a3c506e0de667 -e927405f7acb922ef58a8ac42bdf1e121eb9bfe6e87b5f20cfa1727c4c8aacc4, 4aa0b60675dc855de44553678b2336488b8274833b9ecb8fe481c417ff5c31b6, 60d0797e4f8a3551c5b9d7261f735d57c752bde04b736ff6e0fc3a39a0ad75b3 -ded7b14119a2bba1fa7bd01267eef137b268647dd92f08bafca111985b4d58dc, 85cc87989b11688ec326da243cc8c52ca4fdd43862d7b9ba78587840358df825, 4be674731e225548c1aeac1717dfb45f7a80903f212c6f96aa3adb424a7df6a3 -030bed7be1adcab133a7f628ab031632039537f6bd4715558e05e23fbcb4a35e, 2ee82a6a89a3cc104c2bbe0024ab27ae714cc7bcff005612690fab2150945419, 61427d8a66897a3781b43ba0196c2358a5742c33e3c53e39b3262a7f604d0f35 -c4225c52a2581e858b4b43e54d879c243760c69abc2948653fb56f813c447e3f, 351e3b0cf5d241e9df6ba88222c873dd87a10d861abe642b784c2ad8152493b8, 849d974f0e006e0b10cacf18c8afe95c1184fd53fa325376a2216dde0e255fb0 -3cc4afdd22d31d1ec1cbf7d737417a065f1251f4d63dad5f24b42b6d04d2b25d, 7d8f2f07b2285d18b57bc78b0c3872bafc12d4a3516a586dd22775a598e96aa1, ea4fae363365ad3bc1914f17471213b32218ed0c0a8ad5154f01a852fbe231c5 -a187339102c09f69fa682afe59308efd5dd8f9d59ecfba7d3d9989d51343cc41, 905197e1bb022e48c47e4707ddde5d4437d4526ab340f1790c8a44d5cbdc014d, d6742e500b944f60b2ab83f3ed04c256e8a8eeda9405cacf254911c9e870a4d3 -643d4de094c6252e22dcf606352026da68b078d8f111f3c08a098333c01c1c0a, e65ef52fd9498ed3f050a0fb06249875bc1ec677fcddf1a072110ae83298b770, eac0dd5a2356cb7549e1ecdd28ba173bacb550fdf1dfea55e5acae1bf6731b91 -23a0044c210c71f4b4103fb6c5d92801bff6b583f78d65330ed28b50fca15339, 3f5c09ca91ed23dadce3986743567eeedc88bc4ed7c775abf8d8fdd2f28df1ee, 365a789fb42a8512234eb1183cb9cf9d4dfc9830b22e3d05f9e30b8350f6a511 -f77d887e5c6dbd75fb40bdb150e5e34e121033e6b716999d6018c54d72de483c, 43dc046476135a5d812661f4b9fddad49797013749041c575e9609c4713f8116, 531bed28231af68114e2dcce46241fcb714cc07934a58535b1c72bee372fd9ba -46fa044797745feeba9e8b868fc51f37c45cdfb4a1c360972fa48018f28aa1c2, e7f3f14e9cd96c93e7f560ceb75d753b816b98f5c0188ebdaa57443f3e170036, af5f3bfb8915596d37a55bb02402c460a7b4d43f1842d75c6d6a74212d39421f -29e0cfb8e3fdf905dd359fa4db3333679b3d21d49b42a0f06b686e30d59a37dd, 2ed5076aedf9156c5d90bff9eeac3811a1019da35e584d6832d204597882fb61, 3dce65fd2f9a383e45286ac14e10ed434401215f7800d1e7ae0f4453541de1b7 -4dd448c246230bc3f11be1a301fbfec02b60fb7fd38b1a04bd3f0ca7870116fc, f48240f0fbcf926f260be5b7cf68dd7776e117c41eff3c4d43a2d5b3121210e7, 7fc84b31b390655a947b7476b055cc5ce286b49f097889cedac9712509cfba32 -63ceed5b765f4b51ba23db3943924654ae8058e0ec8644eb4f05ec93ddb64935, 3718988df681796c5919837c92cd8d6c894387deffeb4be147582355c8214988, 6b9c6566a40f4c8d6313c1593531c3f95e48a562a30aed9751fb2e3f08036c0f -34c5ffa58fd5050dabd2a26b57aa12c6e897645f041331eac298618597024ce9, 16d89875d663a3708272fcefa0ffb73c1e4d93c4d3baa2015cad1e67bdc09542, ba4e9dbb7e8ad7f86cafb9f4de18038b223260d39cfc70af72db3724b6db047e -1c504fe2820f2271fa22d7f4900e7c6847a9adc69ea5e16df2f98aa2c77739e2, 982d5c7101b10c2af008fb29b072c185d7d4d79415484ea7955a66498f109ea0, 845cd014f802445d474038994453332d2dda40469f91913fbdfe78b18c78406d -a8912024d2d19b63b21875db68a1809f03f2c90bbd042ba7f5a12eff1419ded3, f9a6d177bcf8cf4599e3004e1ed3644ec2c45d854edcb9df0988a466ad6cc83a, 5d7e0023704eca645ae8d0dcb0317e1be4a7b91710fa2782902b8a44eb9b559a -6b7a7ab7cb19aeb27473dc778414e19e5e300ab37517e81d547d7b03251d57cd, e66f3e18d1e7fabb0a83dcf8074b57f90d529f3f7f5192292f6ca10266139816, 4d23a39a5935527b9cb6e03baec756bea2e88eff0b2630d0edd164c27df97753 -05eded4550f09869ec75b4e5106d06cb65e28c84a02b999c3d9a932e5da34951, 5ad04f3e38b3ab49fed60c151f090ce8d4812ba4752932548109f5605c62ed7e, daddc53bdfb790434e9c9d482fec8ea35c2c1af4b3d21c9c42d26c110b35bb2a -558c22719f4e7d11945b97a5c76f9fc438ac57e31343ead0c32cbe3f9bb63b16, dc1b8c0848ef11dcd44c6ccc5874bcfeb98684b649a26cec8881aed9f7f674e4, bfdbdc6f94d04d21837e8ad52b91ffaf512915eb8e400f245142b1b92f8913da -8caa196aca6eea7e1a9ffa8fecec6d831f6f7baa3b5f43e72686121edaf1a185, 6ac73dd34494fdc673d0341273a3ec3c233bdba8e29c37ab7511c3455d6f9b79, 13c917efca6ca014ce9223c154f376b52f38d69d774116250592853656556c6a -543d9996f124168630d5218c635ab04b51c5edfe4add36895dc8ba17fe591d1c, 9ee486e0ab297f9dd244ac9863212bef5ac9a04d442afcbdf29f7dfac7f6c04b, 011e4ab3ee33401fb993ae42ace726e503ca00481f929bcfda9737dae15b8e71 -7b6ae543d0ef6ea352b0ee0615c116cf71bd2bbdee919230810c7fd70732b37f, 93b005b2a39e239e0dfcdd58443bf12824a16f34551e06a065a88bec295ebd38, c7aaff013090dc03e5fd640fdffc53506681634bc4b7f8d66076aceff71f2b1b -5c903f4d9c9a40a7a8fe893ba012162ab463e6c953f77556fd14b3e8b22167af, c98e24852d6db1f823fa6e8103228285f4b231fe4f360d63ad18787dccda6cf1, e4417bfa86a83ee6fbb54f97568ea71079373b890024a4728fc9595693c07269 -3b36f28bb22b971285a6bc7cc26431f99d2419e54bba14c5eeeaa539e7e92e79, 2d162b7f8a135835d9f941d48984fba8866e35fc32940f0f207c29e4d58847db, d3f9c1a3a3d57764647b8e5421c08f1d57aa2c589b38a77ef3935b208ab78399 -a1109eaa8323d1675f84d0ac562c4c79c114a75b92192a375290240d5090b8f1, a9956a40eccea973e3c815614ca5e0efb86fcbc1b1bf07610b109127c9ae9482, e7dec54080b4368576ff366000c9f787506e5fb0f76c47039cf59e4e1118f433 -a3271e5380ef1fb53a7d5bcf891728c0c6f6e0ef0acd56eabb18699c0b16a66b, 3a0a797deab4c9523c9fb6a6630101af0c8fdd7498604cca74a13e0bd2ee366b, 856ac08f92e0db1c8dc4a640fac823fd048d7705db856b61c4b690c13b4d411d -c83192dc0e4f9dd527c076c5001c27241ebc21659ef86c859464982f1118adde, 8c9a6d40b8e8a4c18039755baf2a1f7fe22ae290a59686d1aef46b34b3ced665, aa040a19abd33d1c3e11931c23f0f0dcd9cf6e8cbc6e3557a59af565ab7f2c0d -5ff50d1d8a56af42c4b5a72787f49cc090f59e9f8f69ad1d5db842be33cff9c5, e80f9f6dba9b171856cbaa51a253ac69147defaa5d162adf049fd00bef1d38b2, 6032efb102c7aae2f84f7d4bcc9bad306c27eab7a8bdcf1d91f7a330ba55e329 -1bd2028f4e6030972aa78793d8cda498d08662d8c5d075bd1d22cff3b95944b3, 2814ae876ae86b7b9ecf89e893a38756c147fcf7a577f3fbbee1a0b7cb80ff3e, 6321797c554664eb31229564ed1068d590b5526e46449b4c96f555fdfb587e47 -08236565e69a4fcd88a62d8c44d78b95629c46f5690e5ae28fac657797c6711a, f15c831e99b497555a6b5881e143455c31cfa04e598c4626b9ade318f2f55765, 182fd58b52b52950eddc28975b141724d47cea488d65381e29d39d3c29926ec8 -0f6a4437c4e4aebf4803a036915e9908f82380b5bcd401fc674a4f944445baaf, b626fa4e09a9c6b3ba9cc2631da6a5179828024198f1bb0d240ed045f6a4b6a1, 40e1af0a1ab20d23369084ac30ed1aa1beb067eb9f15754fb1c202246fdb1035 -ba717a06996b11bd87ec9ca5e93a0231f3e455ea2fa8ade9253ad4eca574a6f7, aa23e4528e300097e0a9dfe2b7e6e7db71f902a2c768296f9eb15a30b44ce8ff, 2fdc327ef51e8e074a5ace52b610bfbab0ada652092073c7e4cbdc11326c51f8 -5cad8e962c4a0d7b00765e1c6f4ebbd17468d44deab07ec600515151ac3ab7c8, 7579a5185a34c39a3a928147add251f011a7c621872e444e6ecdf6a006775beb, 091b8603feb97c48c75ded60cf38bc12e21662c143380565d9af4d1545fd415a -3b36db409e70b1267c768d431152be6a8312a0ddfca88e565e09ea5c85ef6fda, 2fc0f040a45c8c3140f005caa3cff9cb2e3c5efd788a214a3eb731ac8d082575, f769e7a57ad73ca3ae1d4b3d6b5d00122f0db96ecdb0302befe98140afe3019d -aabdc34f6a79d9637ac1726b1fc5e8c35a5e55db1ff2e0b21252c3553224cb72, eb7f52715b1e66f699bd3a116f797e0a12d30ffbf947539d26badce3015c7e40, 5f042c9bd4d18c0e7e67bc61d9b62ff025830bf0cc75d64a3192d04276405dca -70dcd66a7dab955ccaa265ad5219482c9e9721c22bea95100d75935988569ab6, c889f45fc8e2c996591d5fcd19a5b83651015a260cccc95da344a88ece7baedf, c3b76507ef15d27d96d7e22d17b56f3e8eee359a492ec3f4dbd73e02bc2a565a -1f4b6ee8735b02bf6ee2c02851e99f3541d0e73930201635861440ec78d5306f, 0e8bf9734f71731b7fbe633f590abc84cf49e8f9b57d2984d4bfbe76d546b33f, 83444048204aae392adab314bfbdfd35a0a5c76ad93317c9b9eec7d728d301c1 -0166a11056b1d820143cc4a4ada13e1b3a5f0dbe198be25ce5423590ad6618af, 1cff13cd82b0689f1e2ac91a6a5bc6aeed6515a78b61cfd4532daba15e5a1c08, 9672e23777af26913d8a6e433af1a4a8bc6f837da63b1a76918bcb152878c031 -cf54ef27b6711fbab47144309b6e90fc3e72203c83153703506617f4ddc283a8, fbd29c2187168967ee46e56751fb22be376fd55b8f689323656e37f6bd5d78e1, 33961812dee929de388bcf04e517391e87ab8d6cd92ce4e9d46c69b046011bcb -d4065d4d6bc5b13fa486a9146626174900d4b3828fd954fff4d431500bc9bf13, f22afcea9cbaf36a6c2ccb5ab7d5026a571af0ebe9cf104c410e14a009be2ae0, e1b94f1bec56bee3ea3f04e2770a3c3090baf12c9be828e184b66ce098fbf3a1 -393e1d55746f1bb98c5bb7d2131643f37bc4edd3d6bc5d07e02564fa37cb549f, c924ce7c3b97f9824edaf88161075660b51a9ed30ba37812c6aefcb3fab7d86b, 82b1438dd13b572fcc8505e6cc225c9f7e1e60ffae7af599de91ce1bb0a6fb7e -62bf6c30ea482b6a9a17f26de679f4d36300a683e3c82d19d7a7e507c3ec1807, 99b5d9f294b724999dfa436acf62bf711a49edb5351df982b84ae880596af50a, 932981258d722c9fdb0da862ff3ba495cfa5c7a2c6da18e0b9687507ae7d3291 -4bb7ac3ee04340812b4b92b1d9bcf8f8c1ba683958702f59a93c98474211def5, d5d9ca675f68c661693d5cb5431a740a36f8bf8eab66da82b4a3bbd4e40f86ac, 323a958c5ea0a6b2f4a351a1a1a27bf315b4e40bf63041d1e96b4110cf690302 -54f2cee88a6d4d735056b8b7eca1aec1c34f229c01a25bb6c34df75ceecc6722, 3598cfdae171e8714d820d8dfaad21553f38711924a7e2fc0bc65f8930512e9e, c620f5e5fa41f8acde5e37bdc543631b749886eb840c5a2bb51bd04f1ff9524e -241ef6d53ea7b83fa0c9aa81bc7cdde94875f4c054cddb9fb31cd07304e9d005, b49df9f3c62871ba67961d9cdfb9e7633bd960a52b5781148412301c515d88b2, 44a5ab8725bea6339991357eb10a4c7370ee5f818527f685ef1c1ca5c3a3db48 -1d1d52dee61db17a01738410e280cb6bf472d72137111cac509f05e85042795a, 95ff4f33bce14fa9ea25ca6b7ab41a7c90adf291dcd0579b69ac11aa89039af3, 699d1c402b4666fc6bf145a2a1a52c90911d0d2d95b3cb8c8566fa99f00b2f62 -f743134f15be64cbea9d286091441f3fcb23705d0d0e7bc483776774c9f03ce4, bfae0c28c07d30714ed7bfa1847e3d32666a795d6e7d909ce6ad930f05f1d23e, f663c18c86b49910725241efc602f6d6a7afcf23ba3a2c26f6e7f9ba5c93d83b -6b83d27ff0dd0a52ee32c5ab5917ade346c39fd8b6e2151e2b679cfd6123ee60, 82cbbbceeed6e6cbee5c1ea2e6a03d112c7a302f548c36c805bc24e3ba2d8ba1, 8e4750478d2d2411b1b0a74df66863fc7ca55f897302a757d061a626eaf46863 -a4c063984e850269ceb421fc1a8834d2525ad137be67bdc134f0a2f2950c7d15, 77944936c17700dddd03fbbc5554fbb99b0fbb179fce87504e92d4318cc5d36c, 941dc6331cacfd787060566959e13126f9543a7aaecc8b32014a8fae0c012470 -0c2a4fd179c9901a73fe0cc06ef0aca6ada875660b134c575a4e21ad3810b501, de73c2f7ff1405c32b1f3abbde49762d971834e4ff7cb0129e3af65b9f4bbaa9, da26ba88594b1651766dd8cc259cbc76afff56d9fa96e3251bd314a1a9295da3 -7c15265cb5eb653c2ff23dbbb96d07cc04ec558e9319a14cc95643370cf02e58, 3218dcda1e655c74ff2ff00cfa58347b49e128f9cd2827e449d8f8953cc177d6, 75868aded4a3c4df6a6f6f60aeade0d3cbe494fbf2752de483a363fc8491197c -2ea0f6cacf829eadb9c24756b0f91194491d0518c9646fd77746ef40235413de, 459d21d494c893ba50644e2af8443784bc9040bd93fe1067f5748252305b58ad, 569dd3476b890049e641e8530bc25a734c984902a7249f1980fec414b4cbb257 -220251966a0a4a3a5247409dd85a36d264ea4a4d52e330c211a617ab232fe448, 8e6265ee6927b610ded261b98ffea44b26154b66e82e9fe9ebb20c973fb1f81a, 27b2d947326e4ad3dfa7e9363bbb03cc01a5e5c98af622afcefa262a6f2cba56 -db10ed0c306243746cc4ca398fb9cae16c21be93cbed67558fd61ffe5c68da5c, 0b2b8ae06d15ccffb23775ef57e559c223e30058a6ef39fe7525b31deb1fa369, 4ac94a81a71f51b459bd8f028c2fdc05f667231af219aabcbe1a2e62604f27e4 -c68bb78bf14c175623dbf65ea9ba72d35310867e989f591ca594782c60a295ad, 15b5913605f4e155bac6a87c28ca0eae5a890ebcd300aba956fdddfc31235bcf, f39d0ef5ea107b3e85fda47a50e331629268e62f28c1d0536aeb7510fc8ade26 -d2ab9938cc63dc2b33a62bbdff448200272020855d931b8bb6ffd1532b143a54, 6583ce731b6bc12753be76049d2fef5b32fc260ae2fff5dab815be3188174306, e1fc9f1f8846cc02d39976e069ca939e327e74f390aae9b393df413a0a1e5dda -e1b45eb5d28306e5d5451266a1829ba3dbd88e63cdec61465e618dcc7ab389b1, 5781cc82ffef86199b3d32c9b3a06d83a1a86726498095cc92550ed31a5b26c7, 0c6e98dd3389ddfbd0b74432d6d8242e7588011f02637991bff1d4ba16015cb4 -2791dd0c7212c8e0b5fff87cdcdad19365e7a1fdd9e745b45ba28f6d57aabc55, 83cee4ffac53f23be86c55c47a1dd7dd0160c8b35860df33499abf59d1499ec2, 4ba8642d57646de179f2bc2d974a89535ddc3c2506e677e2d42a2d93b0a5a891 -89099a83c2084c393d0cb043bfd38bea3b21ed0e6cc6a19a9f2f3fc4ae88ac8a, 5df36c4e0e2163cebdb5f168531daf42338634295d9f22f5146c6c613599d5a8, 4af209c3cc1b69290ceb0c3c1964fd502a422391a92cd5f4bf07ff8194d48bcd -480c1f7c2d24f5e246ed4ed0fd5192a796fb768c8fe0b053f3446f1ca15a8ee4, 3a4de30c0cae2be71810e96cca76daedb90fb727ae8e45b83e03e129b99a23f1, 7b4c7290890ef7bcc4598340eb3ad5c35ac3607dd96ba2cbf28749cb14b7c6e1 -9568488a4b0b5ec9bce29b298373db23b87f4dc358f1632260692c95378d6d40, f58837e87d4268c8e68bb0e266580d1d3ba87e0f2301b79acbf86dd5251d6b33, 2043417b7ea26a0fa15f70c5ce71eeefb5296099a2798a0b0309d487eabeabf1 -f93f2e0c440e73f59eb793305b8067368567be86eaecdbbcedc66d20d767987b, 7889d83ab945110f4d01cd6a07b197d297e68b16ea75ea7131b04b0659bd9efb, 00cf3b0b3f98f95685b8e756595744aaed2bb03c5a0108fc530cc699ded246e0 -f140f54ffbdd10d1fa0baef2274b9fdab712467a3fb550d15bbb62685b6f62bc, a13fc89ad924f884ed284ca53d56f9cbd580bd80c323c40b6368bff357498f7a, e46699bfd36144b8a06a562d20577967d889c349e78dab709c398ac27b2fe2b7 -b8dc36657dadeb95bca8f5ef2d4be3e7c25cf53d8f21608b96be1ade01509f20, f98c7be277e63c8c652bff69dd47450b092aa5f57adf692c92c8794b4d41a61d, ec47e382c7e3e5457b40b7df422c77a400b1a286ebf0e137e8dd00ce7680606b -fdb5520269d6dcf9e51a6fc0f039351bd40574c11b5d5684a5f12ec6a88f67a3, bdf57317ab92d00785d96d3c98f42238bb5821254853ad9b1714c2f12a64920e, a509f5c850467cc89ed48474a7e322396095ecb4b34f2975595578fbdeecac91 -1446539c6b8aeb8fdd4786fb49ed0ef79c043502c5ae5b5a1b8dee8077ec36a8, 052e2da1e6a1be3b4bf60566a699207756cfa6f23d2125d0d1e49de0480efb58, 8dc5de46c0fe09c5ddd649d9e6a59f4231568fb121d38108a7e174f9257da2f4 -7b78daa73045414ddb10c8d9cd092f3e6240d64a1c98b71a6cc7b6818b98ac65, d44d4cb916abaae9fbf1aebb81c1fd1d914047c30a9005accd72925e611332f4, 1ccae304c1241b6020d36b1c77608dfa15b54fedb60bed63ca3f89f1e4e25d0a -e43daa06d74cb9d91b4df806ba730fd4dba793c722000d8d0195a628e585d338, d5f1e3f71a23a5b1e045b94557d19cbb7d2a64633096b4481d2ef8e01c26bf3c, 78c918fe31056cb65fa206d3eb2e150bb85896d05ed54e4a8a3433268586fa71 -709600e730c9f0d8ce84f8d4619dee97f4be3870ec1cca2faa949e5a30d31fcc, 3ecd551246d9cd0cbd0a3178eb070480c1c4ea7dff0dc4d7e2e215fb96be1d5f, b9252f82e3c5f759f9cf44705f4e5c699d26117928f3352033ce39959656e4c5 -1c5c239953d8e8a962b34fb041895fa9af3c64a9e53307f35c2988a85eb50fc7, 29bcfd8230d28f53ea70ced0f99c3fb3fde045b0597730428de4adb297453350, 3cf8ae2d5a22c164ac262715d8e05f43b3c0cbfa1885a469c5b33e33ef3a4ed2 -bbee804d1fc609813811310bd258bcded4cd782bce321fb784e24b1313352c0f, fdc05083b3b67c830d5ff2026792fddee53b190c00751be347f0c28d2cb7a5f1, b5d5ca781d60bbf7fbc1949f39bf4a0e36bb0acbc9738f0f75eda064a127992d -faba7c9fe6ad755b01fe92bd6fc631a367ea377c22e8fe6b21b9fed86cb3afb3, 8411113e01ff799bc5491b2c68a2513f5a911ac8dcaafb8a037cd748706e3039, 777c4f3d7a716e9e69adec815995580497d499faff335bec1b7fc85f12bd74cd -8c980cd2466cc24fccba044576618ca7793f13ce0cb8c4fc4aa74b1f11e53e21, 89a4de9b1b8d7f03d6f4f6c2079c2d31ab35ad72a0de0d831d35c9c961215b6b, b1c478de71f22dfa169aaa21d53726a9b6a3efb4c2abb672beb9e2f33b8c4c4f -2b7b495b8a6d483a895d9d3db173f9f9570d5bf156315ce436ec38bab04b652a, 9c14d052b18da101b3dcfd2503577149ef1f6ff19e32771ca4cf6cf7c968ddf4, 43a1d8699d57da78e33c73ae1f90894310888f79865c0b0d5aec43abe3f7bc3e -a176690ac98dad32894887dde853e8186c92e91bc99262939deaf4be79fca3db, c4b9488ad3e755ff3df72e73660ae2a9d2e94b24e1feb7337cb840b11ebe3bcc, 8811e5eca771b0ff3450f912b1a601b3e0b5be78730aa9dc52830a75c8c7989b -1b637a78c9d2f43196f98303b08884a1b45f17fd29cceb6f39bab4db0491b5b2, 8e59b26ca567269919d9dece6aaaef17e0e067bc40415f279da0adc1b507c4bf, 6bf952ff73202f54c884522dabb8580756e370cfd390b8f26d4282d87541aff9 -afa2f83fd3f74364a6e4b70f1736ac15d4dbdf0793036b3e6c4c1b36427fc471, 952856c49105344a1e51e1c60700e8992a0055ba2c3a9c8edf8336e2e4d9f4cb, 94c97c81ef89ab87698666c274aac145e25712e69878f8ffb0d3a536729dec34 -a404bcf7c9ad7a8a1caec3b67682f21ab12108fd62731ced1f7913f9898ba9f3, 6d1902de673d54ab7cd25616bee3511813022077031a7605a9acad1d8351a43d, b1d5ebfb1209994217ec64f1d26d784832e198b686503220388def7a5a3db0ce -b5d395cb4583f470d388d8355bd062db6753b8405e41d8e19824911ef1f789a3, aeee22fb9901a3358944ec1bb275330c1d71fdf34972a75afce7ec7496af335f, aa291f94a82362823210c0e3c4378f1017d576e6f95a91390c3b9d5b41ddb6f4 -55af59d5b12e127582d95b301854d33d324d0a2df9e90b90b9644182df98e49d, ea1f0c914ff4ea68e7b52de56d33c9b4ecf2f5e5102f5cef49663f86cfc02720, 8e33d68c4a91e500db2472960cc996a63802cec8eecd877ab4af095de4b104c4 -2ae79ebf07a1a33a3df94f29a0528f1893c1550278a12cc997b80c8502eec967, 22ec1831d7c15b09b45a0ff709c36d2d7fa768bc2b0dfd7931d2f36ef4b062b6, 2c9b495d65375d5e41789734186ba60d03b37dd1b7764e59513ec15370e82e73 -1d72138cf9e1a7b7e92da3e08866c3e88cd5126c7a79a63cbea0a820d1a7db6d, 8bebd8c514e4d3deb8f453bd650885a971d4002b576c4876b0a383d343c0bbb7, 5c137e50b52c9895467c2ebc5b17a7af391f946b42ea54dec388baf7fcbf21d8 -a14619bb8d3edf3d9b2bfea2303a18869fe3b58b91db4de74fb8305972d93956, 7f546a8b97600c3c93961e83328f704b716f6c560f7e866ad03b501a320be871, 0b703c4474095582b5c359ea9584c2711184521fb823cdc2b68468927f2d677a -a315c0bb11a9ec759df49e388d6f02ebef328db2808455cc6f971b94e94dbeee, 0a41453f00155db70d571d8b5133299fbf805aee53e5d383a6f369d5cb9ce309, b3552996cbb116485d033bde7f6aa7d8ce776844e6aacbce83ba81ea209b1287 -49532675b676356de6f2dcc5ac299daba228d8e5c9d0188e324db1e83b14bbdc, ced70fe6c67a609f5c63d21e1869c46afc8262f79aa2fe198b3cb15293e2c8f9, 54e651162f46a6d0859c33c714d8b7ec9e6810d5c98d9cd600b46bbf592a4798 -4c043654b67235c41de77858491b74d5b32964b85884be45ec338602f56760b9, 75740e61ccd6ef81623830a3c09133f80db70dac4e402585f6d5409c957db44b, fc2fe9fa828ba1e9ddad03d4f4ed984c3b4bf7b28618d30b5b2c95c76c58fa08 -a7a6975ef3e4d375b09d8c8291632acc56b7ce3211063336256e44580fe85dce, 5ee86c2d2dab6b437e4805fd9fc4a818bcd42ff4457cfce2dc50719939e7a7f9, 1c923c8af753d2d24afc5bee94d1f350bdcaaefc603a77ed36066eb4e69a9573 -485bf2a8fc11d690e3be651258a61fdc87a650791c7b0782a43b4cef99d4a4a1, 6b8c6c57e94e27890851e71afd687486b0f8aa9a5504dd22bcb7dbc06a48400e, 9824bae3dcd3dc48050b32a193c41d4b5e5d1d21155fde839af5828be2090630 -eeaee5149657b70f8f57856e418283b3730fd7463371f2abd1e530f75005e442, 6a7a2317a7614a43423ee132e0de9bfde462beb940d85a4d171325a1e004bd22, b195a6d7514a5a4edd0cd579e928c5a35dd51ed786f934ead29f71a7f5323b68 -b5541d2c73f347595f60f64202de530059c5a3d9addf81642429264b2d92f678, 5b3f4984f40d18b985200cf2aa1990d611a86bc46dbc3f7b16aad0f028e0edb2, 82df9e32c89e91d6d2d7a471dda9e60909defc58b044a6ac2787e02b7eae6e5d -962c914eed8380fe39c93022c3ef5dc551b7293efeffeb80f99ce3d016e02fbc, 4306eaf8e72a0a8efa53f347220059e3a1261f749fca5c1760cdb341e8bf49e9, b42ce84f22b5874f415ac3ce2c7e1ea8e1e2ef7dec37b25c0b3e68060a3c71a1 -f544ed01e23649841824aa2339c66874531e8639107e8408b0fbd797215ee0a2, 768e571a3f5288e58167af8a2b9d9be5ac14bbad46f26fe2c25d28b31b4d6b36, fa0197aa9ca10a15885cd5821961fed14801fcbad271c0c2f45625ddeb155d7b -e919529cabd35bdc2ab892b198b6a4856bbc7457489eccba19a17c1dd79df72c, 27ced58831fa2197117f570fe108c4a9846d2213faff74f1c1fcd3f2b3280ded, e50d0cbf42faa7b45b2239885b458aa459089027bd7525bac47dbb5652df8eed -cf62e2ce4929821d604c0aa068aed2f1adfd3fc923a90e1cfa87f6681ba0eb39, a5fc8e0ed905cfec2dbd0a39f6535af08c355c76ec06acc3e00909edb4b2554f, 74797cae7ca3c1e17b44167dd4ff268c3f07abad802cb0c2c05018b1faeed73e -4297efbf5226e2d49260f1ec6881cb0b3c574835afd28ed57feeb305d5584a29, 8e1821efe3163a9aab1a1735dda065bd694bab11750f2bc0105581fa6c29d4c9, 0231b729929c556cb3fce169e88379885e8b57c31085d39b43a9ddab579b9b66 -df7f21dddb99807141985eee6a5d85d049203c3d87a653eff65478481f4dfc3b, 5136de5e155aee0dd8c256f57de4a72cc88936ab90d2682f1c17742811cbb6a0, 210ab8d6bf090250d9d9f73367685c0b958e7daace83748076236593ee35409b -494d3030ebd3ac12a605fbaeb570899704dfd2fce6203c77dfad8471e1eec190, 09f7775c2c1c53e4e36c2808581c4e91c215f730dd4e72b08c19c5b3b59a85f1, 4be01e70457fac5d8cab386a03e54c5a7df3f6a6db1e099a2d74097e5ff0de48 -83c5be1e6bd56fdffd699a0a6ba5848c07d27fb6a7958db198d6e92dc4aea62a, 1ea615debf3824439046c91b2452f5d66f6b38f99a3a8a2d1eca70e4249585cb, b9043a1cab196fe6f308aaf046bea32afa0b69ee30ead8da290c5777dc594e91 -8041200bb038632c68569fb3427d6628bdf5965657c4ce321f4a653fe789fb58, 60f5c78dfc27fe03ac805e9520f6c21c4eda8b8583bf6f9b11ec2e89d0e97097, 2e33092e38ecdc551f5b1bd9fdf10361f558e38b7b7e720907816a6f676d26fb -c8482e966578aa318d32137049dc56b48af5a55c0b6f26ee7aeefe0c28457420, 6e40f0ee21779ae909656ecf979f16652edc32d374ae2444c528c61b85dd2b64, 95cd5e0303e23f38ceb6cf15ae2012c988758278eb923b9b9afcb1ae17184477 -fa88a17bd0833dc26363e4f3b8779a55c0bc100d802016b5e0a585ce0a33f666, 7f2d690dd240df7f182e2a52993cb18ffc8dc987217d8ee01b3ce6fe3345c61a, aa855c9bf190138ee46c41b52547891085adb207a409714430e272a548e3fcbe -510a9a03b006db6bd6f1a20893d8838ee9da62ba4013dca60d29aa97636226d2, e128bfcfd6cb085f097364887e635a9bad9d24ce056e6023be4ee24c22441a27, d2c0f9f5e3fc0c992690c8cf2b9c76c0389a2ad421b82ed9118f0f3e0349050d -8c6c2072517e0db75c5d0a27ba996a074fd481a9ba084677607220da9dcb329d, 41e94f5d87eca6f53e30faa0847ccecedc53a780d5d39163d985588f524e1585, 67e9b3f53690c3cdb314c112404fc40a2ed11c7a7041ee2803cb16d815691196 -8773afe2dfcb9e41d3dddc280ebaebbb29a03b4b3b0107ee30a833fd4a16bb78, d2dec1b9697d7640743699876bf368efc87d64aeb35c2b107b20277e88f7c492, 306c51ce6bac40f80ccc790b5420c00a2085e923b37f77218fc4ff61f2d1b904 -5b4a86006e54fe0cde72297ff8dd78873200fb9a34916ab8ba7dd600294ba637, 2f679212dbe8bf6b7981ff37a0da9259fc2bcf4ae2430511a5c0d5644c2ed551, 5f930e6e75fce9c85c02f8bd98c5d6d802069fb95fa544a4da1f6839638ab1d5 -07d87d19c500b4e59ef6750247d7ac917f6fb3fedf779afb21588387b5f553ec, 2503037a3b41b76b55b68daa96c082822085958ca9b00923d451666e1f73eba7, 802b8f28b6e95ad43c4e741e8afaf67301f443a8fada54be707e7877a5f9bd5a -63500927b9683b878a3e9f50407b50ebe31164eb45cee26ba66feca4c889ccba, b20ddfd0ca76da8d38054a4116de5b37a4f825bd63d429df39b6c8275fe1feb3, 23e98a3fb3bdd79e38b2fbc034271037a2468e1da2359e386db2a354af4292c9 -66fe4bed24f9aef590e2dc6fc4642605ccfc153b727821758f5e6f5bdeb9aaa1, d140bf06bfb905ba6af7f9bd37cdc058aa4de79e6f9c3e97467b9ece787a8558, 50e8b2ceba232a51631a224b495b8fdfdc8e36c75fe6a7719c67bca6584cf56c -03641c880e60cc789ae13a526d4ca1e50ceb3273970fc441c2e958a7c40a93ad, fc1ee4b911faf9510ab9dc0938e30db3235b5a186251a75f875ff570f9ab4f8c, e755c393d4003f5a7ef8e6854793afb02e635716ed8ad3e21f20658d2a25ca40 -4d8d77e86f61b7633232648476fc0699bf12a79999fbafb130cb4b74876d4ecd, 172b93e50092fec99b3b752d183962798db4353f6ea734feaa73bab23d6e1f39, 96335a8aba41803094a70b438d2b54e17480e3afa8446081776c9ac2b64d11e3 -b0afc196864c9399cb5dbc77e34b9c66e1b8e375106ce09358b3375d99e8ab4d, 6de81dd98c7b62c37a52bd7565232843a154ac2085d76624546b7ab1df5f4352, 6042ed47bac29b576d5f084772b8374c23f068e0c0dd21c319620a6db5a1a415 -df789811436e1cded18da4536dbf4e1b2d1280037003ee6ea4ce679683654185, 100ef2f7393abbcd755190bfb58189bd1537b66d9c3a12c810a220f4444617c3, 13930428bfb879e675c8427dc22c89349128c9b3686b4e7d23c6d7d2c3f8e142 -9a3087ba71f10f7bb33458a8a7ff52a2f57fef37ebf14464b7f2d3874e0715e2, 67a6f63fe58b0687f036e45d55ada7d080fb486fd58fc3f0a15144ca83ab0167, 95d92429e259e87d434dd74cfda10c5f199e19aea0be83a30cefc818fd48e258 -a2121f71d2e3f0e2331af0b5831538fcdfb242ba523a8abee0693d16a4ae9343, b2104b8428847da79fb479c7168a2a88b8a328c5927c7f6ae534d93d0f7cf70a, 25664a34378c5e0cbbdbe88af9fd41e565a9c781ad32ea48a01b2a91e6b31278 -7a04368b80e539dbb100e5113c8b9a8c5d9352ca4b04aa7bf47a421af97915a7, 17d617dad5c17d81a9aad9df33998e6d10d23314157657440719d78325410f78, 49591b418285cf528e2f17af335d415eadd37512415fffe554e527e6f07fcfe8 -ec859bbe5ba1bad3fb029f73dca26b98a208e7d1a7a2887ec2ddbfa8dc454ebf, 9d48e28e17866c09ba9eb4fffcb61f11cbbfe688324f3c2095ccea91b2836c44, 80181615514dd0cb79711c5b6804999fb2f31135669acdd794eaee7476f66224 -97e22cf712f5eefee411407bb57229e16e28388d4b3293fc2e9a9854c39d7404, a05c8c852ebcba0792fc2151d8df1f1fa5b22cab63d13a2c4a994dccdc063bd3, 997be930d0d631130b5453bf35c4227f6ba35e6565e4ddc42c48e1e4ca4a09ab -fb956a0899726ef43bb72607522e671528671184adbb674d68d374581f72805c, 912c626c1c1f1ee35c684aa742f23c3ef51ab0e6d7ad1a17ebdb5cefa1428d90, 2b49b5fed7a3ed1739e7daa610d2e13ef7bb125ec24ebfaf62ab81d1ea70533b -ba46b79dc316204f934eb238e5731f8d37c8cdca03b74cfa7f4e8d6fcd1d91fa, 84ee417b50fb735bb76bcfc2476b3615b00254b0385432d332767f2bb5fddde3, 438c5507c28aa3d3365b3c62bad3c09a7b8aa3fa56e9be2257fca75c06024cd8 -68cb6b13b7869e364c9991b80b6c83a4aefb046c5a887a0ee3f65fec32d82fe0, d992000841e78146d6d737d48358acb3cf94222717f397f292246e3c0903b59e, ab09dc20a529a26dd4dd679ac70aa8bba1bd5d1571a706305bcbdadc7b2bab33 -a771fb668e1746e1be75888c4543c7119bd4bdc36462f4d75cdbb4ae5fca87fc, 144ec2196f0b83fb327fab6e8dcc6890bd88a3481adaa3777b5772ecac6ec13a, b89fa2ee6c5a9a671b6c7e4098a4fb7eb7c725e75f276cf4ad9338accc5ffdf9 -578d27d5029b1515433c4a908d185a1e45548f72beb9fdfd7f34bd2bb616917c, 92d9343584545f70dcb4ea0700395099c0da1b3f9b305538f7e7ce4cffe18ec9, 17c5a302a02646d2ff5bb851948604e2486b69f69d53dbb3f266635b7f6c96aa -1577c32a24632369fa3079c256d04ef09336e1da7a988ae1b792270395411d36, 58ceb0521d266cd7c50f5013a41e590f730d58249d91e7794de77e32a4e2ae1c, 74c1eef55d15f6ce5ac34e014cd33a704df39e8dab152b3718181ef2783c4f03 -9452fa8e98c51ee4af4333f262a6e27693d406e8dcfd856cc25a0170fa5358fb, 90c55c78375e7de9305fa2fe1f5a3a004014a457fde1b7d4ebb8cc437b7e6495, 0200dcd0b492d924adb85483c4bb9dfcdd5d49c6391fd12420a9b5099364895a -c49f69a8f9054b39bc61af83c8f583ffff7f23109af45a5055352b57203748b1, f8751ee4af6f68c40cf11a0f4e1b021e026e1e048b2d513785d42e9f888cf051, 5a72b29c3f4bb257f2fadd35c935d539d488d160b3d2fcf1427d43b25f3da4ba -73c9e636b40f48d40885de931c4303a3226b899d5babff66ab5c2c5183713722, 2d25fcb8c74c1bac673dad09af824107829373d87d91016de16426a069ef408d, 14af5979d0b32ad9f803a719f1f3e952fe588974ce983656965d30ae2080a7e6 -7dc9ff16b0e13c99dd0aa62cfa7c2f0b29bf25a12771f1b3236191dc7dcc98e1, 1a0f6a2fb31bf98f1669f26cdb7189ac2102d3e6fc018de5e8648a0fb69ecd31, 1dd04b68e88cecbe7a1bdc83666fb907d82097ce193fd6aac0010bae07506c56 -1b39981d2f96af24fcfae7fb947461a6d1595a998b9d78306e6a474b3fb34af6, 2a9b092fe4d4c50c495db13faf8eabfcb7de88f403724c9e47aa64e69c3fb7fa, 5b6f21ef9f740d08762549058b19694f7f9d8f370d06d33b74d8f6e98dc8cdb6 -0cd75d16e1547150f4b9c424e42e8bd14fdc2bdf50b139b10ad40b9d9b73f825, 4745dca6f6ffbc482069ab10dee7785c53ab0c187478bdb3f574e75a3b4a4be6, 219009aff5cf35fc9e78ebda0b8b850633af1953f7b5785ea976628fcda7f148 -fe50eb4d4d4886782bac81100c0676ba2e72ecff8d802ef1d9c71691acfd5b5c, 52e670f2260d4a95317a71013a951fda207e2978d2cdf967c2b2ee516bd12866, 256f737c569edd5dd87db1427ed7b831e13d495120aa3ba162e3960f94673a78 -4c9220e2cc51fd5e0cbd91f3b846b1aad7eed16e1ec88502914136cee6e0206d, 78d01eea0c2938210e053f2e6c82f92b2e93818392f224cab0a551eae471a6c8, cb6eb0ecc6510b6c1d24d612bcd94cd867a0333e6e57f25d066bcd7c30e32045 -ea491875a404200a8ac0949aeb426834f8682fc80ca60bf3399b2ab1470be0c1, 18b36cd4614d40cd4f755af697630d6ce601f85268fd4ade4d27dff524871e13, a895001457db58924b3f574392b75382878d3d2be6025bf67da43472e83e5b77 -fdae785d86cb3cc41b12aa8031b340072cdddb562601f4c7e3963318e0908fea, fa170dfa7b46b69638121af04c3992ca3336413143970ec2178596cd4ec23c69, 85850af8fa4bfc011db39a7a1f5c4ddb1a3ba0c61bdc74976fb09a7cfdf5ca65 -4a7cd0e026c41ac54ba688ad4ae30194338756dcc92bb8d7412d7a9a7366d1fd, 09d771de71399fb74dc7556b4b07fb29a36d814caa5ba0e77941cc776e7304a6, 9000a3702fac0b8ff51a7a9ea2f316f50960d0abcaf5515a3d683afcb66b432c -27ca5bbb78925da94f6eed74c398dd718879593b36d462ed6f2e09a2fbfbb1a7, 04f49a4a0d0fa282f12d340bbf406794fe48b0c373b042cb0703fefef64bae72, 54209faaf8c53611bd51e0d9c36a4c7074ea3a001b87f5b3946b9be5a37e5621 -92c43dc32d14d29316ba4f15969be4745be0b2358ad2f69484916f6da3beaf90, 868cfb33be2d95a483dec4a4e9f62ab26ac8d75790d0b49f407bc7d8f0290ca3, 5a3639390ab550b514cc17746268ce609bca006ef5374355784c0330989ca6a8 -21d633eaeb698e090f74a849720aadefb10b304c92140b893479ba3d50d99cb4, 99c0a3f7ed4126f29a60ddbf7f25454b87770823b6dd68a34ef1e0c7da5805a1, 7ccecd87b3a40161401997eb8a660d962a8cfcebd2881df6367cd6641b60d9a7 -3a4a2d36ee8848bd9f34887f194a2da656e0fc670db8a5157cd11a2192645a35, f149f567cd8713214113618f6afb0bd060c706dae52d87a071302fc7b3c5cfce, 097b2881b7bc560f9d10c2b4e67b69fc300c15afc8f60f888df9188982bd24bb -d48d45e5867fd6f259f0b09a1b4837d15e6f7562af93921816c97a1563475892, bf1fa8742cd733831b2e514dd5406fd9706a8e43ac7e79d338a17f0cb2c2c226, 28b11b91ba9630cf55167cd52a5078d63cfab6aa4ec887d2f78925b02eb4567c -007c33b64fec946b35b8454f573d1fe640701d216cc1251f0173873114e178df, 4ddf0497df1bdc8dd85f152fae240ac5d508e6315005a5fb6e45bf7f2ee1fe12, c3ae3b8e042ebafd53bf433e819e50fbdb3c4c5f3731e238a170e07ecba02056 -92bc97f06f43fe23ca449b310d42c8eac8b6ca3a4d4ec08e22fd9bb9c336242c, 29ed8d29138f576ab0daa0ce53c12b7435302d2f6cd34c868c8fa5263d81f942, 50ceaa961753363dc71cb7c37485c2823cf20470ea1dbacb26f328120ec64f2a -278b86a910953c61d6c1c280e505933c1e62ed320e388b6357fd6d8491d0f2ab, 73eaa50733532133831260d36d328890d025e0ddf4e80753fd1f0fd75e26105d, 954680249dd10cb69c83dcfce640db95c6374bb8b2acd5f49436b4edc57b486a -122d53bc4133f91eaa8c0cf970fdfeb35f14e68a0b1c1ad00b583194ea09b5b2, 32fa2faa17b12332bd06f755c1b089246e327e987925e4e1bc1a185e23c53dcc, 4653824976f1c38fe20bb9326afe40d296c75c032499edb1d5be9cee08da446d -8040b21ea2e78254c5527527d813ace0b051791c51c927f7706b5f5f0122c513, c668f503541995285f3fdfe7d908bb8e8f59a45498948ecf611d7b8a030f059c, 8acdec94adc97cbd4cc07bb99a7ea3800c42ca47d95ab1c63f064229689e0eff -c3c0b20a6ca8ef71325cdb72a092e010de87a4a4c5d306c29a7538521b998283, 41f58a061b698abb2cb65f1728a1ebb20acaa678174c2069eca35018c7eb296a, 7d3536ab695ad1d95ac21951eb68a6629ac58512722116383a1edf5eec004831 -bf5a77cc2fa598d0b7ef199010a48fbb924dd68fa82c9b05e1cc8b9e84d11e1a, b1b20ee0607329dc28d1cd54b2a3aa86526048d53c5accb45049a6fd5544824d, 82a5f79d0c1d390c1fbdfcfbc9d6305ed7b6eaa6f4ced45e79303ce1bcfac408 -333cb2db1d3b33d2c627b5876a672974f8a551cd633803460ff990cd470e466f, ff6349784fd87b04a923b5e3b1e0517324069634ba01725bf755810f5f251c84, 5608860ce02957d48a1e7ec28cfaec7fff64227555e69de18328544bdccaca39 -92bca0d1eb4d20305a23745a6390ce02bd6e117b19ef4fe259f25c5a8bf569f2, f3418378b547a1c15dc52e1fff2809b022d3c47e971c9747b64c24c4e9595628, 49055a7f8865111a4e18b471005c54ce2c950b11670c359284d5c8c7b3908311 -e38ad32f5c40ee44a70ca75c447f7a48ffbb5f22cfd7bb89adb68b1afe71c38d, 837c107f71d7d2c0eeda12bf8767b3c4fef2ab8e5c6d745fbe18592a458f5851, 59a37e2567aa80e63a57e19974eb5f7e84801fd0d18377cd6d96b3c065330b95 -a78e313c740cf1fb68d2c9519062b45a0afb65241f77015557c5508a1c46df4a, b0b1395a3cec97db670762a1b1c0cf5b722732ed5be0854c6bad09f7eb33ce87, 1205f1f0108729fef7d4fdc0860c50542a2e07c05e1d3e84e83da9010c49b524 -d66ae7e05ada20b03d0a47f8ef7d39d0f99506ba42ebafb748dd3cb47fbd1153, f1d92f213d03d044062d133840cd380575a4b1f0e83f879678933562169c0145, c6a0c075145614273cd7064b7c149741190c594e08b373bf7faa13e604d5de81 -3a96fd3981a74c05b1b3b8976e7673ff3a2e53af49d7c486429b75e179b2da85, a93d71934b9bb6b752e45e7a15c2eab928557754e9794cc039a16145f792cdb4, 0583b600bb9343634a63e9e9f01cb5f88af197cd9169604c6439e5a7698893e9 -32396fc1c4dcf0cd3c055eff536fd719102a31830642d09dedd11115f4feff4e, f9d46680e8e7475741069561cf99cc0370c4e6b6bc98a26a974b4e0c512b1ab5, 3034e2bfd664069ba292ff75feb3f4f6924222ad642d2e6172d153813e682996 -d802f5dc04fa78f4a0ad928befaba5a0dcf2b6133fca96ebb32332c760a8e187, def85ad79ba4c6a58888d83bf403e8b935e5246f29845a3f24a2869a30269263, 6d7b245912aef1c1ee8b8a07d1185fee12a3f9f5fbbf7b89a4918e7137221dd8 -78e36910d28762d7292b08506d0bae4e6b19f1786787f849ad08d0220a7193bc, 8ecdcf60d70c830ceb6f381a7fb3fe211d9a125b2f884bbc3b6d30ca8aaa081f, 2bd203e7dfb3440eb21bc63cc86bb0f17994b12e401bf619d56d1b713ee10d8c -4aa7d9ec3f34a01c684b640a7fd66cfd91baeb1f69b641fc768f10afdf0baa52, dda5a3931f24edf650ebdfdf9b466840fff4cb647589fbde6a4f7386f76ad723, ef31fd5eb808a2ed9fd32a1ab2648e5df6c6ce55c51809ada921e98c9af04615 -538383c109030c8f498be19671caf88d7879578adba546d6adb5a864fac04e9f, 65a594efe8edfa6bce02aae1cd13acbc54cdf1b7e790ae64806a043bec3b36dd, d535557d78e9622496b50324f034527e342ff1a4ae93675493c924c8c319f36a -d5aa73009b803602f8cb5ffd8b25517ef21f682f5e2e2e76063085f9b8817112, 8705504d84ef8ca170d7a2a05b16ebd73b28349561c35cf94263dee1b8c52ce9, 77c962d39bb66de539b9de60b68babb318fdaaf3fde8157faaa3f3a5bf46950b -730a8e8795a9a6218fd08d4d9c105f5720f972af6979a7551a24a0044393587d, 7c1207600de1aa9128998af53b4ede9766e97640adaa7cf44b2607732f41683b, 2af80977ad369b2e87bfc801f309e729df35b452574e306de1ec34d10821d986 -8fa34c107bb1c90c65e6652c37ecf192b5cb690b868d69c9d05c1bf94bfb7e59, f75ed5e1c7637799f032179d25bb8df9359b91c7d1917e7b3f9c4e98557f3a83, d1cccb623261f4ca568cfa1b63de67055cb13dae19a75e435f9f6efda5e2bde4 -f8644d4e4f90d6512511d3cdbf1cc605593243065f773d2ca1e3e179fd96f820, 5ad5306220b687270cdab6d1c02afb03cb7bf304fbdb3762882b6e726d8d04a9, b699d235a505d02e65b1f422e8bedc385ce77d784c46ead72e5a23ec34abb432 -e755596b8aa5c015132fb79ffb5b6b117337e67fe6cba9db8cf88621055ac4ff, f53e99fee14313e1148f9bab50b698f1608429b5ecdd9a4570ccdb651400794b, c098375d688d819b4eaf5b4aef89f9aa5bf388b07b88c55c589935c58c675806 -b0eb184d3cf442b9d8baf1123f047fd969c4236634a4a852107efe48711e00be, 1197f080bc924bfdd4dd1f0c8e33b6dcebd0d9705ecaf11ec216f5529ad60222, ce67b1b10ae7b3929f3f324f6515d1b5cd1227990ec9761a5e58ae987a8698fa -88d4455e87a15e0e7a3487d6a991adcf9c8cff58087df0d6a3b6c9423694ccf3, 20be381711e7e5ca58bbc0e5d31373bf10a314b84378a8aee3f4ac30752f5725, e019189d45336008459ff95b46133ac968ce95f6e7a2d40100d81d1450195190 -55efccc9c24d64d211d9d21f76a7e93883f2a3abaf5536d03c5fd4b2ea156143, 1272529d14ffabad6fadb18e590d68653e2d8c565f805a7a546cc66890e117f3, a17016ef22a22713a9b45ad6837f0ee644e92dd27967b84a938e5ae90a54ab15 -6161f50bf2d0d41c67273f36cb5cdfff490530133ae5ea5f36d33c591aca11e9, d374a988835424b079ad24e3272e42075f94833d8570c8f9be8890f40bee8122, 4d741aed5b4d1f952896643efce8b901f38a428dc3592bfdcf151415c6d80958 -07eeef65fbfdcfe8539e8c341026e02b51f8fcf0d411770a7540b77e84a3f759, fe3545333c3b556d086ee11695b6b45f27ef5adaefdcf5499f4e3c5df7a9add0, ccf3b756db33c91ab020a3e9892bd77378a073e7fcbfea64db00ab494bb43363 -d6466f348e57bacdd9a970193cecd327984da221538c97f1b5d54a828f54f99c, acb2dd560d14f60c34cf8fc48c4ef0a6d58403656b065652d91f103f74bf7b03, e7e16d7cf9a584606ab2dddf10dff774d03f7e6544082800c6f1442ecd6cca4b -ac45d3bb363ceedb9c46c737adad992d04fb00db8983f2bfc5fa44c0b40e1614, dd34cc29cd80b00a0f1ccc040fd38d8a2e8ea42889cd799c6996b1d8b2c14ae3, f3ed23574ab149e73495c384449591f55199db11f387b4cd994ade57a0c4767d -8872e3fda4361ff9776e43f6011622e73b5a5e8eb856b175835cc89933208df9, f80f689e1d0d2d30970fc80fcd6a8cef8e4e85c790b4123cd678ab5ab28d1c1f, f350ae1254de39eff6b602e23ab58578dab2fa77c4f80a1b938bf71d0fdf2a90 -c40796a69c57ada52d591e4d44b0bc209d6c986ff6fb91b4a2921165b699c8c2, dd8a41fc5004720366e5a61a7007474d994d0a4cba2157de74bb07a31771f24e, b149fab2d7d0725bf6f0b9a0618c53670f409c50887e70818b29a110ef278dba -8a4a465a524e6ce25e658a30b9f1bac240cc8cd7579713a634fc0c14df279c2e, d19e120b6a76bf0a568ca19fa159aaa80564d5fe84067208009b0dacbc17bed9, 2be368f62d5d2bbe6604102c484cfdf7dcb4c9624cdea54699e3a6ab13dd3169 -d31d0ee7c3a4f33ac2475f7cb68d84d454af126547dc9b4abaa8d3242bc64fc6, 82ab5074e654a0a0ace5c61d6a5ecdbdfb1d70b11877359a463a47ec0bb777df, 3a66436978424e99acc966b18dc80db3300422aac44c95bbdc1f88a9eb7acb5b -62477cef7453cc6cf6ea2f3d2e360b6d0df0257e610a10db3cd35b5d26ffa130, 11ca5eee5e04927ca270a10372830915a33ca49de6dadadbc310cb3619a52324, f1254f639c851d5bb77fd2fdcdc5f0a413a4459c3b64cbd9b6757d53e6657cb2 -80d5f1e25740872b1fec0b895b3658fdbed0f2b08ce0055088cdceec8cb14feb, 4a6caa27dc9fda079f471e24cf5ad836b2b4135f6bd643e39ca673c6ec99666b, 649f0b881bef646ca5d68c50fc6d9269acd09728219f6d5ca9536811e79ce224 -907e17cbde23c6538cae55d2013130264d97d704053287d4fe555fe31d11fd39, 8d0b98e18d6eb1b006901acf6e4699548bd96d28482bb3a805aab6c03bd450bc, 464205f7bb7c8012e5623786e2f467d13f0d856b658ceb0930265cb82db75509 -89e5a7ba2af7079869cb6911ceb879e83b56eaa1e0a92893fba1f61e2948df52, 61a616104af65f40eeea515ab40abbb9caa93958346d53f3bc62ccb805a5881a, a23b97685af5267fe794885925fc28b04960c62762c1ec2323b238039abd0181 -cef17c4afefe6d8ee7c4494c9e8e918ea9e30794862e2e3fbe7c143cc52ead8c, 44c68caaa2f151d49c18eea8759061e60109e1cc5afffcda98706123eaaeb29e, 4c90b6d15f752ea448b5aeac8e235e6a7a55bf6263b558e12818fdf6ea7d0fc7 -4bf45a8769d6e5fecb33cb0c4da477818c49561c039b6041924f31ed146445a4, 9bb5b229c2ddf8152f1bd6cf3734cf335b7d86eff6826ff80d51d923863d3294, cbd6bdaccb8f49254c2ca2d62936ba9d34ac18ca283c42eb38af8ecce8b35c27 -3bfb4e5a4c1aea39126a8a3cdbff21c7ddd07e34cce5adaba72f1c40828ae491, 22e97192f4a0ce7c50841654a731a3fe3a3c0161efa10973acf6aa0647ce6dd9, 8f6a271ebd0b63a489f544a352566ac7fe10c858f89dd5ba382b09383e09819b -47a87674d860be2caa8eb706a10e4dd84e5ff324c52a581ae5e4bdd30e327b4a, 01f4ce4466684df92adeb49dd3ac0a7c30ab1d69a34f513f2918f590319175be, 832b354ee78a7efc29160a988c5673b9ee7795400e0c6bcdee5494e5cfc60d59 -2f4d855c542996674997b352fd1efc9bf332e5c09fd8e65713c396a17ad7ca15, 305c57a28728e221cef70499e49a7fc9ae742225d5ca25df82e942527089f559, 5c359a4e45496831c6f9a3496ca93448e0f18e13dbc0def3e3cfa8fa5be61b39 -5d7485b280513ad3c0d57af2bbff321527f46d92f213b431312293aef0aa13f9, da042de761379b5db4e862db421a6e90390687f474fc1db649b09e2a0f3a4632, 235d9d964b4ad9c7d328791c1766969ebec651615fdf164571f4c0b0d87bf410 -b0e068bd269772f4abbcfd8e00b886750b4ef4834b5aaeb0e76ab2d3e2a33e76, 29380f02b7ceb617509268141a3b5bf809331c89ee8a4aedd3e8a97345ba6245, 02c00e5c14b7e93b00f35570b790374b86c8d5b658271f06a86578245914484e -6d5a58b1c052abbe27ade1f8e209d49eb97e65bbfed52791dcde9b70cde6eece, e6b6d92ac86bb32ebc0c91ad831439b596f3aca180b80419718d0aa015f1affb, 62e0623cf313f38c6c741c6415f069f12b749d5c6007f305229f5f94f6d73915 -3a5df40ce2f9615d6d29cb5e2497f59e97240e7028572afdd7ca8dab75aaf39d, 0fd438bc89d4c4abfcaba6a9b1c8d783534c576cd6ba976c5893d24b1b97154a, 56f085abac0600d339695b54876eebcc4c36dd87f2ee2bf95775984675bfaf9c -04b6f0da7548fbce49e7a4c552dde847a4ac2918a57d26ea6536f26003aa8622, 2ea8dc2f03894f0262643685102bf038d229e208dcd5aaa85a5d4784a54b471e, 31226364fd0d6a6241f7884f0a7719f7b4eab4a66e49680c6cf35e91f9741f8b -b34abbf74e38e476439ea10dfc9ca93f1f5346647c6da65b8ffcdae721d5e918, 7ad61a12f7d0aef01b1df392b08b289e59972a69ba7306ccef66f0a59113e4aa, d5fb5a2c92646cbd4af891187d07ee707ecbbd65065808b0acef20962e06c929 -98cb427b06acc9c3b9142c2d998c2afe9b25510c1776dcdef3ef0fcea2a97b10, 5bd1e69723c32aa37c583ebbaf494d28562c4289d845f828d07e1e351f2b8e5e, 8890bba8acc1f70ac02646c8722e2d44fc4a6a8501e056d26d96fa40f7993dbd -58fd00ddd47086831cd219b09bef2066d8a6d278f8a7ce3d7e4117aaa822f9a2, da81398ed4b28e949d0bcd88f497649dec3b51913198f74891d0a902d7f6e0c6, 3819dca4d04fc7adca4bb64858dea119b4e549258fc020a52c4250413e4e279c -707c65f7fda2e109fb8567000613827dcc46352d613dc88560b33e3bb5dafb01, 8a920cdd2a9ca2829b7534e648822f05991c5e3a10b57dcd95baeef9a38204de, 0e749b3a62d648aa5fed220255cdd604d4b694c1b27e96b82980b782ca0a1c03 -c454f62ae9194b44b1b896f63581a01fc95b21405cf5551fbfa13471da2a319c, 477ccdd6e24e6ef4f24c84a153f0e46b1342f8315d23cbe03751ba0c0455fc62, 76137057c4c3b9323bca401ea8ae66f971e94ffac07c7d071ff225543cda8b55 -5bc1cbd21a54c567827295a0b845e2aa4ca13e2ea2c0a3a74e087b8214fa092b, b67130e2afc8adbb84d1f555674a2b515a3a4a5d9c146c96c5c9b9544bc75f74, 8bfed72ebafbee1a1ac5caff60197df22acaff232f85ddeef2e1eeb3d4b9e40a -2cce12835daeb93e52f2b78eb79307ed4867fa923838b92a8bbc211dca658fbd, a354399db860de6d94f8d892e28f11c065695891fc48246c6daa4205f30526a1, 1ba1654894df4a396dbc068dd9d0d0fa6c085e7281689f2a89ecd95fa226be0e -ac5acd51e6da3d2d1ea3416d13496b75087e2bce7f6cf3ad5c116597f4d8e819, 792af8dd9bca67f62f6039dbee3fb2f9e5166dbec0412b71e7f6645fb692d376, e6ca3f88d3439cd0ca383f2c0501bf9de89073b8baf3c867af9ec466c38c009d -49a262b96078a552437ccf9bcc31215fbd7a117d8bca151b5fefb1b2ae689896, a9a9c9d46abf3baf8ffa648d48499bd9f2495ce4a0359a7b90b66751d484ea7e, cce70f2378ee4c2a9be79d249ee225322348f2a88273c8e7db1be36409c8f8e5 -7ee408f96531baebd5217391e57805c7affd1dd2c84dc8442634794a1ea828ce, d2289846186035e3dc85d2be57e347818f4426389df791730f9310a95d4de285, 03b54ba429fd7c92a27ba43356bfc80c0bf22379e012f6666d05c2013a73202f -0cf6374dc48272d9bfed40681943910ed88a03a10546e9c5630f7132c8bb9bf1, 76b409b4acd48cedc756170940cfafcc061a12719a24f7955400d9929a87423a, 692730cb854e66e9201bf87b1df3ce72e27797d87fe98ca1e8277f4dad68b3dc -1bdc39b18cf0089c21a34c0b66018a51f6e2e071d3c7e87ccea8905888b619fe, 5747ef0d20d68623231d698572b8deb731efed176fe62ea1f6ceb7dd1c359151, a0cce673d23e6784bf934a1cec46cc301ff07a2e7f8f173104da5d433988d2f5 -c043e9f28f8201bd0045860fe4a8e4ba8668399d65f7725ce3372351a48edd46, de2db83dfa06eb82f5c5a0e5567de653d2b0885ff810d1507a540d0edacb7720, 80faf6c4e122a18e762714937fcbfd11bfee46b0abaaeb3ff72fc9b35d457de8 -06b4ae91a7a59ca73c5413cce0545a0030a09a999f23bc388d983a11d24a3e53, 912333cfb964152e5b93b61c39ad9c3f64aaf555228859e4c5f370acd6af05bb, 21d7bf363ce657ba61e4cbc83fa20fa8796551db907bd27728600f0ef9bb59ec -e9031ff6688694095fe865b87d6dcec648e6a0f435d701c36f362e6885aeef7d, 04d58f65f032a6f3580ec530a34a0d2ec388b38d6c8062ac1316bddc787facbb, 2049ac59c80badc9441f2736317572068b2dfd60b0c5f1bebe1da9c03c2cb875 -4210aa958d91728c7d35c68cdb5aae91d6a5ca8831e83167bba7e1b975d781a1, 186d4f42adb0242c89f4d0464d548e04db0d5477b469afa5b5d22aebd5a16811, df758350f63754f8264177575faf171f510bc5fa5ffed101c918a3396ea34581 -7330c55f2f023eaf768f0c904000816aaeb6af1a4f49ac801d0a6ba00435399c, 5d0aea68c5f1fd72c341446d8e2b03cc29077988daa269cef3d75c7493b3ab19, 6ddea0680bcc3b950c720551aaccc20f6d2e5a96b86efcc34e7daf61fbd5a57f -cbc93f8c8229e3e03893fa4913b7a17fa6c68efdaad22ba1c1d7de191adc6683, a4d009a556301beb04acc92fc2b7d06969881961b468828bbbe99f55aa3d7fba, c83163684bf5dc392a733fce91c133ce64931f0e5fe16fdc0eb44d21c8e1f715 -77d8402d5cca78c4706a3976d89ffe1c7e4ea92201411da2bf979d8ee80717bd, 79a3d1ac6a704283da997a36d246245bbcc243377063c7758edcbb15749adeab, 0c099d8af878aa34c75a3381a651300be34eccf42160bdf056aa7f421e3f607b -52ec890636c38659937e02a3326a212f1576d6ffaafbe1025af3d614dd3d5fa2, 3f096e169eb8abd19400ee245b6f276e4f9b078ef7f5f876abcb209a4c4fea31, ee6b42259cc730afb4e106a4887efbc21fa740031b3ce42814c9a33b0af23fcc -b26d78ff51df02cae29d00400934090070c815a95c6ad310cabdf7b968888d82, 2691e19c0e15ebf91518d5185985ac347ea279e9430649bb152c0c524e4ffba2, 9465c3ce6a02a9291a045957268795782cbab595d831c30e859178f9d95bfa7c -704777c9eaafdea9aaf428b765b67a48aac4743b417d27fe3284e6e608e06107, 37172fa0b88328b59c6ad5055c44201937134a3fbdc12ff5e4e7b53e822f5f75, 51a15bc1e7c32096e938a179715ef8a9f99047a0ce48a58d17c23a50c01e7503 -5be72e6b29093346b282bb3eaf4e430f59772d8b207daf27a10a75579af61a64, 76d5615d90af56bb1fc97bec545c90b5a5956ac1c96d20127945ed8a5856682f, 32b7673b06226516be198a533c2f60f482830f0d360971900e42df5393538223 -da98b4deabcad59e77c65d5ae4a4893382806404f2ed6bedb3afbb846af222ee, 31030f7d385af05aa01a852af697ce7a7499321d2e701bc5678139c94c88ab57, 36a1cd1a39c538d26f175a4e0c27411b2e7d7ad5ec0193a98c2b2b769459812a -476480323bb112e434baa19ba600581c1fcb38a987c6bea8496fdbb68ebb92ad, e31b244f01df0a8ab24cf6e1f556f571fc406f6c4701e6c3ccd3102952b493b4, 2f27b0023302f2df659324ed90a090628484b2ff3808f03d93b00b9713cf8f07 -66607e2896e4273869a1f3b3d6439b360645a941e773ee8d648857effd2c2aa4, 7d550424183cd2589819b56345a0800fbbfb3f979cfe2589bac64776e9c8803d, e1136e713dbaf3ef97c62eca26a7d2041e339b0410c012cefb3be3d8f702eaea -8b93377b5fc408c3648ba5c4eb91d77eff778eab50f4ec98edbb92c58c09b6df, 80595fae3fea5524a7da31dffe364322beb752a284dd375fd1818f94ad43f8c8, 49c25453c7934b5f1c95126aca646cb7e2e985457fe09374efbd9185d108609a -3ee566dcaa019f6afffc5ac76daebf2da9743b43e8b32778d645df140420847f, 15e26f126585351acdb93b63adc462bcf9d6cd82470209f0017036934a3f3718, 41cd2e8434a798b83909089fbda6b355727ade1f2f692afff393e0ceb9665889 -d870d0da4d44a718e64b6207a54fe44a92791fe7f546672c74b5c7ffc03c5baf, 85792ea127eda8db6e41abd601b911fd1c5e1725278399f8aae6c7a92a15238f, 154049ad9532c71a4d8ffc444dc10f69ddb696fdf391b1e2d004e56d60d3ee2f -73e0107e351022dd208620fbf9b4639c9c39283644815c73c74eadf491cbe93b, d971a4b0308c8d99d3c72d7ab59d6f240bc9a49ed413a7355655162553fa5dd8, d051b89a655ba42c0c88c1c1d41dd89e46bec470fbbf99227fff84a86fdeb308 -8ef060cda043c8c9b1d50082e0b5865614982631934ada510e5f889f2fe6f6e2, c3de387f59404b6658a0a70f62fb86e36ecc5b4914de4f6ec105fb400854d297, 3e03e7e10f77614fc5834954ce697ae9077dc2db744ff5520f0586d65965172d -19d9858aae7ec5724a618b67d27a6dd448677a2f5a1b3184900c95c95cb2c104, 477ea4243782c65524a506eb612f0aab32a2ae41ea2f204f75634f7f77ab9d86, eaf8d51417e9748918fb97c47f5a6160c6142778e15abc8cb372bf710336d801 -12ad67df1ba88e7ea6217efedc505b0e42d1b1fd58603889b180b07d283c5759, adff9cc4c6e54fccb3a9247e385168ee58a4bcf131db3d30da6720e2a6af34dd, b871b0f8bdb13bc8b81ab055f68819cc0c5ccb95989a50be4aac1232526257cb -f668f8b88faafc806ef8160ce4992f428ac5287f381e20bd3909752541aae357, c8f247e0b003c7e9d6171d66ae581037065dbe0975bd0aa6ebd1396a60857d87, 8f1ab371070101825137edf4b9162ef247b92d651947c4058a9ad177d83b7edc -609c71325afbcf644c23283c9ad94c843eb4656750910caa10b832a5493de8b4, 1637ce001f23f0416147834c88fb6195281b7bfd38d5d3c7c47928f1a0bd801f, f0fa06710efa88b3ae9a03d8777095f98dddcb05ebff2374eacb3a0a3d065a6e -b45a8717b47e34cc1c159a76c30bea499255b21a59d11ff4200c726b9f9bf998, f3aa9fb0c8c589934a3d0050e887ee7e6b62fccdfffd4d0ec0f706ab1317bb57, 79af8a2eb95b64353de25a7935ca511feaee9b87f231cc79345b52462a3bf8a6 -47bcddd12ac3c4fa381148170c8f8c92b7dfa45d0edb4e47446c7bbca91391ec, 0eb28a4694ea9b3ba12365e110145ed068b76e9d15681725270687de4358bba3, 1d42b9dc4af6823c270b3002237d4e48f231470dc793233dadbfda0c35f1409d -57353a3327445a3e558edb0eda678e88341cc8a19ddd03b4a222fe24775619eb, 070ee911a686f991e1fc61b29cf4cf6fad2cdc74cc1d2f3ae4f4b291e658aaf9, 581311c62d50b89797f928ac7c7a38feba36d9e66b72161025b0fadcc4f72845 -b14e671295b77c3aa4a81af0aa5ae851ad9ef01ab298ff89430dd68bd02f2942, 61194db888be5ea26edd2edad27573430169e2e2cf91d34273fc0d4c7d2db17a, 4abf33712e5d59fa4c83099ea5374c72a1f2aed408cbef8f186b5a21f169b134 -7326cd36d75ca090e19e0c00324c4ca5dd9840ddba01e7c75ba4b7f297031966, 9d197c2c6bc16373eac42b2de6ac7514b55d193f7807641ae2b602454b214d2e, ec97ba5790dd77076e3c54de03e3fac812bfbdbff16ee8bbd6212b7aba1d7d2b -abf5732c9f9a4899b6a665432efd5483311d52542d027d0b9b369cb161493943, 3c501dcc66265f49edc14302523c7248e00e021e0daa7479138945eb779a0273, 456e115dd0ad656b9934ab7997c87b928a8924d20570e5d9bd7072e3b1149faf -cfb0e6561ddecb93ff86ef2415e46787c05a04316ffe60cede0675f89d4dc0ee, 5b91183611fb4f4fa6d2472611264d54ae2e945af36d201aa5ac4895d5d4e36d, 35fbe945a8aec6d6baa1a031c3505689cb9f28f637d8d619adc3ff8fe378be54 -56a3946c34e82528e36af5a184f8122553e06951282f23df1e5461fa4a4546c0, 76199001a2402e2d636210b1634dbabef26e1f290f3840dc7a5d7fa0c13eb3bc, 26c8b006f3b92aeac7888ee46fa625ffb44b679789c8d1331c1ac5c00ffc9457 -a64ecc0b90d3f56e291d8aa3864e16860bd45e293065793b521f07f69b48d808, 8b8437af86d8d30b1b24effc1fbac7f8c16af7bd41f9c2a8c8ed50bdcf5ea58c, e65c2381e273a13d9006fbe3ca28ac5d71023e8d07577f94d1402a6d09872b1a -04600e2d1cf8bbef2d3cc045aade4edfde895ad6021103eee732a18d712ef664, 9361c368eac01766114141fa53ca57484dce0126c1005c053b135a5c38fdab9c, 15dd541c7b220e303a18d711de96e779886cf0f7fd05f8a1a26207933874a6c7 -1f8246b2485ea617fc067719ac0b2e151af9dafbc423ef614bc40a8b29443561, 68c72c7db152d38232b140aff8ecae60ff5887100000737130f005e4c4894bad, df8f8683c8b4c33461d5809d3eb8b9aeec84009270a5114a4f714e08b9f1520e -e1e5c2e423567dd129dc7a6e399786729ff48bd7d8f9c45a2bbe8864ceca5213, a5f1d4b510eacdb614cf60e8f903d3724937ddee548ecde29a9e5fff37b28df5, 54b6b771059fbd4007d723d5264d269eaabb6929581df5b9c766c45e60107ba1 -74ec2db09a4c36092d6e89182a7d3cc75fbc07a0a40b5fd561843d7cd8ac59ce, c04e6b44d9a1a7f84fb2fe8ba3140dba32cc876936646983c176deff16ec4bd5, ecf5098edebecf7b22b7032da3956527018eb8caa94980498808283330752cfc -0d73c6863966e53602a5cc0b4ed716f9a4d924d8369b90bb631632f5503bf500, 797e30b8bdac0ff20296b16adb5f079b7af43b79fb692a2b02401a71b8114246, 75c0546e62ab9b1e31449548c279c5ae56c36413a32c9d9809455ef177fcd728 -1ded08743fd1cab7a8c940e18b1c4c46486dde4d7eef808802520ca5ac590b09, 6245ab37910c644afe752e9ae6909b49383e13c62377a5a7e3a7a0bf5c0b03cc, e37234a0cb35e29cb89eb47332325dbf2ad68f6354ce19054d26854dfd64ae59 -d6089e2153c5625159111d63b5761ea48de24c8f3ed09af6a8e9f1456a03c965, 2acff1fc203a44a40fba7cafddb342a549c5448f4fb5d59abd99aa149da26386, 5d468d6b034c85988a79adbb56248764cdfe4dd23d812d152a7de6f50abcfd9a -2aad0822991dc0a584b390e5b62e972a007f344e7d6c3fc7da609c581f9a3d30, e5f3d761a257597253699ba4525ebb8b02d40a4cb55cd97246cf67ee69bc7c32, 1637ed8a6dc28c74cbf9698c961581b8daed104f61874d446074dab332c71b10 -121fdd8ffe44451a83fa8a39d508238fad1d877b68dbfe5c862c791ec2699c6b, 010fba950a44c44d41bea3120f61bdededcdaa70d124be40514935cda0ad5ffe, bff90137591ccdbfdfb05752d3d2d1bcf13f98cd8f021346c148ffb10e71c3ee -d879e60301bcedb25a760cbca12dbcd0680cc91e6783087d752dd2ff0f623391, a90cc24e1c069e60a8960b4edca71253fe064082d767e4701113099e0c27df13, fb077e43c8157c846fb1ccfa3393b643be306f9719ba05eae49fd01400e4f93a -5d3818e8be093f45f28cbc5cbdda16245489985aa200fbc61e425c254933708f, e68b884fad760c0167662c98987c6b0b1b114a573e580a04608f84cbc00abcff, 8ee1196406ef4fbd77fc0b705c8a0ea80d970a7f4b5104a7cdf14901f436bf10 -e05a834b27b07e886ed5910f906768b4f9889edf3fa2c737d9e0e39a68c40059, 8ee8c935f573eb88f42c6bbf64b6fa4cfd608d3ef6084946f91161f6ba33f45b, 8c2ec737277742045884bb706b2485e59db07b0addf511e23c677bab60727015 -1ffc20942eadfec2c9372923be6ede74a1c24e2dbfe56c479e84f02b7455e408, 0fcc1eaba8e68e44b9250f43dd4c4d7f04f5b15dba4a8609fca1fc089cb47c30, 2e47c1fa85e51041e8208f52e22fa7f2b403ec28b314c0f77aba87cec0fa2f27 -7543795b1b381a6a024d914a65189c2050bf81d3fcc0fa854535af740bfd9c14, fb94d99c164fe43445670afbcfce2ae4a3c207f9129d596c591c5df9bb5fc8e5, e5101bd41cc86c9d025f127e80c4fd6a74c61cac7a4ce577dc9f4f47daaf9bc0 -8cefacf5b798f1a7d8afc551d6b4be8c7abd9b0cadc19d2ec1fc1f192ec047af, 55316291731a7851d451dea4287148f6da2dddffabba752822b0cceb413c4fa3, 2baccc9adcf09634caa795e25511ff0b45e51002dad7fb1806c8dac1b4defebb -69bbf83c1e01d5bb2f9b0fa16b3082b08e3ccb53142771f600f37b14ee5a1304, a55b50736838ecbcb4921e419de81d6afea99e91938d5c8c83b53cfb4885328f, 475b7e5cd98fc10c2d5f82359114310ed7c0fb89b496633aada7f21c9e545886 -1109e7bc9717fa4364dde4ed2ce56b1fcb163cb7a48a5a044235e75af0a3f0b4, 30a7655a304c11fc31e539d1344af235b5848d7e6d26c4f2d94be5e68de76dfd, 99969f9dafb8bf81a02e7e4557289709c923c934ae8c55aa691cef498915a5d9 -5f124d9c5c4774befde56d4b5bc8bc554d138263e05b042abfbf492e9b53e72b, 45e6a457fa8f1bb45f41c61ea0aa33c2ecbb00fcb2668aeae9d20d728d6309b8, 96329113d1a5c717eaa79859ed163ba9d5d263d5151aa8ddbb972adfdef9100d -d8bf73bdc89ead65d6e94f2f27484604bd8838caf4cae997a03e2c0164bc9bc1, f8767802ccc7c01f016c6b170fe043c20d2072e598e15dfb83d70ef1479c45b6, f175f5f066fdbf73500e0fd908ca3b29fb4f6b31e3c012e6fbb7fd18ee73446f -be5139390ef9d741f1a3fd2ac22cd046205a22caf9d6335d20a2f01ea6122c6a, 16a5af81c671dac13ea4114788e2322a33e20d2af8554df300b7d8fdb356044b, 6145844a3d2b49e60ffae0405bd5a6f2f5596e08be09f6f8c9046a547d146fc9 -a18d72accd6000cb704a3f61874794336bd4793cc1c3b8b921e4b07d42e26140, dc14b211159e7ceca46c9e1a0f606ef20181c5942c0ae35961f48fedf54e0f2a, 65151595c298bf6b1660c65f4d45d75628fb65b1ac6d5081ceb2780770fd18d2 -1fbfc8fc104ae695e38d8773117d3a92b2b1a0e522e6a2b2378203c76b03d3a2, de3710675def99170f95eeac502d48449dbbfb93a8cb8e72f3bc55968bfd842b, f3e2c043a441e4796543777b6f2476666fba7dad755ccbc91c8454c8dee8de60 -a1f20b6d4baf8194f43dcc4396efb67c9c76f10aea5bbdd5a3d5e4556ca2aab2, 814f35583f8d64cd5a523d41094d22515ef6b91e0bda3318a4f6734719725743, c1ecc37c12f05cd2649200bb3d9911d4ac7fe400d45c6e27148ca95a38973024 -942fec10b70a7bb7daf9613a71f4fa7742cae623ef735f29c706bbd871f8b955, 30cedcefcd5a84a004b358235ea549896a576cdd83e5dd2a1780a467802b25c8, d096b485667219d5331ebe93c7e16f8bb9f73ecb1e795e98310cded15c687f0e -1cf6c86007a75e6e51e8ff387a1d7a7a276650306706b25c6fc0eb546f2a430b, 969bf270082e0cecdd26d88efa1c5e592ea23376ee82c020d74e316c0015740b, ab530af80b803fb3ccf5f93177a6288de6420fea6cee44ce9859d745ebfd6c19 -3e62337fe7e2faf71d2806d6aff95e754bfcbeb49a137238f259bd13e067b007, b031f59721613a5b8299a1652738675d810563dd0b3ab33f5f3451180af69981, d40806f96b9cc3f5f4e2f6db626ac8c507022470eb7181f3ff4bcfa012a07bba -76ec758aee3c439ad7f1daca3ad52dcd60b16be5fd534079ee2266adaaa8dc37, a94e9611690756ddf490dedacc6a2565a000f52354d23d2761d3e2c119d8fe1e, 275acfa8e6a37686cf6b48da1d02c8633a68718900a6be45766cf181727a2312 -6f549772df13ec15f440791da9104e967d33aa59307474d551a5da4f99db9664, e5d8974420d7d1c253d5228bb0a2dfa6db654aa639cfec38fb7311bec46e34f9, ce0d37bd05d3ec1f7708d23df02b5add4926b9485cb7bf301996208e3e48157a -6f2b37b78c47f8770f8829c6809a9afddac3e0c172c13ca98a2e2638abc613c1, 1f7908203b08d3b6d69b9b6b0ae5bbef89f8fe2ab0a2e631210b18bbf5ec2ad3, cefc62331c73489013303a5360ffb0ec3a116d2bb69e12304f93e6b8228a6f7c -ac6a05390df8870bf22f0e2d68e979a7a4cd602e8d11a04f881b4d65b97d8c0a, 50bfb3bd5dff41bd65734447d48286b05fadb29dbb9ec727824b7394033564a1, 86af5daffd89f35d808caf3ae5166f34a43d0252e17dea1ce99963ecddfed2aa -e4cf5f736c1c18c2a2182108d9fd361213c7e4afb1021a212f3bc68cd66a4465, a5df7b7612f8154630c0d633de228527cc9eab4088a5915ac9c8f367cb74d5b9, 48931d482856aaa6a04415b89fcdeb41cf35eb3b7a972211c4970d14933f1367 -605603eaba49c11511bbdce8cad426d69c718ca4d0978e03c55df02c04fcaba5, 6eea20696cdbcab6147936bc1d229dddf40ce923608d853caf7356f0d8d6b840, 0879090b9067abba3fd8b9216b95a42bef903b460f2c452da2844f6eb18d0576 -c0f33ea34bb9735268eefbb3cc315706020a6589ffe9ea4025845cdcbca5574e, ddb874e5f90a5018e87973326cc741f7baf1bb56d913cbedc77a4771eac6d406, 64d8e522d949bcfb3c6ab0010f33216693d2c7b6e795734990978785377f5a76 -0a390136231ec5926ad849242bb3e93a5c69ae4afd29718b48b56ac14ef705b3, 06ffbd0b5fe1c467d7bb74cf2a6276e173b97163d7a3f1ed458589e7e82dd5e3, d0c621f607b7a6c3f8e7fd4fef6f6e25cd276761238f467a0b37232ef20423f4 -740e485a0dc2f4399d8a838c6a79f79a79525cbaa303c36e920cf1951b85afba, 43eba3e92f9ca37d7ab5793dfdb136896f82e732676ba9f36f6e8da1fadb4414, 262d0b685787fac92aacce2cd9fba79a49c9665c430552c1aac6619e8cf5b3f6 -76783fa6a19be2a7bcb65a9e13b8dc60c04e394467aba20f0eaca02dcf7f646a, 6f9b05cb7e52e78585c0d950b7debdca8d483926b5836ef0612d2806d3605ebc, 1cd0f4c9d41191d16220f73b1a68173d8e9eb8eb41a0f7f2813822721c4af406 -9989f0d3c1bd981e88eaf9a741cd007f5cc6a9af705c9c85e911d45c76a3fc99, 66b864d545d00bb40159d3dfbc6874cb49c99138d965eb3bf13e3cc78d07965b, 2c6172f08c08da11dc1a9efcb15f5379764ebcc93da733b2475497cbf300d451 -6389e877c74bf1ef4d0527341d080f47cf0f486115256b2e90d12e9ce5d1bec8, 38e98a016b7e495f68ad593f753318510fe567903e2efe88965fda62b681be51, 6951ce3d9f99f27ed07608edc9401ff27eb075289b99d57d6a1704df56ed88c2 -ea18d5e46eb70628e7906dbf4f169afe4bc5abe876d100c2ca40f09f2c2c2837, 2477467b39501ed65cab5296a138d8c0dc1e4b1d4eee4825b86bc652888e2fc8, f270be53ba5928da2f6379a12c826e4ba78a5c5d06d668839c682a750feebddc -6b340d1167a387e2239ef7c9d6fb92e56b9dd7e70dc59843584fd8cc5072bb48, ee395934b3147c5d0c95ab5e84bcc5f8176f53b0f566459817258144ac04e771, c370e1b52ae9908ced79f9b7f83dfecc8e431509f86bea368058f520f06e6f04 -9a72b26852e51cce8377a2c4058346a760ab824bd3f2055732e70a9adb597f52, d2c2c210eb71cec68ecb8eb5763b48c73426545bc11c244f3679e399fb06aa4d, 906f9fd06d7e5256926e087d55aaae229796e57ebec013a6df703f4a7e4ba2bf -88dc10906c6bebc22bca3241427fcb090cd1b5b14790a16c9a7bb1cd735d8f55, 9a2b1509e855879310095f3528891127db71d59da54bb7906fbcec82c18761c7, 7bf6fe0c68a45b98e4868b1e604ac61467c32e6445df7f61e0fa8c26232673d0 -ed5813590470914c2fe3f69ac67f85f424060b406da58dd868ff4b64fcd589aa, 1bb189b511858270dbe10a5996e83bdf95ca162805d1e82b81d67f3d01f037ea, 0d052eec1b3a2a2265af54fd415cd3352fdfebaf7bf57a7a3b0965c4f8d10511 -e5cb6a10359814aed137a987f7ff29b9b79e13161367858c325ff4e3cf09675d, 329334af14a1c97a1a98730cf172590239f44a065c446b31e1dbb5032f2bd6c9, edaa088182ddc36e15f49dc6c9541aa5e3a0826e0d234f2c604f359ae2b78320 -30c5dd294a49abe4b74a101640845ec029f94ef1d00c22c9e624196cc95a4d53, 4de03dbf26077c929ddae08e31a6cebc093da6756fabf61cfa500ab95fa8866e, 8bfb8ffd3a314c88fcba94c180bc7adce5abf84763c1078857157f3caefa0aff -a3830eb3f04f7361481caac17488bbf6b00d49031bd7ae5cbb691b6081e88f28, 866cd264547e5c3fcc7ae06c14fbfcd40e773088ad4783563c45b74390d4e4fd, d78dcffe89d52307b08e5fac364693f220e4cc01c85d1a88df880cca104becbd -9919aa5cee30d867a187ad687846442058e8ad616a52f48578e59e87993c409a, c556d1f7f59a0d62bebf1b36e4b6935312216dc64cb7d1546334e8db245ca930, bf57b1d097537b39ee8e83318a7a69043ddaf75428245e71ceb2cdbb05773789 -39610c4433b295fb61877039122e40353d41d483c2caffcad979c60b21d51928, 4c5cd01ccc4e741774fb74092a1e3432cda16b9906d722a92ca2ba57aae97694, 6b0305a8c4cf240e3a621f281df142ff1dccccf277ff879d95497c51a033f88e -09dd9f1a244b03032379a9b8dd89d8fcf4e296fe300f7a237595932898351533, 1a0859ed9c9499a5f168a4cd03673340780c446a5b0a03f93635b19a76956922, 67592d8260773c4c8e003e104d844583e65f4a3e517edfb736c4cd0170116ad0 -a34f5e1d0ffc653c05e14967830d1a35afb9d516229260404f1d583b3fe54451, 62d8d7d5ec708414e38aeb42075ab3d9d8ff204fc65ae17acc8021ed7830081a, 06e843ea8f6ca0fedf2819e053f84269b695041f53af744667a1fd16a13ecb0f -fb288aa8b0b342d4df3b4bd1cadb71d7181ae697b3922bf9bd67a8ba78b03ef3, 94e7ba899ec6ef40ed851f24db3d0c9fd5f8afdc35e5dbeb97fd7b5bbef8faa5, 44b0af56703ba02c8318815e601e0e27943162b2484b6951de4b248220fa0bf1 -96c42493b136f39f734484bef918b055b662c88d0569d0972ebc340d6e431f6f, 09bbed24ab46fbf9eba629c44ca02a8dfb3d927dd9d639e75c24a1f896e89ffb, 0cbf9f0e61a5e31da16787243bdd29036367fcfb6c67ff8aed343058096e3fce -d1b5fb7d8b35ee6fa235aa76be0d67fb3f8e3eb66f00496350bea43fe5b84604, 15d3a73e7271b1811708a1840c3804f84866d95eadb59b3c5f810ab00f14b2ca, 3ca42232b07a2d5cc24dbc933525f3d5dce5f5489229a4ebcd37d17bc72e2780 -38a4507fd8c05e2c8dcbddf9f11e8e02e1303f8e6af0b1ecd0840510bce6e94b, 1231645d0b136810bad8f610c1b60ac1bf10fdfb9da2cd4a8991a88b765b66c8, eda9c2a2635996ca8db64fd94885580cc85731e2e8fcdd2435fee8bdf8561670 -1ef1e927822aec3fb082e001cde47190c3c2e5b386783dfe2da3dddd388a40d0, f31ec8156dd1b4855e13bbdf3c5e36c7897257e4c1f68b3289d0aa496e655767, 6ee3b5c862d9c54560d1b6478ea7775baa29e3a27f2bb9afd578d370a9eb2caa -747d64c0f3df3c0bebec1b613b7837c0fd7a4e1613ebe0dd47f4f0a3b7ba86f3, 4a8d69c0ed8cc7db1c66c125b11071f713f1e9264d6b30166f84d7cc2c756ef5, 5007362c4afda612f7aff0d2191bfb64436834bd5c407a3bb2300a54fc88e87b -a5bb197ba47605873487ac1f17ba5fc26349e3f1eb72bdd400c33671d91f0cb3, 0693facca3e470793786c55e5ed16b17ce4ea28ea3b9aac71adfe93dbb45a78d, be9efde18b1c557926d1bb83de68f3f132c542eda7ef7752fa380110f870df9b -5eaacd8809df848778e17fc836ba70ce97c7e2f2cdd9fc398524711dc4685621, d43b24a33afb37382cea46d8ca34210676035c1170a6bcf11d81dcc4e126549a, 065529d4aa1a7b0057ba8c0fb5cf49b5d103989cd9dfce9f563313aabd4a0608 -76233f983f5fbfdc9034515383e0a05aedbe26819a11dfc0646b225a8e3916ce, e37c44ed9fe67b0c9c21cada3612130d8a23679bb15a1c735defa09addefbaa4, 0be6d49ab7c145771f9c96e2ce52dc7cf89bfe3de9b157699d7a30425ad05630 -3370e3eaa2ab6507745c0134d43a280558093e8bd64edbe549a1ed9494dfedba, 0d829439c3f15a9734e1e74436e0dac7a758fff116f6625ce99a98bf539bf9c0, 5f200565dec0b3443c60667b6280fef8fb8711dc80c1d972ece1583f0eb58b0a -a97ad0e703f0ffb017a464f9ad9ccd2e9377c902c831d6b0bffaa49b6b7af548, 81957929d85999c0beea37fef876f08dc030bdef2a07664c9280b4622518aac5, ca0f0326128c0aa46cae04610a08c0e36474ad052fabaf7cc888ee443b09c559 -800b3a3bc73573a6802d79bd1609a57f88ab16df9e0d8fb9741a2f576d046d13, 2f4c56116f1496ee24703dfcf021d49a0093dedd8a9d1a692b8318813c4345f4, 0ef9c1f225f180d8bf8ec3340deab2f5fb5f0bb5fe2bc57fb4c9bfcfad0625d0 -bc30737dbdf21dfea2b3fc09da649fdbc0232f2411c192322dc41087e2b5684f, 0d7cb63e218ed54dcd82eba02385b5acaac0b3db463197bf32bdbb313b0b7f58, 4ef1f2e30f02da171093f742c7db6ab1808bbdd60851808ecec921d377aa5a66 -75e7fc921cfaa4633cd04e7210354d40e7bb594d64536c38bdf9c320e283eb09, d6f069952a64023e8a30a8a01880220d2c2fc287f346743b8be79a7671101825, 7dcd3bcba0395c01b68a235bf5fa6d542e1a6d77f2ccc9c1b1e084b004c1d8f3 -3b9bad9421464824b66220da821f925a51ac838c5ea9f61dd37ffb639338fe65, 55e2775c0837cccf90d49aac62f11936cf43b6a6dc8b9bdccbf0974a9895b860, df7a5ef5d9d36889be44abbeffd7f9df279a8301c1ca154b2cb5f6bc28c6f983 -84557eecc6ea62237c2085deac96c74a0f06462f46a4a17398a2bef1093049ad, 0321d16e6099548e59d7c2f8a68a7d66270539c9cc4419e12759f89220ce835b, 0e55b11f1b9f6f2b852fec36a06c6579d60cf7cbe60612a14a01d1609761f4d8 -1ba126ccd1b3f7b1b26262b6ee9706ec6dfc870271ae379597086dfec205fbc2, 401761f50703f1a7e1497668e77ad4c95d6247f1bee168704dbc9e87fa926eda, 17c876f11e4d2cf3be58944c8dc63d38c2cfd3db1925201db826240818b8df2a -8224f769799a40e2e17087710695576456bbb346ff39bd23afc75a6f2596a4f0, 179cc98c1ff592cf4d446b95664c5533711800ca9f33801e474f037ac36a52ff, 98c9198bb56297dcc7842a2b498a1ba8bccc2c0acfc6e2d59798c195e50c8717 -5b1dc56ec8ce89191e9c5f6d06ffe4b2310a1790e113c55ed8fbd9d2b66f4b87, 70501a6071777bbf4b22bfe42b6deb74b67a0f286e97e077888a891335c620eb, 88bb08213efa2a4fc6780b5df0dcc4c79485cf85a407d858f676e2ca356132f6 -a83e26856a4e4c3400e8fd28ec61a87801054be560203164d3e54ece579d229b, 904a4ce1df032cb5bd581567a08bd548de2b7ef83dd6097a14dcd0a3ead32920, c5780e0bc53bacb1d2874344f2095b560babd2bf6faf4a662d8e23ca724b43f1 -a6f4cd59810c590fb39b897eda9b4756e2c700355b34d005ff234422e3d91b18, d94c35faf30263099df9bfd87ae8af055eec50ff6d784589376a4b9ed059c878, dafd7a25524d31f42998fc377c9e078568381dfc7ea853fa6a365e7bb87fe9ff -1bfadfb7429ed9aa7c3e4523dcf5a828632ce5faae9a8efda8d6ffdd74c6fa24, bb7f38f4444c4a75819fa3d46ff4abe42179b516ddc2b5790b888e92b7aeacef, f43881d19c5d772618c017bdd7e998c948559a21ed2ccd0a104b215a282b6f02 -5e41d7d408c8358a1426f29e0a764b60223aec2351864d84dd3a779d0aaa09f3, 1b0fef36cab9f932f3fc4adf3924c08faf1928fae0cf75e549d4df0e19d40e67, 78f041e8e458c0fd266be2c70f50be02346aebdec1a6df7cad76490fd5992ece diff --git a/test/data/scores_addition.csv b/test/data/scores_addition.csv deleted file mode 100644 index 5aa5769d2..000000000 --- a/test/data/scores_addition.csv +++ /dev/null @@ -1,1025 +0,0 @@ -4-3-50-64-100-50-36, 8-5-120-256-113-100-171, 14-8-150-512-172-150-600, 18-10-200-1024-228-200-600 -28, 682, 66416, 1283571 -30, 702, 63538, 1301601 -26, 647, 66304, 1314976 -24, 667, 64428, 1288193 -28, 634, 66832, 1321928 -28, 715, 65520, 1292861 -30, 668, 65772, 1285605 -28, 637, 64367, 1308512 -24, 718, 65062, 1316256 -24, 640, 65127, 1329976 -26, 660, 67472, 1309517 -30, 610, 64966, 1361152 -26, 748, 64768, 1325100 -28, 704, 66802, 1317252 -28, 652, 68400, 1309717 -32, 616, 65867, 1334816 -22, 692, 64736, 1328712 -24, 633, 64318, 1305683 -22, 668, 68104, 1340880 -24, 688, 68512, 1291670 -28, 674, 65135, 1330138 -28, 630, 66020, 1309504 -24, 702, 68616, 1276059 -28, 644, 66430, 1274860 -26, 720, 64960, 1331512 -26, 660, 62840, 1355506 -28, 665, 65582, 1333088 -22, 665, 67209, 1329600 -28, 669, 67076, 1331636 -28, 664, 67783, 1271267 -30, 648, 65610, 1289288 -26, 660, 66116, 1335335 -24, 644, 66144, 1356608 -20, 642, 68192, 1313408 -26, 658, 66678, 1270965 -26, 662, 67488, 1352880 -28, 678, 67660, 1311585 -24, 589, 65756, 1296210 -32, 624, 66956, 1295984 -28, 644, 65360, 1296090 -28, 716, 64944, 1297520 -26, 640, 64938, 1263843 -24, 697, 66358, 1306240 -26, 693, 64672, 1320208 -30, 674, 68043, 1331591 -28, 688, 68304, 1285812 -24, 640, 65958, 1321510 -24, 615, 64064, 1349360 -28, 638, 67016, 1336864 -30, 716, 67032, 1331360 -28, 665, 66190, 1300828 -30, 641, 64121, 1336409 -22, 644, 66416, 1325336 -30, 637, 61786, 1329824 -24, 698, 63098, 1268192 -28, 680, 65904, 1328354 -28, 644, 64969, 1310384 -26, 640, 66786, 1286465 -30, 720, 65615, 1286803 -28, 704, 66700, 1322128 -30, 710, 65182, 1323520 -26, 683, 67902, 1311200 -25, 686, 64152, 1292614 -28, 640, 66817, 1341696 -22, 636, 67785, 1288112 -28, 679, 63725, 1355736 -30, 683, 63496, 1300480 -30, 640, 64635, 1296379 -28, 633, 66668, 1285018 -24, 642, 67584, 1344814 -26, 660, 67816, 1279632 -28, 657, 64990, 1330496 -28, 664, 65312, 1355520 -30, 654, 63792, 1314839 -24, 622, 64280, 1277120 -28, 611, 67904, 1299509 -26, 701, 65456, 1309256 -28, 690, 64445, 1324198 -30, 660, 66384, 1336576 -22, 684, 67680, 1334012 -20, 608, 67312, 1326864 -25, 665, 66928, 1317492 -32, 670, 66379, 1313410 -28, 652, 65936, 1318056 -32, 706, 65258, 1323340 -30, 693, 67085, 1320795 -24, 706, 64473, 1315072 -30, 741, 66448, 1307425 -28, 678, 67320, 1357312 -24, 690, 66110, 1337766 -28, 660, 66295, 1300768 -24, 668, 67704, 1282720 -30, 632, 65168, 1326864 -24, 661, 66784, 1362480 -22, 662, 63968, 1346904 -26, 669, 64837, 1332920 -28, 636, 65000, 1322202 -30, 685, 66038, 1339904 -22, 624, 69920, 1297477 -28, 669, 65551, 1312064 -28, 602, 67421, 1331200 -18, 634, 65659, 1325885 -26, 643, 66211, 1311028 -24, 648, 67160, 1312804 -28, 716, 65011, 1321747 -24, 663, 65088, 1336800 -30, 685, 66396, 1305792 -30, 694, 66160, 1297696 -28, 655, 67024, 1313608 -26, 667, 66216, 1342480 -30, 686, 66235, 1307299 -28, 645, 64017, 1271930 -28, 668, 63439, 1347473 -30, 676, 68374, 1320624 -26, 666, 64619, 1287665 -26, 675, 66556, 1318736 -28, 688, 65739, 1312320 -30, 662, 63618, 1338750 -28, 634, 68230, 1310240 -28, 754, 66714, 1322152 -26, 628, 67774, 1328303 -30, 602, 67088, 1329712 -26, 630, 65680, 1313256 -28, 665, 64136, 1290353 -22, 692, 68728, 1307660 -26, 671, 65542, 1314176 -24, 662, 66976, 1312974 -29, 684, 63687, 1309184 -24, 712, 66352, 1328192 -26, 724, 66556, 1306086 -29, 642, 64880, 1321571 -28, 700, 65454, 1348512 -26, 676, 68400, 1342765 -28, 634, 64893, 1326384 -24, 657, 67184, 1311423 -28, 648, 64302, 1314368 -30, 648, 66784, 1344064 -28, 693, 65728, 1349531 -20, 681, 63160, 1291217 -28, 640, 63952, 1365037 -25, 616, 66120, 1340416 -26, 665, 65429, 1290752 -25, 706, 65198, 1311920 -26, 672, 66052, 1358488 -28, 673, 65772, 1312587 -24, 693, 62752, 1300959 -30, 670, 67121, 1307448 -28, 674, 65806, 1312008 -28, 667, 66144, 1346328 -28, 664, 68690, 1306856 -20, 652, 63280, 1320213 -22, 688, 66384, 1323688 -26, 661, 66364, 1314343 -23, 662, 66732, 1311360 -26, 660, 63776, 1324176 -28, 608, 66576, 1301148 -24, 672, 66969, 1351712 -30, 683, 66633, 1307308 -22, 657, 67940, 1330816 -32, 710, 67726, 1316264 -26, 656, 65680, 1317648 -26, 637, 64736, 1324552 -28, 670, 66775, 1301842 -29, 664, 65872, 1354304 -26, 701, 65880, 1341368 -28, 656, 65771, 1317742 -22, 674, 62987, 1297944 -30, 662, 67445, 1290324 -30, 713, 65698, 1359376 -26, 625, 64336, 1309728 -24, 593, 66818, 1272161 -28, 704, 67128, 1368890 -26, 666, 68048, 1304116 -22, 656, 67312, 1309436 -26, 634, 64815, 1326717 -20, 680, 65104, 1285574 -30, 640, 65999, 1333792 -26, 640, 64672, 1299211 -30, 670, 66559, 1276638 -30, 681, 64010, 1285968 -26, 737, 64112, 1337368 -31, 716, 69626, 1308288 -28, 681, 64801, 1328387 -32, 685, 66176, 1337004 -24, 692, 67128, 1317696 -24, 736, 68607, 1329814 -22, 688, 65536, 1311247 -28, 691, 66960, 1305565 -28, 710, 64997, 1303087 -22, 681, 66380, 1311448 -26, 666, 66734, 1340811 -20, 646, 64941, 1337184 -28, 658, 65412, 1292424 -24, 690, 66323, 1340528 -30, 682, 69472, 1283726 -30, 633, 65199, 1300671 -28, 668, 69205, 1357319 -26, 611, 65690, 1302360 -28, 664, 67088, 1312256 -28, 648, 66039, 1340208 -30, 682, 66240, 1291776 -24, 680, 65479, 1317206 -28, 685, 66885, 1296433 -26, 663, 64624, 1337392 -24, 709, 65987, 1305092 -28, 710, 65048, 1287764 -28, 682, 66592, 1309147 -28, 647, 65739, 1294067 -30, 644, 61816, 1299511 -28, 688, 64698, 1339034 -26, 624, 67161, 1311498 -26, 692, 68112, 1340220 -22, 648, 65551, 1309456 -26, 642, 64264, 1300172 -22, 650, 65664, 1326612 -26, 681, 65794, 1328768 -26, 631, 67168, 1320280 -28, 683, 67024, 1290723 -24, 614, 65321, 1335120 -28, 648, 69600, 1327232 -32, 703, 62888, 1343732 -28, 656, 68544, 1297363 -28, 678, 65917, 1336952 -28, 655, 65472, 1307044 -26, 677, 64440, 1304342 -26, 662, 64362, 1312107 -24, 703, 68192, 1311392 -26, 668, 67152, 1324818 -28, 685, 66816, 1331028 -26, 642, 65440, 1310056 -26, 700, 66414, 1313568 -28, 727, 66186, 1341517 -28, 646, 67408, 1276080 -30, 685, 65895, 1313088 -28, 654, 67139, 1316065 -30, 712, 63584, 1328974 -30, 655, 65807, 1306208 -22, 716, 63684, 1309237 -29, 676, 66370, 1324852 -32, 638, 65364, 1293264 -32, 686, 64536, 1343696 -26, 684, 67376, 1302826 -24, 598, 67156, 1328906 -28, 716, 64008, 1308640 -28, 698, 61862, 1292850 -30, 728, 67377, 1291264 -28, 692, 66016, 1316160 -30, 670, 66457, 1264827 -28, 669, 67509, 1303250 -26, 730, 67013, 1344368 -24, 632, 66844, 1319520 -26, 644, 65040, 1302131 -22, 696, 66117, 1348096 -20, 640, 65508, 1285365 -30, 678, 66207, 1325198 -28, 718, 66825, 1331536 -32, 661, 66011, 1325036 -30, 636, 68067, 1321532 -28, 653, 68320, 1298386 -28, 750, 66536, 1317253 -26, 657, 64897, 1320184 -24, 704, 63946, 1330492 -28, 609, 65860, 1347648 -24, 664, 64320, 1316896 -28, 654, 65059, 1305703 -22, 628, 66403, 1301408 -26, 646, 65686, 1300938 -30, 693, 68288, 1302463 -26, 643, 64453, 1312439 -30, 679, 66407, 1315888 -30, 668, 64488, 1354256 -22, 633, 66668, 1334736 -24, 624, 66010, 1331235 -28, 616, 66190, 1322031 -30, 628, 67680, 1268868 -30, 629, 66700, 1323096 -24, 662, 65200, 1308803 -30, 656, 65372, 1320808 -30, 683, 65532, 1312535 -24, 641, 65010, 1311367 -28, 636, 66463, 1311226 -26, 654, 66688, 1293520 -26, 712, 65241, 1304717 -26, 643, 65559, 1323416 -30, 667, 64768, 1314248 -28, 682, 64452, 1366592 -30, 580, 67124, 1289632 -26, 644, 67152, 1287616 -30, 648, 67312, 1353856 -24, 660, 64798, 1315888 -20, 669, 66312, 1311272 -26, 710, 66544, 1312544 -26, 644, 66464, 1326382 -32, 737, 64927, 1328880 -28, 659, 65451, 1317906 -26, 647, 64840, 1269152 -24, 654, 63544, 1324288 -32, 636, 66776, 1331398 -22, 625, 66699, 1303145 -28, 703, 66463, 1327736 -22, 656, 63719, 1356452 -30, 726, 65666, 1302528 -32, 646, 69056, 1326720 -26, 697, 65476, 1305343 -28, 599, 67061, 1304532 -18, 676, 64337, 1316116 -26, 673, 67440, 1314953 -28, 714, 66057, 1327440 -26, 660, 65411, 1334688 -32, 748, 64142, 1351609 -26, 629, 63225, 1314360 -26, 686, 65808, 1327577 -30, 664, 67392, 1302806 -30, 728, 64347, 1307276 -26, 720, 64920, 1299448 -28, 663, 65889, 1277056 -30, 616, 62103, 1321252 -30, 704, 67320, 1317344 -25, 662, 67061, 1293696 -26, 666, 64398, 1331736 -28, 680, 64301, 1335127 -28, 700, 67816, 1306408 -30, 652, 65878, 1349028 -32, 688, 66483, 1326125 -26, 646, 67885, 1316823 -26, 674, 65209, 1323296 -28, 704, 65070, 1345203 -26, 651, 65564, 1309248 -27, 634, 64682, 1267840 -28, 650, 65012, 1323755 -28, 693, 65768, 1341185 -26, 660, 64639, 1337656 -26, 606, 65314, 1304716 -26, 644, 64448, 1301376 -24, 642, 65778, 1294720 -26, 688, 66000, 1330152 -26, 672, 67472, 1349233 -28, 668, 65088, 1324544 -24, 652, 63534, 1335152 -28, 640, 64625, 1352064 -20, 705, 63000, 1326656 -28, 659, 66211, 1313964 -28, 674, 69388, 1300224 -28, 685, 67165, 1321456 -30, 644, 66506, 1320720 -24, 718, 67869, 1307547 -28, 639, 67731, 1327881 -28, 662, 68992, 1322792 -24, 682, 66336, 1300876 -26, 640, 63568, 1306918 -30, 684, 64011, 1319835 -30, 668, 65259, 1298921 -24, 647, 65728, 1300960 -27, 682, 65872, 1316464 -28, 634, 63434, 1312082 -20, 672, 65840, 1292032 -28, 636, 65968, 1324095 -22, 692, 65858, 1326464 -30, 694, 63633, 1317792 -26, 686, 65947, 1298048 -30, 641, 65778, 1291440 -28, 660, 67688, 1322386 -20, 668, 68224, 1283870 -30, 605, 67073, 1299376 -22, 691, 64726, 1312764 -30, 664, 65563, 1315290 -30, 682, 63374, 1345368 -26, 669, 64219, 1318720 -28, 648, 67056, 1309259 -26, 672, 67588, 1296179 -22, 648, 64756, 1306165 -26, 688, 66924, 1319320 -30, 737, 65391, 1337248 -32, 654, 64851, 1291823 -28, 669, 66561, 1294298 -26, 653, 66244, 1285015 -26, 668, 66605, 1309461 -26, 647, 63345, 1358016 -22, 671, 67445, 1303242 -22, 672, 70144, 1267699 -30, 636, 67392, 1286212 -26, 624, 66896, 1334367 -30, 708, 65616, 1324695 -24, 620, 65013, 1333579 -30, 739, 65536, 1314490 -24, 656, 65524, 1278698 -22, 713, 67904, 1303368 -22, 642, 65460, 1237428 -24, 609, 64918, 1308564 -28, 608, 65712, 1323744 -28, 655, 65608, 1288824 -24, 667, 65470, 1314592 -28, 672, 64375, 1308343 -30, 672, 65793, 1313108 -22, 690, 65896, 1354272 -28, 643, 64656, 1284424 -26, 630, 64904, 1319035 -24, 670, 66192, 1316832 -22, 680, 65736, 1312764 -22, 693, 64128, 1299834 -20, 692, 66122, 1319088 -28, 680, 65470, 1338127 -32, 649, 63862, 1283968 -20, 684, 68852, 1285184 -32, 654, 65711, 1301472 -26, 592, 67210, 1295675 -26, 654, 66498, 1306733 -28, 685, 66133, 1329248 -26, 671, 64652, 1323936 -22, 640, 66444, 1328000 -28, 699, 65408, 1302612 -26, 589, 65965, 1303686 -28, 716, 67455, 1311520 -30, 688, 68288, 1292575 -22, 612, 63736, 1317920 -24, 642, 65994, 1304192 -22, 634, 65553, 1321312 -26, 693, 67334, 1314544 -28, 689, 67703, 1302919 -22, 738, 65554, 1338427 -28, 678, 67881, 1333554 -26, 658, 66176, 1306384 -24, 673, 64800, 1307547 -26, 654, 65394, 1344912 -26, 659, 65592, 1308002 -28, 591, 65336, 1321224 -28, 678, 65952, 1326442 -22, 663, 66588, 1321410 -26, 679, 66451, 1343000 -30, 680, 68432, 1308985 -30, 627, 64448, 1294334 -28, 638, 65445, 1294016 -28, 599, 63873, 1344960 -22, 648, 65011, 1274979 -22, 668, 66613, 1297450 -30, 692, 67519, 1312453 -24, 676, 64388, 1292581 -28, 696, 64928, 1319044 -20, 660, 64760, 1289750 -32, 692, 65248, 1324313 -30, 666, 65004, 1310768 -28, 704, 66080, 1307488 -26, 682, 64464, 1288163 -26, 659, 67632, 1326576 -24, 641, 65616, 1315874 -24, 692, 63467, 1293110 -20, 644, 67213, 1292791 -32, 646, 66368, 1296032 -32, 690, 63506, 1295536 -22, 684, 65453, 1310518 -30, 676, 64105, 1322127 -30, 660, 65029, 1318697 -26, 619, 66672, 1327040 -18, 666, 65068, 1295090 -26, 674, 65136, 1288681 -22, 571, 64201, 1311096 -29, 648, 67823, 1329890 -28, 630, 64994, 1293728 -26, 664, 67056, 1303616 -28, 676, 66214, 1331520 -28, 685, 62528, 1293254 -28, 659, 65745, 1321984 -24, 648, 64032, 1316341 -24, 662, 64841, 1314868 -26, 635, 65521, 1314264 -26, 664, 67712, 1285522 -28, 687, 64296, 1320048 -28, 674, 67152, 1336505 -30, 672, 64284, 1285004 -30, 699, 64832, 1319266 -24, 766, 63840, 1329728 -26, 609, 64846, 1300928 -30, 668, 65584, 1303315 -26, 680, 65840, 1303864 -26, 610, 65156, 1356672 -26, 692, 67664, 1293472 -28, 658, 65428, 1336506 -32, 658, 65790, 1313920 -22, 689, 67131, 1307671 -24, 636, 64636, 1323928 -26, 688, 65448, 1328128 -30, 672, 64865, 1332944 -32, 645, 64552, 1265540 -26, 694, 66336, 1309437 -30, 665, 65216, 1301536 -32, 640, 67216, 1271936 -26, 748, 68800, 1304736 -24, 700, 66568, 1339536 -32, 600, 63486, 1289922 -28, 660, 63869, 1319657 -32, 600, 64206, 1312320 -24, 731, 65733, 1284667 -26, 645, 67672, 1319858 -30, 678, 65800, 1310448 -22, 684, 65479, 1336964 -26, 698, 65126, 1323408 -28, 684, 62431, 1343008 -28, 697, 64968, 1307178 -24, 633, 65212, 1339296 -24, 676, 65488, 1343436 -20, 666, 63839, 1356512 -28, 618, 64848, 1331499 -28, 724, 66589, 1291508 -30, 666, 64861, 1326369 -28, 644, 65117, 1295992 -26, 626, 67571, 1299104 -30, 675, 65851, 1309641 -24, 609, 66090, 1304263 -28, 670, 66881, 1328919 -28, 711, 64982, 1343584 -26, 652, 63612, 1290011 -24, 645, 66637, 1318981 -30, 689, 68303, 1322891 -28, 712, 66408, 1324200 -26, 687, 66632, 1344258 -29, 640, 64168, 1300352 -28, 604, 65870, 1316292 -28, 659, 66656, 1294938 -22, 692, 65271, 1321728 -30, 616, 63582, 1301568 -24, 622, 63265, 1331920 -26, 652, 65224, 1338573 -26, 678, 64344, 1323704 -28, 612, 67800, 1316632 -32, 694, 65102, 1331856 -23, 668, 63826, 1335776 -30, 690, 66033, 1280382 -28, 704, 63907, 1342492 -28, 653, 66904, 1309408 -26, 704, 67776, 1313984 -22, 698, 67312, 1343616 -26, 675, 69111, 1342470 -28, 653, 66206, 1285752 -30, 676, 66492, 1305407 -28, 656, 65113, 1336872 -26, 626, 66128, 1270154 -24, 602, 67501, 1289828 -28, 661, 63556, 1315933 -32, 724, 66540, 1318980 -30, 646, 64672, 1326709 -30, 695, 65030, 1288736 -28, 706, 65424, 1319025 -26, 670, 68272, 1313197 -28, 656, 65828, 1331924 -28, 632, 64882, 1309412 -24, 620, 63895, 1289301 -28, 686, 63593, 1341440 -28, 721, 67437, 1340280 -28, 647, 64728, 1330000 -20, 667, 67815, 1323808 -24, 637, 63721, 1322752 -28, 639, 67184, 1301240 -30, 680, 66348, 1314400 -30, 659, 65576, 1303364 -26, 661, 66393, 1315720 -27, 650, 64477, 1295265 -28, 676, 64928, 1284141 -28, 697, 64064, 1293926 -24, 658, 67328, 1286464 -26, 682, 66914, 1294903 -28, 637, 66960, 1299233 -24, 646, 64911, 1320163 -32, 688, 66722, 1313078 -26, 629, 66771, 1314592 -24, 695, 66424, 1347008 -30, 672, 64906, 1319513 -30, 664, 66093, 1310208 -24, 660, 64758, 1325580 -28, 660, 64009, 1323136 -30, 697, 66808, 1291358 -30, 611, 65965, 1342136 -26, 679, 64583, 1312688 -30, 674, 63756, 1305553 -30, 675, 63856, 1307584 -28, 654, 66538, 1282384 -28, 716, 66208, 1312684 -22, 660, 64408, 1308704 -22, 640, 66040, 1330413 -28, 652, 66258, 1341698 -24, 669, 66786, 1324864 -24, 624, 66388, 1334623 -26, 654, 66960, 1333764 -26, 676, 66960, 1312836 -28, 647, 65265, 1295777 -28, 656, 65848, 1334656 -26, 670, 62988, 1311660 -30, 664, 64426, 1263358 -22, 615, 65099, 1291614 -30, 704, 63831, 1324561 -28, 700, 65486, 1280555 -28, 640, 67165, 1330931 -26, 632, 66384, 1336424 -26, 674, 68704, 1262880 -26, 670, 65682, 1300528 -28, 646, 65900, 1323216 -24, 704, 68276, 1309440 -32, 688, 63864, 1300105 -30, 603, 67845, 1312448 -22, 691, 67784, 1312890 -28, 695, 67376, 1342194 -28, 663, 67947, 1314626 -28, 661, 65156, 1349873 -24, 616, 68400, 1319200 -26, 709, 66567, 1329648 -26, 644, 67178, 1306449 -28, 664, 66024, 1334950 -26, 576, 67266, 1288800 -28, 678, 67128, 1338591 -30, 629, 64296, 1319756 -28, 656, 66566, 1294832 -30, 661, 67960, 1314055 -30, 686, 66136, 1353366 -28, 666, 63736, 1304115 -28, 690, 67303, 1302304 -30, 678, 67203, 1336498 -24, 714, 64956, 1340960 -30, 614, 63114, 1328608 -20, 648, 66513, 1338688 -24, 680, 65517, 1306593 -26, 637, 65552, 1299681 -28, 612, 64674, 1319344 -28, 663, 64052, 1335804 -24, 630, 64725, 1326008 -22, 638, 63560, 1321440 -30, 776, 66600, 1338912 -30, 648, 65526, 1292499 -26, 656, 65344, 1324352 -22, 716, 64996, 1310304 -26, 722, 65772, 1296656 -26, 676, 64209, 1280113 -24, 649, 66099, 1300992 -22, 634, 63664, 1290171 -27, 663, 64771, 1306633 -30, 654, 63994, 1306640 -30, 665, 66820, 1311527 -26, 674, 65879, 1298284 -26, 684, 66704, 1271339 -28, 694, 66193, 1313280 -28, 703, 68338, 1314261 -26, 676, 64084, 1311862 -22, 670, 66016, 1310839 -24, 658, 64172, 1335899 -28, 665, 64945, 1305368 -26, 638, 62842, 1337386 -32, 668, 66368, 1325857 -24, 692, 67432, 1313216 -24, 676, 62900, 1316352 -26, 606, 66110, 1299170 -30, 691, 64741, 1318601 -28, 674, 66626, 1313098 -26, 628, 65827, 1315298 -22, 704, 63802, 1302169 -30, 706, 65984, 1314381 -30, 669, 62273, 1329536 -30, 640, 64160, 1323680 -28, 661, 64298, 1307174 -26, 656, 62519, 1306480 -28, 663, 66196, 1340976 -30, 620, 63257, 1356356 -26, 666, 66071, 1282464 -30, 658, 65549, 1349434 -30, 661, 64825, 1333864 -28, 676, 65572, 1315805 -24, 672, 63623, 1322944 -28, 650, 66389, 1318779 -30, 612, 68792, 1302896 -26, 651, 66223, 1334736 -26, 638, 68032, 1349556 -28, 687, 66377, 1332413 -28, 710, 67824, 1302086 -26, 652, 66646, 1346056 -22, 590, 66963, 1299591 -26, 626, 64822, 1303484 -28, 620, 67448, 1285794 -26, 654, 66527, 1315888 -22, 594, 65728, 1346240 -30, 640, 65523, 1291260 -28, 669, 64373, 1314405 -28, 632, 67424, 1311164 -24, 660, 66871, 1328520 -30, 640, 65574, 1322270 -28, 667, 64208, 1349696 -28, 644, 63648, 1288331 -26, 640, 67288, 1330288 -30, 674, 67010, 1327868 -26, 667, 67264, 1312033 -24, 685, 66722, 1324189 -28, 688, 65998, 1287840 -30, 675, 66406, 1307854 -20, 642, 66603, 1292024 -24, 655, 64995, 1287256 -30, 633, 64219, 1291112 -22, 682, 65742, 1296304 -28, 644, 67037, 1309712 -30, 669, 67069, 1305271 -24, 684, 66564, 1337940 -31, 629, 64249, 1295616 -26, 680, 67558, 1325696 -26, 694, 64764, 1293365 -28, 644, 67045, 1341968 -24, 646, 64668, 1285095 -28, 699, 66239, 1323614 -26, 691, 64505, 1316897 -31, 626, 66184, 1340416 -20, 708, 66232, 1293824 -26, 687, 64528, 1306272 -24, 688, 65024, 1292816 -28, 701, 66142, 1297247 -24, 636, 66352, 1333184 -26, 680, 65736, 1333976 -26, 689, 65090, 1316534 -28, 688, 65168, 1299604 -26, 677, 65307, 1331200 -22, 648, 64789, 1337888 -26, 680, 66255, 1314560 -28, 697, 65396, 1317616 -26, 613, 65721, 1331509 -30, 674, 66330, 1310897 -20, 707, 64533, 1322478 -24, 690, 67712, 1318775 -20, 666, 63713, 1302036 -26, 622, 64281, 1347008 -26, 628, 65226, 1317776 -22, 698, 65700, 1318871 -30, 608, 65283, 1305890 -24, 632, 68386, 1316864 -30, 650, 65120, 1292549 -26, 626, 67504, 1293766 -28, 736, 67336, 1339577 -25, 674, 63728, 1283847 -25, 649, 66556, 1293120 -24, 655, 66340, 1311834 -28, 696, 66551, 1327120 -30, 684, 65692, 1321872 -32, 666, 65308, 1315246 -28, 704, 64348, 1319760 -26, 580, 66826, 1336192 -28, 620, 66496, 1302991 -20, 692, 68848, 1316104 -28, 678, 66198, 1329913 -30, 672, 63229, 1302704 -28, 717, 64559, 1305463 -32, 672, 62947, 1313504 -28, 696, 64544, 1325664 -32, 672, 66328, 1318528 -28, 668, 66819, 1294272 -30, 676, 65664, 1325904 -32, 642, 67364, 1289720 -24, 646, 69888, 1318544 -28, 664, 66064, 1308451 -30, 660, 66544, 1308736 -26, 662, 64084, 1291424 -28, 698, 67981, 1342329 -26, 667, 66495, 1339215 -22, 688, 67393, 1333040 -26, 642, 69280, 1316860 -30, 604, 67635, 1327232 -30, 695, 62056, 1264616 -22, 663, 68057, 1330156 -26, 631, 65378, 1297264 -30, 600, 66960, 1282624 -22, 724, 67432, 1332056 -26, 634, 68461, 1271384 -26, 648, 65953, 1306934 -28, 639, 66560, 1296321 -32, 646, 65104, 1297640 -30, 646, 66410, 1320860 -28, 634, 63407, 1318080 -28, 654, 65583, 1289244 -28, 650, 65788, 1280174 -28, 673, 63868, 1307620 -26, 606, 65429, 1346864 -26, 646, 67584, 1331400 -22, 654, 69320, 1312047 -30, 617, 67604, 1303504 -26, 636, 65637, 1298266 -30, 632, 68398, 1323375 -30, 634, 64232, 1291456 -32, 662, 65406, 1314321 -26, 740, 65230, 1302288 -24, 616, 66430, 1310715 -26, 672, 64849, 1332331 -32, 647, 64453, 1307591 -20, 659, 66089, 1271593 -26, 681, 67904, 1326286 -28, 605, 65160, 1328408 -28, 760, 65919, 1304211 -22, 624, 62823, 1268006 -26, 691, 67477, 1304094 -28, 727, 65678, 1318919 -26, 740, 66480, 1349213 -30, 697, 64031, 1339328 -30, 694, 66359, 1287558 -22, 656, 67253, 1286386 -30, 648, 65487, 1303649 -30, 683, 66360, 1344771 -20, 680, 67137, 1327023 -28, 620, 68771, 1306665 -32, 668, 66623, 1319938 -32, 664, 64759, 1340612 -26, 654, 66560, 1286118 -28, 649, 65942, 1335552 -24, 664, 66616, 1281240 -24, 692, 65362, 1316818 -30, 618, 62688, 1301296 -24, 654, 67711, 1329440 -28, 685, 66445, 1298216 -28, 716, 65931, 1275161 -24, 680, 62635, 1303767 -28, 696, 66816, 1353541 -28, 708, 66323, 1300584 -28, 661, 66659, 1337272 -32, 660, 64658, 1353216 -22, 672, 65418, 1301728 -30, 693, 62000, 1321863 -28, 650, 65284, 1314853 -28, 670, 64908, 1354000 -30, 707, 66211, 1314094 -26, 684, 66768, 1308377 -24, 692, 64384, 1301584 -18, 690, 68914, 1338334 -28, 654, 66384, 1251521 -26, 686, 66268, 1310187 -26, 644, 63788, 1308304 -22, 634, 62820, 1336096 -30, 682, 66518, 1312719 -22, 689, 65311, 1328448 -26, 664, 65381, 1317760 -28, 711, 66714, 1326618 -22, 698, 63200, 1327552 -26, 631, 65174, 1309352 -26, 674, 66498, 1306976 -26, 666, 66093, 1290827 -30, 599, 67392, 1326418 -24, 652, 65782, 1306560 -24, 613, 65318, 1314762 -28, 678, 66192, 1326584 -28, 648, 68480, 1297708 -28, 660, 65235, 1352640 -26, 704, 63960, 1305806 -28, 679, 66553, 1322578 -30, 648, 66098, 1311366 -28, 650, 69089, 1330112 -24, 608, 66944, 1352512 -28, 662, 65128, 1298820 -24, 650, 66805, 1328303 -28, 672, 64331, 1333184 -26, 748, 68568, 1310408 -28, 576, 66351, 1307740 -26, 686, 66182, 1299796 -30, 668, 66168, 1281887 -28, 727, 66388, 1288356 -24, 642, 65826, 1319256 -22, 672, 64475, 1310609 -22, 672, 66274, 1323536 -28, 618, 65521, 1302739 -22, 640, 69275, 1288653 -22, 682, 65302, 1315069 -22, 630, 66104, 1330832 -26, 625, 66040, 1304934 -30, 672, 66124, 1314641 -26, 662, 64697, 1300209 -28, 656, 64051, 1317649 -28, 678, 66717, 1349147 -30, 710, 65760, 1313709 -28, 703, 65728, 1273418 -26, 694, 67888, 1300448 -26, 666, 67638, 1313657 -24, 651, 63271, 1271767 -28, 670, 64633, 1329348 -26, 624, 67424, 1338506 -28, 660, 65232, 1315772 -30, 702, 64715, 1342976 -28, 708, 67072, 1325637 -26, 670, 64216, 1294687 -24, 693, 67307, 1287024 -22, 685, 65374, 1325833 -24, 702, 65553, 1290374 -20, 676, 64678, 1343720 -30, 692, 65453, 1343300 -28, 698, 67507, 1327040 -28, 666, 63376, 1319392 -24, 644, 64792, 1316668 -28, 672, 65496, 1315511 -20, 690, 67076, 1303640 -32, 692, 68348, 1306087 -24, 664, 63661, 1321846 -30, 642, 62988, 1314257 -24, 692, 65448, 1292841 -24, 676, 69405, 1326592 -22, 624, 67688, 1340985 -22, 608, 65272, 1298976 -30, 578, 67468, 1311040 -28, 600, 63862, 1313830 -30, 684, 64188, 1286719 -30, 664, 65013, 1321722 -29, 680, 67584, 1312332 -28, 676, 64170, 1286696 -28, 681, 65472, 1316663 -32, 666, 65816, 1352249 -28, 676, 67284, 1332001 -26, 672, 63222, 1288224 -28, 688, 62114, 1292348 -30, 704, 68096, 1323209 -30, 680, 66156, 1306624 -28, 664, 64022, 1325292 -28, 605, 64795, 1302732 -30, 636, 64744, 1295386 -28, 626, 65042, 1312352 -28, 702, 66884, 1327649 -26, 628, 64991, 1342144 -32, 676, 65572, 1281384 -24, 660, 65296, 1327063 -26, 674, 65844, 1326836 -28, 686, 63189, 1294764 -30, 674, 64173, 1321309 -28, 678, 66648, 1278308 -30, 638, 69232, 1311256 -30, 723, 63743, 1322544 -26, 650, 66607, 1346367 -28, 658, 65027, 1294768 -30, 632, 66456, 1338336 -26, 653, 68126, 1302325 -30, 719, 67119, 1265909 -26, 670, 64099, 1308208 -26, 640, 66160, 1331840 -22, 635, 67936, 1318792 -29, 660, 65161, 1333607 -26, 684, 64290, 1307072 -28, 663, 66356, 1316507 -24, 664, 66912, 1304832 -24, 652, 65444, 1291098 -24, 688, 67296, 1299377 -22, 671, 67609, 1306990 -32, 672, 64557, 1341099 -28, 679, 66079, 1329352 -28, 672, 67110, 1289236 -22, 634, 65672, 1304028 -26, 708, 66808, 1342079 -28, 714, 65876, 1330848 -32, 686, 67164, 1309656 -24, 709, 66404, 1329808 -22, 674, 67309, 1326240 -28, 715, 67181, 1285238 -28, 714, 66934, 1341351 -26, 697, 65912, 1305800 -30, 652, 64728, 1348032 -20, 698, 67065, 1280378 -30, 681, 64970, 1290124 -30, 712, 64818, 1296336 -32, 639, 66459, 1317366 -26, 620, 66916, 1353351 -28, 700, 66757, 1326812 -26, 704, 68961, 1337664 -28, 662, 65304, 1300089 -21, 692, 65296, 1319392 -30, 700, 64015, 1339328 -28, 694, 68640, 1294491 -26, 653, 65352, 1299075 -30, 646, 65744, 1322232 -30, 664, 68144, 1317376 -24, 650, 64647, 1311066 -24, 646, 65878, 1341320 -24, 663, 64390, 1324455 -28, 626, 65064, 1320640 -26, 620, 65872, 1334928 -28, 693, 66473, 1305664 -26, 628, 67200, 1293216 -30, 667, 66572, 1296400 -30, 674, 67362, 1327235 -26, 697, 65808, 1325768 -32, 680, 62112, 1344960 -30, 632, 67315, 1295665 -26, 685, 65689, 1310048 -28, 621, 65180, 1344951 -26, 668, 68928, 1315316 -24, 636, 65046, 1333804 -28, 616, 65536, 1333376 -28, 692, 66088, 1315072 -26, 696, 64992, 1285901 -26, 704, 64602, 1290424 -30, 724, 65728, 1350546 -23, 664, 66732, 1315376 -26, 688, 65099, 1270464 -28, 647, 64656, 1325259 -24, 647, 66664, 1287502 -20, 712, 67080, 1348608 -26, 650, 66807, 1365376 -24, 646, 65414, 1308049 -24, 645, 63111, 1324702 -28, 700, 66410, 1280960 -28, 674, 66415, 1330872 -20, 704, 65536, 1292065 -26, 684, 64560, 1298346 -27, 684, 67707, 1322756 -22, 691, 64613, 1292844 -30, 647, 66716, 1258181 -26, 672, 66756, 1318086 -26, 692, 66673, 1332342 -22, 694, 65623, 1320553 -28, 691, 66595, 1302226 -30, 632, 67685, 1289583 -22, 650, 64852, 1296906 -28, 627, 63819, 1291399 -32, 643, 64076, 1305598 -28, 650, 67461, 1338880 -25, 734, 66487, 1309223 -30, 677, 65915, 1332695 -28, 673, 65340, 1309269 -30, 614, 67870, 1334193 -24, 668, 65568, 1324434 -32, 629, 65517, 1335292 -28, 679, 66816, 1258780 -30, 690, 68018, 1331661 -30, 665, 64583, 1328110 -26, 703, 65366, 1284483 -28, 664, 67072, 1342368 -22, 655, 64601, 1328639 -32, 676, 67910, 1346684 -26, 648, 64302, 1335128 -28, 630, 65835, 1310994 -26, 654, 66997, 1318680 -26, 720, 65660, 1325616 -24, 597, 67014, 1342725 diff --git a/test/data/scores_hyperidentity.csv b/test/data/scores_hyperidentity.csv deleted file mode 100644 index c4df5ffda..000000000 --- a/test/data/scores_hyperidentity.csv +++ /dev/null @@ -1,1025 +0,0 @@ -64-64-50-64-178-50-36, 256-256-120-256-612-100-171, 512-512-150-512-1174-150-300, 1024-1024-200-1024-3000-200-600 -38, 145, 277, 553 -42, 144, 281, 556 -39, 140, 279, 544 -40, 138, 285, 541 -39, 148, 276, 542 -41, 149, 282, 539 -37, 147, 282, 546 -40, 147, 278, 549 -39, 145, 285, 541 -45, 142, 274, 538 -38, 141, 280, 541 -44, 157, 275, 544 -42, 141, 282, 540 -42, 143, 279, 543 -37, 143, 290, 548 -41, 147, 279, 550 -38, 145, 279, 541 -41, 145, 287, 555 -37, 149, 275, 543 -41, 147, 278, 544 -40, 139, 279, 558 -43, 143, 279, 548 -41, 142, 281, 542 -40, 140, 278, 544 -39, 139, 270, 544 -43, 147, 281, 553 -41, 142, 277, 563 -37, 143, 287, 541 -41, 143, 276, 548 -37, 145, 280, 546 -39, 145, 283, 550 -36, 142, 294, 538 -40, 144, 281, 549 -42, 144, 274, 545 -39, 146, 276, 547 -36, 144, 272, 555 -36, 140, 273, 544 -41, 142, 279, 548 -36, 146, 276, 548 -42, 143, 274, 554 -38, 139, 278, 546 -37, 155, 279, 549 -40, 144, 275, 555 -40, 147, 277, 547 -37, 144, 276, 554 -39, 142, 273, 538 -38, 141, 295, 545 -41, 144, 276, 545 -37, 143, 279, 545 -41, 143, 272, 550 -42, 140, 294, 547 -40, 145, 286, 580 -40, 142, 280, 542 -38, 136, 284, 546 -40, 144, 276, 550 -42, 140, 287, 546 -44, 143, 282, 534 -38, 147, 284, 553 -38, 141, 276, 549 -39, 142, 283, 553 -39, 144, 280, 545 -40, 149, 279, 560 -41, 144, 278, 544 -36, 144, 276, 538 -38, 142, 275, 537 -42, 142, 279, 543 -38, 137, 278, 538 -41, 144, 286, 545 -40, 146, 283, 549 -40, 143, 281, 552 -41, 142, 289, 534 -43, 139, 279, 552 -40, 144, 280, 548 -40, 139, 274, 546 -39, 143, 271, 540 -41, 144, 281, 541 -40, 143, 274, 546 -40, 141, 272, 557 -40, 144, 280, 538 -37, 146, 282, 549 -41, 145, 286, 547 -39, 140, 280, 550 -39, 149, 289, 559 -43, 147, 278, 546 -43, 143, 283, 544 -40, 142, 285, 558 -42, 141, 278, 540 -43, 142, 281, 559 -42, 147, 279, 549 -37, 143, 289, 546 -39, 138, 283, 549 -40, 147, 280, 561 -39, 144, 278, 543 -37, 142, 276, 546 -43, 142, 275, 545 -37, 145, 274, 539 -40, 140, 279, 541 -38, 150, 284, 546 -39, 145, 290, 540 -41, 141, 281, 538 -37, 139, 283, 547 -42, 140, 279, 540 -40, 149, 280, 552 -42, 143, 269, 542 -40, 143, 280, 541 -38, 139, 295, 542 -39, 140, 276, 543 -39, 150, 283, 542 -39, 138, 274, 537 -37, 140, 272, 546 -40, 143, 280, 543 -42, 139, 289, 549 -36, 140, 285, 538 -41, 141, 281, 555 -40, 140, 287, 554 -39, 140, 274, 544 -39, 141, 274, 547 -37, 144, 282, 558 -37, 149, 282, 545 -37, 140, 279, 574 -40, 140, 282, 561 -38, 142, 280, 551 -39, 139, 279, 538 -46, 144, 283, 538 -40, 146, 282, 555 -41, 147, 280, 541 -40, 146, 286, 540 -38, 142, 283, 544 -35, 149, 275, 550 -38, 143, 272, 548 -41, 146, 280, 555 -40, 152, 279, 551 -37, 143, 294, 544 -40, 147, 278, 560 -44, 137, 273, 550 -41, 143, 274, 543 -38, 150, 283, 545 -40, 142, 275, 554 -38, 141, 281, 544 -38, 141, 276, 549 -37, 138, 277, 568 -40, 149, 279, 560 -39, 145, 283, 545 -37, 139, 277, 548 -42, 147, 280, 539 -43, 159, 278, 545 -38, 143, 278, 548 -39, 142, 273, 551 -38, 146, 276, 544 -42, 143, 279, 543 -37, 140, 271, 554 -38, 146, 274, 543 -38, 146, 278, 542 -38, 143, 282, 545 -39, 146, 277, 551 -39, 139, 278, 543 -39, 143, 286, 553 -40, 143, 279, 544 -41, 147, 281, 548 -43, 141, 281, 544 -39, 143, 284, 544 -38, 141, 284, 548 -40, 141, 281, 550 -43, 144, 281, 547 -38, 144, 286, 551 -41, 144, 280, 545 -42, 144, 270, 553 -39, 141, 280, 545 -37, 147, 290, 555 -39, 145, 279, 542 -44, 142, 279, 546 -38, 143, 278, 553 -42, 140, 287, 548 -38, 148, 280, 546 -39, 148, 272, 538 -37, 140, 274, 553 -41, 143, 278, 547 -39, 145, 274, 542 -40, 139, 282, 539 -38, 143, 275, 542 -40, 141, 279, 550 -42, 144, 282, 549 -38, 153, 286, 541 -37, 140, 278, 545 -38, 143, 279, 555 -36, 142, 282, 547 -41, 140, 275, 547 -38, 142, 274, 545 -37, 148, 274, 550 -39, 146, 279, 548 -38, 145, 274, 549 -39, 143, 282, 539 -40, 144, 280, 551 -40, 148, 280, 565 -39, 146, 281, 549 -40, 142, 275, 569 -40, 144, 279, 565 -40, 147, 275, 545 -39, 148, 276, 542 -44, 139, 281, 547 -39, 143, 286, 543 -39, 142, 284, 550 -42, 151, 276, 535 -37, 139, 285, 545 -42, 141, 284, 543 -40, 141, 279, 546 -38, 141, 285, 536 -37, 143, 278, 542 -40, 150, 284, 556 -45, 141, 285, 541 -39, 144, 282, 548 -37, 142, 280, 554 -41, 141, 284, 545 -39, 138, 280, 544 -38, 147, 286, 543 -39, 141, 279, 544 -39, 145, 281, 547 -41, 145, 280, 547 -37, 140, 290, 545 -36, 139, 280, 540 -38, 142, 276, 544 -41, 144, 279, 545 -41, 142, 282, 548 -42, 141, 279, 547 -42, 144, 278, 548 -42, 144, 276, 535 -40, 137, 274, 549 -39, 142, 279, 543 -43, 143, 279, 538 -39, 140, 280, 563 -38, 143, 273, 552 -39, 146, 285, 555 -39, 139, 278, 542 -38, 140, 283, 541 -37, 143, 275, 537 -38, 147, 268, 549 -40, 146, 274, 542 -40, 141, 277, 552 -45, 146, 276, 542 -44, 144, 285, 542 -40, 153, 276, 540 -39, 146, 281, 542 -37, 141, 276, 561 -42, 148, 283, 550 -39, 144, 280, 547 -41, 139, 273, 537 -40, 145, 272, 540 -39, 146, 276, 538 -36, 144, 283, 548 -35, 154, 282, 555 -42, 140, 276, 547 -40, 137, 276, 537 -42, 141, 281, 548 -40, 149, 279, 551 -40, 143, 280, 547 -40, 144, 276, 536 -39, 147, 281, 549 -39, 142, 274, 545 -42, 149, 280, 547 -41, 143, 281, 542 -40, 141, 275, 552 -37, 140, 275, 542 -39, 137, 278, 543 -42, 140, 280, 538 -40, 143, 278, 537 -41, 141, 278, 551 -38, 146, 277, 554 -37, 140, 286, 545 -41, 144, 272, 544 -40, 141, 276, 546 -40, 146, 279, 544 -37, 147, 282, 549 -41, 141, 281, 544 -38, 149, 278, 546 -38, 145, 275, 542 -41, 148, 272, 549 -39, 144, 282, 543 -39, 141, 290, 551 -42, 144, 284, 546 -39, 140, 295, 544 -38, 140, 279, 543 -40, 149, 278, 551 -38, 144, 279, 542 -40, 145, 282, 553 -38, 140, 286, 549 -37, 147, 286, 541 -39, 140, 280, 546 -39, 144, 277, 540 -42, 141, 280, 557 -41, 144, 279, 541 -39, 142, 289, 551 -39, 138, 276, 541 -39, 145, 276, 553 -37, 144, 281, 557 -38, 144, 285, 544 -40, 146, 280, 546 -39, 141, 276, 547 -39, 141, 278, 551 -42, 145, 285, 546 -40, 142, 285, 548 -36, 148, 275, 541 -41, 142, 282, 543 -40, 143, 285, 552 -40, 143, 274, 554 -40, 139, 284, 552 -39, 138, 280, 541 -41, 150, 287, 545 -43, 145, 276, 536 -38, 141, 285, 552 -42, 144, 276, 552 -38, 146, 292, 548 -41, 151, 279, 554 -39, 141, 281, 549 -38, 148, 278, 546 -40, 143, 277, 539 -40, 138, 281, 541 -40, 145, 288, 545 -36, 143, 283, 545 -40, 146, 277, 540 -41, 144, 282, 550 -39, 145, 283, 543 -42, 145, 278, 543 -38, 142, 273, 552 -42, 140, 281, 543 -39, 150, 283, 560 -37, 137, 277, 545 -39, 142, 276, 544 -38, 148, 285, 556 -39, 148, 273, 537 -44, 147, 273, 551 -38, 143, 288, 538 -38, 144, 288, 542 -41, 140, 276, 554 -40, 141, 275, 550 -40, 141, 274, 542 -39, 139, 279, 546 -41, 151, 276, 541 -40, 142, 281, 548 -42, 140, 289, 558 -41, 148, 279, 552 -42, 142, 280, 564 -40, 145, 278, 550 -38, 147, 282, 571 -39, 145, 279, 558 -37, 142, 288, 551 -40, 144, 277, 551 -37, 148, 283, 545 -37, 138, 276, 551 -38, 147, 288, 550 -38, 144, 280, 550 -41, 139, 281, 544 -40, 145, 277, 538 -38, 149, 276, 556 -43, 140, 268, 548 -40, 142, 275, 556 -40, 139, 282, 548 -38, 141, 280, 562 -38, 145, 274, 547 -40, 145, 279, 547 -39, 142, 276, 553 -39, 138, 280, 558 -43, 146, 279, 546 -42, 141, 276, 541 -39, 147, 281, 545 -42, 143, 274, 545 -38, 143, 281, 542 -39, 143, 282, 544 -44, 139, 276, 551 -41, 151, 285, 540 -39, 144, 281, 543 -41, 147, 283, 554 -40, 147, 276, 562 -39, 143, 278, 552 -40, 145, 285, 559 -38, 143, 285, 554 -41, 138, 281, 557 -40, 138, 281, 554 -40, 143, 282, 539 -39, 142, 282, 552 -41, 139, 283, 537 -40, 143, 278, 543 -40, 142, 276, 541 -37, 139, 282, 550 -38, 147, 275, 550 -39, 147, 284, 545 -39, 143, 279, 539 -43, 143, 271, 547 -38, 144, 273, 543 -35, 142, 282, 574 -39, 140, 274, 540 -42, 146, 275, 555 -36, 144, 295, 539 -45, 142, 271, 541 -41, 151, 279, 541 -38, 140, 280, 560 -36, 140, 282, 549 -40, 147, 279, 543 -39, 147, 290, 553 -39, 145, 285, 548 -40, 143, 276, 560 -37, 143, 276, 548 -42, 144, 274, 544 -38, 142, 295, 540 -40, 147, 281, 549 -41, 143, 280, 556 -40, 146, 283, 553 -38, 139, 284, 551 -39, 145, 282, 534 -40, 145, 277, 540 -38, 145, 280, 556 -39, 145, 282, 551 -38, 144, 282, 551 -40, 143, 276, 544 -36, 143, 288, 542 -38, 139, 281, 559 -38, 142, 279, 543 -40, 144, 299, 542 -43, 139, 279, 542 -38, 142, 280, 538 -39, 142, 276, 547 -35, 149, 283, 554 -41, 139, 278, 543 -38, 139, 277, 540 -39, 145, 279, 554 -38, 145, 283, 546 -39, 143, 275, 544 -40, 139, 278, 540 -38, 150, 282, 551 -38, 151, 280, 545 -41, 143, 279, 551 -38, 152, 274, 545 -41, 146, 278, 538 -36, 146, 282, 554 -37, 144, 282, 543 -36, 145, 282, 542 -40, 146, 281, 552 -39, 140, 279, 541 -40, 145, 275, 546 -39, 147, 276, 547 -40, 145, 281, 548 -39, 139, 286, 548 -42, 145, 278, 547 -38, 144, 274, 540 -38, 142, 277, 548 -39, 156, 274, 542 -42, 140, 277, 536 -40, 139, 282, 544 -41, 144, 274, 542 -37, 142, 282, 551 -41, 145, 290, 553 -39, 146, 281, 541 -41, 146, 280, 553 -41, 143, 284, 542 -37, 146, 277, 566 -38, 139, 280, 545 -39, 142, 281, 548 -39, 139, 275, 544 -39, 144, 272, 549 -38, 143, 285, 541 -42, 144, 281, 546 -38, 157, 276, 552 -40, 141, 277, 545 -38, 141, 275, 548 -39, 142, 278, 539 -39, 146, 277, 542 -39, 147, 283, 551 -38, 151, 276, 549 -39, 146, 273, 544 -38, 139, 280, 549 -42, 149, 278, 550 -39, 144, 282, 543 -38, 143, 285, 549 -42, 145, 280, 563 -39, 146, 279, 548 -44, 145, 288, 539 -39, 139, 291, 546 -39, 145, 282, 550 -36, 145, 279, 553 -40, 140, 276, 546 -41, 143, 289, 550 -39, 145, 283, 553 -38, 142, 280, 546 -41, 145, 276, 542 -41, 145, 276, 550 -40, 141, 278, 543 -41, 141, 284, 554 -45, 153, 276, 539 -37, 145, 292, 539 -40, 143, 286, 551 -37, 140, 276, 545 -39, 141, 280, 561 -41, 145, 284, 571 -43, 146, 278, 559 -38, 141, 276, 561 -40, 145, 280, 547 -42, 150, 283, 541 -39, 142, 282, 551 -42, 143, 283, 551 -41, 142, 283, 551 -40, 144, 275, 541 -39, 147, 276, 544 -40, 154, 280, 555 -39, 142, 283, 560 -37, 144, 275, 532 -40, 147, 293, 550 -42, 138, 281, 541 -39, 141, 280, 538 -40, 138, 282, 543 -40, 146, 286, 550 -43, 147, 278, 544 -39, 147, 281, 546 -39, 150, 277, 561 -38, 143, 282, 540 -37, 143, 277, 544 -43, 149, 275, 565 -40, 143, 275, 551 -42, 139, 278, 550 -43, 149, 282, 567 -36, 145, 286, 552 -38, 144, 283, 553 -40, 144, 274, 552 -40, 145, 281, 538 -38, 145, 278, 546 -40, 143, 285, 551 -41, 139, 279, 559 -39, 146, 277, 546 -38, 140, 278, 545 -43, 142, 281, 545 -40, 139, 284, 552 -36, 138, 274, 554 -39, 144, 292, 550 -39, 143, 276, 555 -38, 143, 280, 549 -40, 142, 283, 539 -37, 146, 280, 540 -41, 146, 280, 553 -40, 152, 290, 552 -40, 145, 275, 546 -40, 145, 279, 543 -39, 144, 289, 545 -43, 149, 271, 547 -38, 141, 285, 537 -39, 139, 282, 552 -43, 145, 280, 546 -38, 145, 286, 539 -41, 148, 279, 553 -40, 137, 281, 548 -38, 139, 285, 538 -38, 144, 286, 541 -40, 143, 278, 540 -38, 138, 294, 544 -39, 145, 281, 541 -38, 143, 272, 546 -38, 146, 288, 560 -39, 146, 293, 542 -39, 146, 286, 542 -43, 148, 279, 552 -39, 141, 277, 540 -38, 141, 286, 548 -38, 147, 277, 548 -42, 138, 280, 550 -43, 148, 277, 543 -41, 148, 281, 550 -38, 152, 283, 550 -39, 139, 285, 553 -37, 141, 275, 556 -37, 141, 301, 542 -38, 143, 279, 540 -42, 142, 274, 553 -38, 146, 283, 545 -40, 142, 276, 540 -37, 144, 278, 546 -36, 144, 286, 547 -39, 145, 278, 559 -41, 144, 282, 559 -39, 144, 280, 541 -43, 147, 275, 556 -37, 139, 282, 542 -38, 147, 291, 545 -41, 140, 283, 540 -39, 142, 280, 547 -37, 142, 276, 545 -41, 143, 278, 546 -40, 145, 274, 543 -37, 146, 282, 547 -41, 146, 273, 546 -38, 145, 277, 542 -39, 142, 274, 548 -39, 143, 279, 536 -38, 144, 281, 543 -42, 149, 278, 549 -40, 143, 284, 540 -45, 136, 278, 552 -40, 147, 278, 540 -40, 147, 277, 549 -40, 145, 279, 554 -41, 145, 277, 548 -36, 144, 277, 544 -37, 141, 278, 544 -39, 144, 285, 544 -38, 140, 277, 542 -36, 148, 278, 549 -41, 137, 275, 551 -43, 144, 275, 541 -40, 143, 279, 542 -44, 144, 278, 547 -42, 146, 285, 548 -40, 143, 275, 549 -39, 149, 278, 548 -40, 149, 280, 546 -47, 142, 282, 556 -41, 140, 285, 546 -37, 139, 278, 547 -38, 143, 276, 549 -39, 144, 279, 551 -40, 139, 280, 539 -41, 138, 282, 540 -42, 142, 276, 549 -40, 144, 278, 555 -50, 142, 276, 540 -41, 140, 280, 546 -40, 142, 282, 569 -39, 144, 276, 544 -39, 148, 281, 548 -40, 146, 283, 551 -38, 152, 284, 543 -37, 151, 276, 541 -43, 149, 279, 549 -38, 140, 280, 552 -41, 147, 280, 551 -38, 141, 285, 543 -44, 141, 277, 553 -41, 143, 277, 540 -37, 142, 272, 547 -43, 138, 283, 545 -40, 146, 280, 542 -42, 142, 276, 550 -40, 143, 277, 551 -41, 143, 273, 553 -39, 144, 274, 535 -38, 144, 286, 537 -39, 139, 276, 555 -41, 140, 280, 542 -41, 143, 282, 547 -39, 140, 273, 555 -37, 148, 283, 552 -42, 160, 276, 543 -41, 147, 276, 552 -37, 140, 287, 555 -37, 140, 275, 544 -40, 143, 275, 542 -39, 149, 284, 558 -42, 144, 280, 550 -39, 139, 281, 554 -37, 146, 278, 545 -42, 144, 276, 541 -41, 137, 282, 546 -37, 143, 279, 539 -38, 139, 279, 542 -41, 140, 275, 544 -38, 146, 278, 545 -43, 139, 281, 538 -38, 142, 287, 545 -45, 138, 274, 548 -36, 146, 283, 559 -41, 138, 277, 539 -39, 146, 275, 548 -43, 143, 277, 562 -37, 143, 276, 544 -42, 151, 276, 543 -40, 143, 276, 540 -40, 144, 280, 543 -36, 141, 287, 547 -42, 144, 280, 536 -39, 145, 279, 546 -37, 150, 286, 551 -37, 141, 279, 551 -40, 147, 287, 550 -39, 144, 273, 542 -42, 154, 286, 538 -43, 143, 278, 541 -39, 146, 281, 543 -38, 140, 287, 540 -40, 144, 280, 555 -41, 142, 279, 544 -37, 151, 275, 553 -39, 141, 274, 550 -39, 139, 277, 552 -39, 143, 274, 547 -41, 140, 284, 557 -41, 143, 279, 545 -37, 143, 286, 550 -38, 140, 278, 550 -39, 151, 277, 547 -39, 148, 274, 544 -42, 144, 281, 547 -37, 146, 288, 543 -38, 143, 279, 555 -40, 153, 279, 552 -37, 139, 283, 549 -37, 148, 278, 551 -39, 144, 285, 543 -41, 148, 275, 536 -38, 143, 270, 559 -39, 142, 278, 543 -38, 143, 285, 534 -38, 146, 274, 545 -37, 145, 277, 562 -40, 149, 277, 555 -39, 144, 278, 545 -39, 139, 278, 537 -41, 139, 278, 554 -40, 142, 280, 557 -41, 148, 288, 547 -42, 140, 279, 544 -38, 147, 276, 538 -41, 143, 276, 539 -40, 141, 278, 549 -39, 145, 275, 544 -39, 139, 280, 541 -40, 141, 271, 541 -37, 141, 272, 545 -42, 140, 278, 551 -40, 141, 284, 539 -38, 148, 277, 549 -38, 148, 273, 543 -38, 142, 273, 539 -40, 143, 279, 534 -41, 144, 277, 541 -43, 143, 283, 546 -38, 148, 273, 542 -40, 147, 273, 549 -37, 147, 284, 538 -42, 144, 282, 545 -41, 142, 284, 552 -38, 147, 284, 543 -44, 141, 285, 554 -40, 139, 281, 538 -42, 144, 278, 543 -41, 140, 273, 548 -40, 144, 284, 539 -39, 140, 278, 540 -43, 143, 279, 554 -37, 144, 293, 541 -40, 149, 290, 547 -38, 151, 276, 551 -39, 151, 285, 549 -38, 146, 272, 540 -40, 141, 281, 557 -40, 145, 286, 547 -37, 142, 286, 551 -40, 143, 283, 551 -39, 143, 277, 543 -39, 142, 288, 542 -40, 146, 282, 546 -37, 145, 280, 557 -38, 144, 279, 549 -41, 144, 282, 541 -39, 145, 280, 546 -41, 146, 281, 545 -39, 146, 284, 541 -43, 144, 277, 551 -39, 150, 285, 567 -42, 145, 289, 541 -39, 147, 279, 538 -42, 144, 282, 556 -41, 144, 277, 539 -41, 143, 278, 539 -41, 141, 277, 541 -40, 151, 278, 547 -37, 140, 280, 555 -38, 144, 278, 550 -42, 142, 300, 545 -43, 143, 275, 547 -37, 144, 278, 548 -44, 144, 295, 541 -40, 146, 286, 558 -40, 144, 273, 547 -41, 146, 282, 568 -40, 144, 273, 545 -37, 141, 277, 552 -38, 140, 277, 548 -37, 141, 279, 538 -38, 152, 273, 540 -37, 146, 277, 545 -39, 142, 279, 548 -39, 142, 270, 540 -38, 145, 273, 546 -40, 147, 280, 541 -38, 140, 283, 539 -38, 140, 288, 553 -40, 150, 274, 543 -41, 144, 287, 541 -38, 139, 283, 545 -40, 140, 282, 554 -38, 143, 281, 540 -41, 146, 283, 554 -41, 148, 277, 548 -41, 142, 277, 542 -38, 150, 278, 556 -38, 144, 283, 545 -39, 142, 291, 552 -37, 143, 284, 542 -40, 147, 270, 569 -39, 139, 289, 547 -41, 150, 278, 561 -41, 140, 272, 556 -40, 140, 274, 546 -40, 144, 275, 550 -40, 140, 275, 538 -41, 148, 283, 539 -42, 139, 273, 538 -38, 145, 276, 551 -38, 146, 270, 564 -40, 138, 282, 548 -44, 143, 289, 535 -40, 146, 281, 552 -41, 141, 276, 550 -36, 147, 282, 556 -38, 146, 278, 545 -41, 147, 293, 544 -37, 138, 273, 547 -41, 143, 279, 557 -37, 142, 273, 547 -38, 142, 288, 550 -41, 141, 279, 541 -47, 146, 277, 547 -36, 145, 276, 539 -39, 140, 284, 539 -38, 149, 280, 546 -45, 141, 271, 545 -43, 144, 276, 558 -42, 140, 277, 548 -38, 150, 281, 545 -40, 140, 276, 550 -39, 146, 287, 557 -39, 143, 282, 543 -40, 147, 281, 547 -39, 141, 294, 540 -43, 146, 278, 546 -39, 139, 275, 541 -42, 148, 289, 547 -40, 142, 271, 543 -36, 143, 279, 548 -38, 147, 275, 548 -38, 139, 280, 544 -43, 140, 274, 560 -43, 154, 280, 541 -37, 141, 281, 546 -42, 143, 286, 546 -38, 142, 278, 545 -41, 148, 275, 550 -41, 141, 279, 550 -40, 142, 278, 545 -41, 143, 277, 546 -41, 147, 278, 539 -38, 142, 278, 551 -40, 137, 274, 547 -40, 141, 280, 537 -38, 141, 285, 555 -37, 143, 294, 547 -43, 143, 280, 551 -35, 143, 280, 547 -37, 137, 272, 544 -40, 139, 277, 561 -43, 146, 279, 549 -41, 149, 275, 552 -35, 142, 273, 547 -38, 143, 281, 552 -40, 139, 280, 562 -37, 150, 282, 543 -41, 146, 280, 547 -38, 144, 280, 537 -40, 140, 272, 557 -40, 145, 288, 540 -38, 145, 281, 550 -40, 144, 277, 546 -39, 147, 279, 548 -36, 141, 289, 558 -41, 141, 284, 551 -39, 140, 277, 547 -42, 145, 273, 552 -40, 142, 274, 545 -37, 144, 279, 538 -40, 140, 281, 552 -39, 148, 277, 541 -40, 152, 287, 548 -43, 155, 277, 543 -37, 153, 284, 548 -43, 143, 288, 545 -41, 140, 282, 546 -38, 145, 280, 536 -37, 145, 275, 548 -38, 143, 279, 547 -39, 138, 281, 547 -37, 140, 275, 554 -36, 148, 279, 539 -39, 139, 279, 541 -41, 142, 284, 535 -38, 145, 279, 540 -38, 145, 278, 554 -39, 148, 277, 540 -40, 139, 273, 540 -39, 139, 283, 549 -39, 139, 284, 546 -40, 143, 277, 557 -39, 138, 281, 543 -36, 139, 277, 552 -40, 142, 277, 551 -41, 144, 280, 541 -39, 147, 279, 564 -43, 144, 282, 564 -40, 142, 287, 539 -38, 143, 290, 542 -41, 144, 274, 541 -41, 145, 283, 547 -41, 143, 273, 560 -39, 140, 280, 540 -37, 143, 276, 540 -37, 148, 282, 539 -39, 150, 279, 558 -40, 141, 280, 548 -41, 142, 274, 539 -41, 145, 276, 544 -43, 149, 271, 542 -38, 143, 276, 552 -37, 148, 276, 541 -38, 147, 287, 534 -40, 145, 276, 560 -41, 146, 275, 551 -36, 143, 275, 536 -41, 144, 281, 550 -39, 142, 285, 548 -38, 148, 279, 542 -40, 140, 278, 556 -38, 146, 283, 549 -40, 142, 281, 540 -40, 144, 275, 544 -38, 141, 282, 548 -40, 142, 283, 546 -38, 140, 286, 548 -40, 142, 279, 545 -35, 142, 271, 535 -40, 142, 273, 549 -39, 145, 277, 549 -39, 141, 278, 543 -37, 143, 279, 537 -39, 143, 277, 548 -40, 143, 278, 549 -38, 144, 276, 542 -41, 142, 283, 544 -38, 145, 277, 550 -41, 142, 280, 554 -40, 141, 281, 537 -39, 145, 284, 541 -41, 142, 276, 539 -37, 145, 275, 546 -39, 145, 285, 544 -41, 143, 276, 545 -39, 142, 275, 548 -43, 140, 277, 544 -42, 142, 284, 538 -38, 144, 284, 551 -40, 143, 281, 553 -37, 144, 277, 545 -40, 139, 277, 538 -42, 142, 276, 558 -38, 141, 282, 540 -37, 144, 282, 540 -42, 143, 278, 540 -40, 143, 278, 552 -36, 147, 282, 550 -38, 141, 281, 559 -39, 142, 280, 537 -37, 148, 284, 553 -40, 141, 281, 550 -40, 144, 274, 547 -39, 140, 279, 546 -38, 142, 277, 542 -41, 148, 279, 544 -39, 140, 277, 544 -40, 145, 282, 550 -37, 142, 280, 537 -42, 144, 273, 545 -40, 144, 272, 544 -40, 142, 280, 554 -40, 139, 284, 558 -36, 146, 281, 541 -40, 148, 295, 539 -41, 139, 275, 548 -45, 145, 289, 551 -41, 142, 279, 544 -40, 141, 274, 541 -42, 144, 277, 545 -44, 143, 275, 540 -38, 150, 275, 547 -41, 150, 282, 534 -40, 140, 281, 545 -37, 143, 277, 550 -40, 143, 277, 547 -39, 146, 280, 547 -38, 147, 275, 536 -37, 148, 284, 539 -41, 142, 281, 535 -37, 142, 279, 562 -40, 142, 275, 547 -43, 146, 283, 552 -45, 138, 285, 553 -42, 141, 282, 541 -39, 146, 272, 537 -43, 139, 274, 541 -38, 142, 278, 549 -42, 142, 278, 552 -38, 147, 285, 539 -39, 140, 273, 540 -40, 142, 275, 546 -38, 149, 289, 548 -43, 139, 283, 541 -41, 145, 287, 546 -39, 143, 277, 547 -42, 142, 273, 544 -41, 144, 285, 560 -38, 142, 278, 550 -37, 145, 273, 560 diff --git a/test/execution_fees.cpp b/test/execution_fees.cpp deleted file mode 100644 index 6fdf2cbd1..000000000 --- a/test/execution_fees.cpp +++ /dev/null @@ -1,375 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" -#include "../src/ticking/execution_fee_report_collector.h" -#include "../src/contract_core/execution_time_accumulator.h" - -// Helper to create a valid baseline test transaction with given entries -static Transaction* createTestTransaction(unsigned char* buffer, size_t bufferSize, - unsigned int numEntries, - const unsigned int* contractIndices, - const long long* executionFees) -{ - unsigned int alignmentPadding = (numEntries % 2 == 1) ? sizeof(unsigned int) : 0; - const unsigned int inputSize = sizeof(unsigned int) + sizeof(unsigned int) + - (numEntries * sizeof(unsigned int)) + alignmentPadding + - (numEntries * sizeof(long long)) + - sizeof(m256i); - - if (sizeof(Transaction) + inputSize > bufferSize) - { - return nullptr; - } - - Transaction* tx = (Transaction*)buffer; - tx->sourcePublicKey = m256i::zero(); - tx->destinationPublicKey = m256i::zero(); - tx->amount = 0; - tx->tick = 1000; - tx->inputType = EXECUTION_FEE_REPORT_INPUT_TYPE; - tx->inputSize = inputSize; - - unsigned char* inputPtr = tx->inputPtr(); - *(unsigned int*)inputPtr = 5; // phaseNumber - *(unsigned int*)(inputPtr + 4) = numEntries; - - unsigned int* txIndices = (unsigned int*)(inputPtr + 8); - for (unsigned int i = 0; i < numEntries; i++) - { - txIndices[i] = contractIndices[i]; - } - - long long* txFees = (long long*)(inputPtr + 8 + (numEntries * sizeof(unsigned int)) + alignmentPadding); - for (unsigned int i = 0; i < numEntries; i++) - { - txFees[i] = executionFees[i]; - } - - m256i* dataLock = (m256i*)(inputPtr + 8 + (numEntries * sizeof(unsigned int)) + alignmentPadding + (numEntries * sizeof(long long))); - *dataLock = m256i::zero(); - - return tx; -} - -TEST(ExecutionFeeReportCollector, InitAndStore) -{ - ExecutionFeeReportCollector collector; - collector.init(); - - collector.storeReport(2, 0, 1000); - collector.storeReport(2, 1, 2000); - collector.storeReport(1, 0, 5000); - collector.storeReport(1, 500, 6000); - - const unsigned long long* reports = collector.getReportsForContract(2); - ASSERT_NE(reports, nullptr); - EXPECT_EQ(reports[0], 1000); - EXPECT_EQ(reports[1], 2000); - - reports = collector.getReportsForContract(1); - ASSERT_NE(reports, nullptr); - EXPECT_EQ(reports[0], 5000); - EXPECT_EQ(reports[500], 6000); -} - -TEST(ExecutionFeeReportCollector, Reset) -{ - ExecutionFeeReportCollector collector; - collector.init(); - - for (unsigned int i = 0; i < 10; i++) { - collector.storeReport(1, i, i * 1000); - } - - const unsigned long long* reports = collector.getReportsForContract(1); - EXPECT_EQ(reports[5], 5000); - - collector.reset(); - - reports = collector.getReportsForContract(1); - EXPECT_EQ(reports[5], 0); -} - -TEST(ExecutionFeeReportCollector, BoundaryValidation) -{ - ExecutionFeeReportCollector collector; - collector.init(); - - collector.storeReport(1, 0, 100); - collector.storeReport(contractCount - 1, NUMBER_OF_COMPUTORS - 1, 200); - - collector.storeReport(contractCount, 0, 100); - collector.storeReport(1, NUMBER_OF_COMPUTORS, 100); - - const unsigned long long* reports = collector.getReportsForContract(1); - EXPECT_EQ(reports[0], 100); - - reports = collector.getReportsForContract(contractCount - 1); - EXPECT_EQ(reports[NUMBER_OF_COMPUTORS - 1], 200); -} - -TEST(ExecutionFeeReportTransaction, ParseValidTransaction) -{ - unsigned char buffer[512]; - unsigned int contractIndices[2] = {2, 1}; - long long executionFees[2] = {1000, 2000}; - - Transaction* tx = createTestTransaction(buffer, sizeof(buffer), 2, contractIndices, executionFees); - ASSERT_NE(tx, nullptr); - - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidExecutionFeeReport(tx)); - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); - EXPECT_EQ(ExecutionFeeReportTransactionPrefix::getNumEntries(tx), 2u); - - const unsigned int* parsedIndices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); - const unsigned long long* parsedFees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); - EXPECT_EQ(parsedIndices[0], 2u); - EXPECT_EQ(parsedFees[0], 1000); - EXPECT_EQ(parsedIndices[1], 1u); - EXPECT_EQ(parsedFees[1], 2000); -} - -TEST(ExecutionFeeReportTransaction, RejectNonZeroAmount) { - unsigned char buffer[512]; - unsigned int contractIndices[1] = {1}; - long long executionFees[1] = {1000}; - - Transaction* tx = createTestTransaction(buffer, sizeof(buffer), 1, contractIndices, executionFees); - ASSERT_NE(tx, nullptr); - - // Valid initially - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidExecutionFeeReport(tx)); - - // Make amount non-zero (execution fee reports must have amount = 0) - tx->amount = 100; - - // Should now be invalid - EXPECT_FALSE(ExecutionFeeReportTransactionPrefix::isValidExecutionFeeReport(tx)); -} - -TEST(ExecutionFeeReportTransaction, RejectMisalignedEntries) { - unsigned char buffer[512]; - unsigned int contractIndices[1] = {1}; - long long executionFees[1] = {1000}; - - Transaction* tx = createTestTransaction(buffer, sizeof(buffer), 1, contractIndices, executionFees); - ASSERT_NE(tx, nullptr); - - // Valid initially - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); - - // Break alignment by adding 1 byte to inputSize - // Payload size will no longer match expected size for numEntries - tx->inputSize += 1; - - // Should now have invalid alignment - EXPECT_FALSE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); -} - -TEST(ExecutionFeeReportCollector, ValidateReportEntries) { - ExecutionFeeReportCollector collector; - collector.init(); - - // Valid entries - unsigned int validIndices[2] = {1, 2}; - unsigned long long validFees[2] = {1000, 2000}; - EXPECT_TRUE(collector.validateReportEntries(validIndices, validFees, 2)); - - // Invalid: contractIndex >= contractCount - unsigned int invalidContractIndices[1] = {contractCount}; - unsigned long long invalidContractFees[1] = {1000}; - EXPECT_FALSE(collector.validateReportEntries(invalidContractIndices, invalidContractFees, 1)); - - // Invalid: executionFee <= 0 - unsigned int zeroFeeIndices[1] = {1}; - unsigned long long zeroFees[1] = {0}; - EXPECT_FALSE(collector.validateReportEntries(zeroFeeIndices, zeroFees, 1)); - - // Invalid: one good entry, one bad - unsigned int mixedIndices[2] = {1, contractCount + 5}; - unsigned long long mixedFees[2] = {1000, 2000}; - EXPECT_FALSE(collector.validateReportEntries(mixedIndices, mixedFees, 2)); -} - -TEST(ExecutionFeeReportCollector, StoreReportEntries) { - ExecutionFeeReportCollector collector; - collector.init(); - - unsigned int contractIndices[3] = {1, 2, 5}; - unsigned long long executionFees[3] = {1000, 3000, 7000}; - - unsigned int computorIndex = 10; - collector.storeReportEntries(contractIndices, executionFees, 3, computorIndex); - - // Verify entries were stored at correct positions - const unsigned long long* reports1 = collector.getReportsForContract(1); - EXPECT_EQ(reports1[computorIndex], 1000); - - const unsigned long long* reports2 = collector.getReportsForContract(2); - EXPECT_EQ(reports2[computorIndex], 3000); - - const unsigned long long* reports5 = collector.getReportsForContract(5); - EXPECT_EQ(reports5[computorIndex], 7000); - - // Verify other positions remain zero - EXPECT_EQ(reports1[0], 0); - EXPECT_EQ(reports1[computorIndex + 1], 0); -} - -TEST(ExecutionFeeReportCollector, MultipleComputorsReporting) { - ExecutionFeeReportCollector collector; - collector.init(); - - // Computor 0 reports for contracts 3 and 1 - unsigned int comp0Indices[2] = {3, 1}; - unsigned long long comp0Fees[2] = {1000, 2000}; - collector.storeReportEntries(comp0Indices, comp0Fees, /*numEntries=*/2, /*computorIndex=*/0); - - // Computor 5 reports for contracts 3 and 2 (different fee for contract 3) - unsigned int comp5Indices[2] = {3, 2}; - unsigned long long comp5Fees[2] = {1500, 3000}; - collector.storeReportEntries(comp5Indices, comp5Fees, /*numEntries=*/2, /*computorIndex=*/5); - - // Computor 10 reports for contract 1 (different fee than computor 0) - unsigned int comp10Indices[1] = {1}; - unsigned long long comp10Fees[1] = {2500}; - collector.storeReportEntries(comp10Indices, comp10Fees, /*numEntries=*/1, /*computorIndex=*/10); - - // Verify contract 3 has reports from computors 0 and 5 - const unsigned long long* reports3 = collector.getReportsForContract(3); - EXPECT_EQ(reports3[0], 1000); - EXPECT_EQ(reports3[5], 1500); - EXPECT_EQ(reports3[10], 0); // Computor 10 didn't report for contract 3 - - // Verify contract 1 has reports from computors 0 and 10 - const unsigned long long* reports1 = collector.getReportsForContract(1); - EXPECT_EQ(reports1[0], 2000); - EXPECT_EQ(reports1[5], 0); // Computor 5 didn't report for contract 1 - EXPECT_EQ(reports1[10], 2500); - - // Verify contract 2 has report only from computor 5 - const unsigned long long* reports2 = collector.getReportsForContract(2); - EXPECT_EQ(reports2[0], 0); - EXPECT_EQ(reports2[5], 3000); - EXPECT_EQ(reports2[10], 0); -} - -TEST(ExecutionFeeReportBuilder, BuildAndParseEvenEntries) { - ExecutionFeeReportPayload payload; - unsigned long long contractTimes[contractCount] = {0}; - contractTimes[1] = 200; - contractTimes[3] = 100; - - unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 5, 1, 1); - EXPECT_EQ(entryCount, 2u); - - // Verify transaction is valid and parseable - Transaction* tx = (Transaction*)&payload; - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); - EXPECT_EQ(ExecutionFeeReportTransactionPrefix::getNumEntries(tx), 2u); - - const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); - const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); - EXPECT_EQ(indices[0], 1u); - EXPECT_EQ(fees[0], 200); // (200 * 1) / 1 - EXPECT_EQ(indices[1], 3u); - EXPECT_EQ(fees[1], 100); // (100 * 1) / 1 -} - -TEST(ExecutionFeeReportBuilder, BuildAndParseOddEntries) { - ExecutionFeeReportPayload payload; - unsigned long long contractTimes[contractCount] = {0}; - contractTimes[1] = 100; - contractTimes[2] = 300; - contractTimes[5] = 600; - - unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 10, 1, 1); - EXPECT_EQ(entryCount, 3u); - - // Verify transaction is valid and parseable (with alignment padding) - Transaction* tx = (Transaction*)&payload; - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); - EXPECT_EQ(ExecutionFeeReportTransactionPrefix::getNumEntries(tx), 3u); - - const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); - const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); - EXPECT_EQ(indices[0], 1u); - EXPECT_EQ(fees[0], 100); // (100 * 1) / 1 - EXPECT_EQ(indices[1], 2u); - EXPECT_EQ(fees[1], 300); // (300 * 1) / 1 - EXPECT_EQ(indices[2], 5u); - EXPECT_EQ(fees[2], 600); // (600 * 1) / 1 -} - -TEST(ExecutionFeeReportBuilder, NoEntriesReturnsZero) { - ExecutionFeeReportPayload payload; - unsigned long long contractTimes[contractCount] = {0}; - - unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 7, 1, 1); - EXPECT_EQ(entryCount, 0u); -} - -TEST(ExecutionFeeReportBuilder, BuildWithDivisionMultiplier) { - ExecutionFeeReportPayload payload; - unsigned long long contractTimes[contractCount] = {0}; - contractTimes[1] = 5; // Will become 0 after (5 * 1) / 10 - should be excluded - contractTimes[2] = 25; // Will become 2 after (25 * 1) / 10 - contractTimes[3] = 100; // Will become 10 after (100 * 1) / 10 - - unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 5, 1, 10); - - // Only contracts with non-zero fees after division should be included - EXPECT_EQ(entryCount, 2u); // contracts 0 and 2 (contract 1 becomes 0) - - Transaction* tx = (Transaction*)&payload; - EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); - - const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); - const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); - EXPECT_EQ(indices[0], 2u); - EXPECT_EQ(fees[0], 2); // (100 * 1) / 10 - EXPECT_EQ(indices[1], 3u); - EXPECT_EQ(fees[1], 10); // (25 * 1) / 10 -} - -TEST(ExecutionFeeReportBuilder, BuildWithMultiplicationMultiplier) { - ExecutionFeeReportPayload payload; - unsigned long long contractTimes[contractCount] = {0}; - contractTimes[1] = 10; - contractTimes[3] = 25; - - unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 8, 100, 1); - EXPECT_EQ(entryCount, 2u); - - const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices((Transaction*)&payload); - const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees((Transaction*)&payload); - EXPECT_EQ(indices[0], 1u); - EXPECT_EQ(fees[0], 1000); // (10 * 100) / 1 - EXPECT_EQ(indices[1], 3u); - EXPECT_EQ(fees[1], 2500); // (25 * 100) / 1 -} - -TEST(ExecutionTimeAccumulatorTest, AddingAndPhaseSwitching) -{ - ExecutionTimeAccumulator accum; - - accum.init(); - frequency = 0; // Bypass microseconds conversion - - accum.addTime(/*contractIndex=*/0, /*time=*/52784); - accum.addTime(/*contractIndex=*/contractCount/2, /*time=*/8795); - - accum.startNewAccumulation(); - - const unsigned long long* prevPhaseTimes = accum.getPrevPhaseAccumulatedTimes(); - - for (unsigned int c = 0; c < contractCount; ++c) - { - if (c == 0) - EXPECT_EQ(prevPhaseTimes[c], 52784); - else if (c == contractCount / 2) - EXPECT_EQ(prevPhaseTimes[c], 8795); - else - EXPECT_EQ(prevPhaseTimes[c], 0); - } -} diff --git a/test/file_io.cpp b/test/file_io.cpp deleted file mode 100644 index 316d132e1..000000000 --- a/test/file_io.cpp +++ /dev/null @@ -1,684 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - - -#include -#include -#include -#include -#include - -#include "../src/platform/file_io.h" - -static constexpr unsigned long long THREAD_COUNT = 4; -static constexpr unsigned long long MEM_BUFFER_SIZE = 52ULL * 1024ULL * 1024ULL; -std::mutex gMessageLock; - -// For testing the scheduler save file -struct FragmentData -{ - // Reserve memories - unsigned int memBuffer[MEM_BUFFER_SIZE]; - - // Randomly parition of data for writing - unsigned long long dataPos[4][2]; -}; - -static std::vector threadData; -static FragmentData buffer; -static unsigned char threadFinish[THREAD_COUNT]; - -inline static unsigned int random(const unsigned int range) -{ - unsigned int value; - _rdrand32_step(&value); - - return value % range; -} - -inline static unsigned long long random64(const unsigned long long range) -{ - unsigned long long value; - _rdrand64_step(&value); - - return (value % range); -} - -class FileSystemWrapper -{ -public: - FileSystemWrapper() - { - initFilesystem(); - registerAsynFileIO(NULL); - } - ~FileSystemWrapper() - { - deInitFileSystem(); - } - - bool initTestData() - { - memset(threadFinish, 1, THREAD_COUNT); - - threadData.resize(THREAD_COUNT); - for (int id = 0; id < THREAD_COUNT; id++) - { - // randomly generate a chunk of data - for (unsigned long long i = 0; i < MEM_BUFFER_SIZE; i++) - { - threadData[id].memBuffer[i] = random(4096) * ((int)i + 1); - } - // Randomly pick some part of data for writing out - unsigned long long remainedData = MEM_BUFFER_SIZE; - for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) - { - // Start of the data - threadData[id].dataPos[i][0] = random64(MEM_BUFFER_SIZE - 1); - - // Size of the data. Make sure we limit all small files in size of total MEM_BUFFER_SIZE - unsigned long long dataSize = random64(MEM_BUFFER_SIZE - threadData[id].dataPos[i][0]); - dataSize = dataSize > remainedData ? remainedData : dataSize; - remainedData = remainedData - dataSize; - - if (dataSize == 0) - { - dataSize = 1; - } - - threadData[id].dataPos[i][1] = dataSize; - } - } - return true; - } -}; - -static FileSystemWrapper fileSystem; - -long long loadFile(CHAR16* fileName, unsigned long long totalSize, char* buffer) -{ - FILE* file = nullptr; - if (_wfopen_s(&file, fileName, L"rb") != 0 || !file) - { - wprintf(L"Error opening file %s!\n", fileName); - return -1; - } - if (fread(buffer, 1, totalSize, file) != totalSize) - { - wprintf(L"Error reading %llu bytes from %s!\n", totalSize, fileName); - return -1; - } - fclose(file); - return totalSize; -} - -long long saveFile(CHAR16* fileName, unsigned long long totalSize, const char* buffer) -{ - FILE* file = nullptr; - if (_wfopen_s(&file, fileName, L"wb") != 0 || !file) - { - wprintf(L"Error opening file %s!\n", fileName); - return -1; - } - if (fwrite(buffer, 1, totalSize, file) != totalSize) - { - wprintf(L"Error saving %llu bytes from %s!\n", totalSize, fileName); - return -1; - } - fclose(file); - return totalSize; -} - -bool runAsyncSaveFile(int id, bool blocking = true, bool largeFile = false) -{ - bool sts = true; - CHAR16 fileName[32]; - setText(fileName, L"tmp_file_"); - appendNumber(fileName, id, false); - - if (largeFile) - { - long long sts = asyncSaveLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[0]), NULL, false, blocking); - if (sts <= 0) - { - std::lock_guard lock(gMessageLock); - std::cout << "runAsyncSaveFile::saveFile failed with size " << MEM_BUFFER_SIZE * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - sts = false; - } - } - else - { - // Try to save the files - for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) - { - unsigned long long dataStart = threadData[id].dataPos[i][0]; - unsigned long long dataCount = threadData[id].dataPos[i][1]; - - CHAR16 partionFileName[256]; - setText(partionFileName, fileName); - appendText(partionFileName, L"."); - appendNumber(partionFileName, i, false); - - long long sts = asyncSave(partionFileName, dataCount * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[dataStart]), NULL, blocking); - if (sts <= 0) - { - std::lock_guard lock(gMessageLock); - std::cout << "runAsyncSaveFile::saveFile failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - sts = false; - break; - } - } - } - - threadFinish[id] = 1; - return sts; -} - -bool prepareAsyncLoadFile(bool largeFile = false) -{ - bool sts = true; - fileSystem.initTestData(); - - for (int id = 0; id < THREAD_COUNT; id++) - { - CHAR16 fileName[32]; - setText(fileName, L"tmp_file_"); - appendNumber(fileName, id, false); - - if (largeFile) - { - long long sts = saveLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[0]), NULL, false); - if (sts <= 0) - { - std::lock_guard lock(gMessageLock); - std::cout << "prepareAsyncLoadFile::saveFile failed with size " << MEM_BUFFER_SIZE * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - sts = false; - } - } - else - { - // Try to save the files - for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) - { - unsigned long long dataStart = threadData[id].dataPos[i][0]; - unsigned long long dataCount = threadData[id].dataPos[i][1]; - - CHAR16 partionFileName[256]; - setText(partionFileName, fileName); - appendText(partionFileName, L"."); - appendNumber(partionFileName, i, false); - - long long sts = save(partionFileName, dataCount * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[dataStart]), NULL); - if (sts <= 0) - { - std::lock_guard lock(gMessageLock); - std::cout << "prepareAsyncLoadFile::saveFile failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - sts = false; - break; - } - } - } - } - return sts; -} - -bool runAsyncLoadFile(int id, bool largeFile = false) -{ - bool sts = true; - CHAR16 fileName[32]; - setText(fileName, L"tmp_file_"); - appendNumber(fileName, id, false); - - if (largeFile) - { - long long sts = asyncLoadLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[0]), NULL); - if (sts <= 0) - { - std::lock_guard lock(gMessageLock); - std::cout << "runAsyncLoadFile::loadFile failed with size " << MEM_BUFFER_SIZE * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - sts = false; - } - } - else - { - // Try to load the files - for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) - { - unsigned long long dataStart = threadData[id].dataPos[i][0]; - unsigned long long dataCount = threadData[id].dataPos[i][1]; - - CHAR16 partionFileName[256]; - setText(partionFileName, fileName); - appendText(partionFileName, L"."); - appendNumber(partionFileName, i, false); - - long long sts = asyncLoad(partionFileName, dataCount * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[dataStart]), NULL); - if (sts <= 0) - { - std::lock_guard lock(gMessageLock); - std::cout << "runAsyncLoadFile::loadFile failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - sts = false; - break; - } - } - } - - threadFinish[id] = 1; - return sts; -} - - -bool verifyResult(int id, bool largeFile = false) -{ - bool testPass = false; - CHAR16 fileName[32]; - setText(fileName, L"tmp_file_"); - appendNumber(fileName, id, false); - - if (largeFile) - { - long long sts = loadLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)buffer.memBuffer, NULL); - unsigned char* originalData = (unsigned char*)&(threadData[id].memBuffer[0]); - unsigned char* loadedData = (unsigned char*)&(buffer.memBuffer[0]); - int result = memcmp(originalData, loadedData, MEM_BUFFER_SIZE * sizeof(unsigned int)); - testPass = (result == 0); - } - else - { - int matchCount = 0; - int numberOfFiles = sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); - for (int i = 0; i < numberOfFiles; i++) - { - unsigned long long dataStart = threadData[id].dataPos[i][0]; - unsigned long long dataCount = threadData[id].dataPos[i][1]; - - CHAR16 partionFileName[256]; - setText(partionFileName, fileName); - appendText(partionFileName, L"."); - appendNumber(partionFileName, i, false); - - long long sts = loadFile(partionFileName, dataCount * sizeof(unsigned int), (char*)buffer.memBuffer); - - if (sts != dataCount * sizeof(unsigned int)) - { - std::cout << "verifyResult failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; - return false; - } - - unsigned char* originalData = (unsigned char*)&(threadData[id].memBuffer[dataStart]); - unsigned char* loadedData = (unsigned char*)&(buffer.memBuffer[0]); - - int result = memcmp(originalData, loadedData, dataCount * sizeof(unsigned int)); - if (result == 0) - { - matchCount++; - } - } - testPass = (matchCount == numberOfFiles); - } - - return testPass; -} - -int runTestAsyncSaveFile(bool blocking, bool largeFile, bool limitItem) -{ - fileSystem.initTestData(); - - // Run the test - std::vector> threadVec(THREAD_COUNT); - for (int i = 0; i < THREAD_COUNT; i++) - { - threadFinish[i] = 0; - threadVec[i].reset(new std::thread(runAsyncSaveFile, i, blocking, largeFile)); - } - - auto startTime = std::chrono::high_resolution_clock::now(); - int readyCount = 0; - while (readyCount < THREAD_COUNT) - { - // Don't flush right away. Wait sometimes for simulate - if (limitItem) - { - flushAsyncFileIOBuffer(2); - } - else - { - unsigned long long waitingTimeInMs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); - if (waitingTimeInMs > 1000) - { - startTime = std::chrono::high_resolution_clock::now(); - flushAsyncFileIOBuffer(); - } - } - - readyCount = 0; - for (int i = 0; i < THREAD_COUNT; i++) - { - char readyFlag = 0; - readyFlag = threadFinish[i]; - if (readyFlag) - { - readyCount++; - } - } - } - - for (int i = 0; i < THREAD_COUNT; i++) - { - if (threadVec[i]->joinable()) - { - threadVec[i]->join(); - } - } - - // Non blocking, need to flush all data - if (!blocking) - { - flushAsyncFileIOBuffer(); - } - - // Verify result - int testPass = 0; - for (int i = 0; i < THREAD_COUNT; i++) - { - if (verifyResult(i, largeFile)) - { - testPass++; - } - } - return testPass; -} - - -int runTestAsyncLoadFile(bool blocking, bool largeFile, bool limitItem) -{ - prepareAsyncLoadFile(largeFile); - - // Run the test - std::vector> threadVec(THREAD_COUNT); - for (int i = 0; i < THREAD_COUNT; i++) - { - threadFinish[i] = 0; - threadVec[i].reset(new std::thread(runAsyncLoadFile, i, largeFile)); - } - auto startTime = std::chrono::high_resolution_clock::now(); - int readyCount = 0; - while (readyCount < THREAD_COUNT) - { - // Don't flush right away. Wait sometimes for simulate - if (limitItem) - { - flushAsyncFileIOBuffer(2); - } - else - { - unsigned long long waitingTimeInMs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); - if (waitingTimeInMs > 1000) - { - startTime = std::chrono::high_resolution_clock::now(); - flushAsyncFileIOBuffer(); - } - } - - readyCount = 0; - for (int i = 0; i < THREAD_COUNT; i++) - { - char readyFlag = 0; - readyFlag = threadFinish[i]; - if (readyFlag) - { - readyCount++; - } - } - } - - for (int i = 0; i < THREAD_COUNT; i++) - { - if (threadVec[i]->joinable()) - { - threadVec[i]->join(); - } - } - - // Verify result - int testPass = 0; - for (int i = 0; i < THREAD_COUNT; i++) - { - if (verifyResult(i, largeFile)) - { - testPass++; - } - } - return testPass; -} - -TEST(TestAsyncFileIO, AsyncNonBlockingSaveFile) -{ - if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) - { - EXPECT_EQ(runTestAsyncSaveFile(false, false, false), THREAD_COUNT); - } - else - { - EXPECT_TRUE(true); - } -} - -TEST(TestAsyncFileIO, AsyncNonBlockingSaveLargeFile) -{ - if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) - { - EXPECT_EQ(runTestAsyncSaveFile(false, true, false), THREAD_COUNT); - } - else - { - EXPECT_TRUE(true); - } -} - -TEST(TestAsyncFileIO, AsyncNonBlockingSaveFileWithLimitItems) -{ - if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) - { - EXPECT_EQ(runTestAsyncSaveFile(false, false, true), THREAD_COUNT); - } - else - { - EXPECT_TRUE(true); - } -} - -TEST(TestAsyncFileIO, AsyncNonBlockingSaveLargeFileWithLimitItems) -{ - if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) - { - EXPECT_EQ(runTestAsyncSaveFile(false, true, true), THREAD_COUNT); - } - else - { - EXPECT_TRUE(true); - } -} - - -TEST(TestAsyncFileIO, AsyncSaveFile) -{ - EXPECT_EQ(runTestAsyncSaveFile(true, false, false), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncSaveLargeFile) -{ - EXPECT_EQ(runTestAsyncSaveFile(true, true, false), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncSaveFileWithLimitItems) -{ - EXPECT_EQ(runTestAsyncSaveFile(true, false, true), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncSaveLargeFileWithLimitItems) -{ - EXPECT_EQ(runTestAsyncSaveFile(true, true, true), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncLoadFile) -{ - EXPECT_EQ(runTestAsyncLoadFile(true, false, false), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncLoadLargeFile) -{ - EXPECT_EQ(runTestAsyncLoadFile(true, true, false), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncLoadFileWithLimitItems) -{ - EXPECT_EQ(runTestAsyncLoadFile(true, false, true), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, AsyncLoadLargeFileWithLimitItems) -{ - EXPECT_EQ(runTestAsyncLoadFile(true, true, true), THREAD_COUNT); -} - -TEST(TestAsyncFileIO, FindKLargest) -{ - constexpr int NUMBER_OF_ELEMENTS = 2025; - constexpr int K_NUMBER = 245; - - PairStruct priorityArray[NUMBER_OF_ELEMENTS]; - - // Generate the elements - for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) - { - priorityArray[i]._key = i; - priorityArray[i]._value = random(NUMBER_OF_ELEMENTS); - } - - // Find K largest in priorityArray - findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); - - // Comparison. Expect all K left items are greater than remained items - for (int i = 0; i < K_NUMBER; i++) - { - for (int j = K_NUMBER; j < NUMBER_OF_ELEMENTS; j++) - { - EXPECT_GE(priorityArray[i]._value, priorityArray[j]._value); - } - } -} - -TEST(TestAsyncFileIO, FindKLargestOneItemArray) -{ - constexpr int NUMBER_OF_ELEMENTS = 1; - constexpr int K_NUMBER = 1; - - PairStruct priorityArray[NUMBER_OF_ELEMENTS]; - priorityArray[0]._value = random(1000); - - // Find K largest in priorityArray - findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); - - // Comparison. Expect all K left items are greater than remained items - for (int i = 0; i < K_NUMBER; i++) - { - for (int j = K_NUMBER; j < NUMBER_OF_ELEMENTS; j++) - { - EXPECT_GE(priorityArray[i]._value, priorityArray[j]._value); - } - } -} - -TEST(TestAsyncFileIO, FindKLargestDuplicatedItems) -{ - constexpr int NUMBER_OF_ELEMENTS = 1000; - constexpr int K_NUMBER = 100; - - PairStruct priorityArray[NUMBER_OF_ELEMENTS]; - - // Generate the elements - const int value = random(NUMBER_OF_ELEMENTS); - for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) - { - priorityArray[i]._key = i; - priorityArray[i]._value = value; - } - - // Find K largest in priorityArray - findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); - - // Comparison. Expect all K left items are greater than remained items - for (int i = 0; i < K_NUMBER; i++) - { - for (int j = K_NUMBER; j < NUMBER_OF_ELEMENTS; j++) - { - EXPECT_GE(priorityArray[i]._value, priorityArray[j]._value); - } - } -} - -TEST(TestAsyncFileIO, FindKLargestK1) -{ - constexpr int NUMBER_OF_ELEMENTS = 1000; - constexpr int K_NUMBER = 1; - - PairStruct priorityArray[NUMBER_OF_ELEMENTS]; - - // Generate the elements - for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) - { - priorityArray[i]._key = i; - priorityArray[i]._value = random(NUMBER_OF_ELEMENTS); - } - - // Find K largest in priorityArray - findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); - - // Comparison. The first item is the largest one - for (int j = 1; j < NUMBER_OF_ELEMENTS; j++) - { - EXPECT_GE(priorityArray[0]._value, priorityArray[j]._value); - } -} - -TEST(TestAsyncFileIO, FindKLargestKDuplicated) -{ - constexpr int NUMBER_OF_ELEMENTS = 1000; - constexpr int K_NUMBER = 100; - - PairStruct priorityArray[NUMBER_OF_ELEMENTS]; - - // Generate the elements - std::vector numbers(NUMBER_OF_ELEMENTS); - - // Constant value for first K_NUMBER + 1 element - const int value = NUMBER_OF_ELEMENTS + 1; - for (int i =0; i < K_NUMBER + 1; i++) - { - priorityArray[i]._value = value; - } - for (int i = K_NUMBER + 1; i < NUMBER_OF_ELEMENTS; i++) - { - priorityArray[i]._value = random(NUMBER_OF_ELEMENTS); - } - - // Shuffle the array - for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) - { - int newLocation = random(NUMBER_OF_ELEMENTS); - long long tempValue = priorityArray[i]._value; - priorityArray[i]._value = priorityArray[newLocation]._value; - priorityArray[newLocation]._value = tempValue; - } - - // Find K largest in priorityArray - findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); - - // Comparison. The first item is the largest one - for (int j = 0; j < K_NUMBER; j++) - { - EXPECT_EQ(priorityArray[j]._value, value); - } -} - diff --git a/test/fourq.cpp b/test/fourq.cpp deleted file mode 100644 index 1f606ef9d..000000000 --- a/test/fourq.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#define NO_UEFI - -#include "../src/platform/memory.h" -#include "../src/four_q.h" -#include "utils.h" - -#include -#include "gtest/gtest.h" - -#include -#include - -static constexpr int ID_SIZE = 61; -static inline void getIDChar(const unsigned char* key, char* identity, bool isLowerCase) -{ - CHAR16 computorID[61]; - getIdentity(key, computorID, true); - for (int k = 0; k < 60; ++k) - { - identity[k] = computorID[k] - L'a' + 'a'; - } - identity[60] = 0; -} - -TEST(TestFourQ, TestMultiply) -{ - // 8 test cases for 256-bit multiplication - unsigned long long a[8][4] = { - {9951791076627133056ULL, 8515301911953011018ULL, 10503917255838740547ULL, 9403542041099946340ULL}, - {9634782769625085733ULL, 3923345248364070851ULL, 12874006609097115757ULL, 9445681298461330583ULL}, - {9314926113594160360ULL, 9012577733633554087ULL, 15853326627100346762ULL, 3353532907889994600ULL}, - {11822735244239455150ULL, 14860878323222532373ULL, 839169842161576273ULL, 8384082473945502970ULL}, - {6391904870724534887ULL, 7752608459014781040ULL, 8834893383869603648ULL, 14432583643443481392ULL}, - {9034457083341789982ULL, 15550692794033658766ULL, 18370398459251091929ULL, 161212377777301450ULL}, - {12066041174979511630ULL, 6197228902632247602ULL, 15544684064627230784ULL, 8662358800126738212ULL}, - {2997608593061094407ULL, 10746661492960439270ULL, 13066743968851273858ULL, 901611315508727516ULL} - }; - - unsigned long long b[8][4] = { - {14556080569315562443ULL, 4784279743451576405ULL, 16952050128007612055ULL, 17448141405813274955ULL}, - {16953856751996506377ULL, 5957469746201176117ULL, 413985909494190460ULL, 5019301766552018644ULL}, - {8337584125020700765ULL, 9891896711220896307ULL, 3688562803407556063ULL, 15879907979249125147ULL}, - {5253913930687524613ULL, 14356908424098313115ULL, 7294083945257658276ULL, 11357758627518780620ULL}, - {6604082675214113798ULL, 8102242472442817269ULL, 4231600794557460268ULL, 9254306641367892880ULL}, - {15307070962626904180ULL, 14565308158529607085ULL, 7804612167412830134ULL, 11197002641182899202ULL}, - {5681082236069360781ULL, 11354469612480482261ULL, 10740484893427922886ULL, 4093428096946105430ULL}, - {16936346349005670285ULL, 16111331879026478134ULL, 281576863978497861ULL, 4843225515675739317ULL} - }; - - unsigned long long expectedMultiplicationResults[8][8] = { - {13505937776277228416ULL, 10691581058996783029ULL, 15857294677093499275ULL, 10551077288120234079ULL, - 10488747005868148888ULL, 3163167577502768305ULL, 12011108917152358447ULL, 8894487319443104894ULL}, - - {11258722506082215245ULL, 3752109657065586715ULL, 9754007644313481322ULL, 10650543212606486248ULL, - 14000725040689989368ULL, 6868107242688413590ULL, 12132679480588703742ULL, 2570140542862762927ULL}, - - {7980800202961401928ULL, 18091087109835938886ULL, 11937230724836153237ULL, 18437285308724511498ULL, - 9256451621004954121ULL, 2817287347866660760ULL, 7356871972350029435ULL, 2886893956455686033ULL}, - - {14821519003237893222ULL, 11951435221854993875ULL, 5649570579164725909ULL, 16529503750125471729ULL, - 5712698943065886767ULL, 10417044944053178538ULL, 10215165497768617151ULL, 5162124257364100363ULL}, - - {10299854845140439658ULL, 5620198573463725080ULL, 18403939479767000599ULL, 3997239017815343129ULL, - 17558583433366224073ULL, 16662387952814143598ULL, 16240400534467578973ULL, 7240494806558978920ULL}, - - {15852260790186789272ULL, 6843720495231156925ULL, 18209245341578934878ULL, 3229051715759855960ULL, - 6553675393672969791ULL, 11442787882881602486ULL, 5043402961965006398ULL, 97854418782578342ULL}, - - {16919816915648955382ULL, 15728350531867604818ULL, 262149976282468082ULL, 16822220236393767682ULL, - 17482650320082366559ULL, 4634282717190265856ULL, 3892072508178358212ULL, 1922222304195309433ULL}, - - {3674093358531938523ULL, 797775358977430453ULL, 6686355987721165902ULL, 16831290265741585642ULL, - 11378657779800286238ULL, 14872963278680745844ULL, 15850192255010623436ULL, 236719656924026199ULL} - }; - - long long averageProcessingTime = 0; - for (int i = 0; i < 8; i++) - { - unsigned long long result[8] = { 0 }; - - multiply(a[i], b[i], result); - - for (int k = 0; k < 8; k++) - { - EXPECT_EQ(result[k], expectedMultiplicationResults[i][k]) << " at [" << k << "]"; - } - } -} - -TEST(TestFourQ, TestMontgomeryMultiplyModOrder) -{ - - // 8 test cases for 256-bit MontgomeryMultiplyMod - unsigned long long a[8][4] = { - {9951791076627133056ULL, 8515301911953011018ULL, 10503917255838740547ULL, 9403542041099946340ULL}, - {9634782769625085733ULL, 3923345248364070851ULL, 12874006609097115757ULL, 9445681298461330583ULL}, - {9314926113594160360ULL, 9012577733633554087ULL, 15853326627100346762ULL, 3353532907889994600ULL}, - {11822735244239455150ULL, 14860878323222532373ULL, 839169842161576273ULL, 8384082473945502970ULL}, - {6391904870724534887ULL, 7752608459014781040ULL, 8834893383869603648ULL, 14432583643443481392ULL}, - {9034457083341789982ULL, 15550692794033658766ULL, 18370398459251091929ULL, 161212377777301450ULL}, - {12066041174979511630ULL, 6197228902632247602ULL, 15544684064627230784ULL, 8662358800126738212ULL}, - {2997608593061094407ULL, 10746661492960439270ULL, 13066743968851273858ULL, 901611315508727516ULL} - }; - - unsigned long long b[8][4] = { - {14556080569315562443ULL, 4784279743451576405ULL, 16952050128007612055ULL, 17448141405813274955ULL}, - {16953856751996506377ULL, 5957469746201176117ULL, 413985909494190460ULL, 5019301766552018644ULL}, - {8337584125020700765ULL, 9891896711220896307ULL, 3688562803407556063ULL, 15879907979249125147ULL}, - {5253913930687524613ULL, 14356908424098313115ULL, 7294083945257658276ULL, 11357758627518780620ULL}, - {6604082675214113798ULL, 8102242472442817269ULL, 4231600794557460268ULL, 9254306641367892880ULL}, - {15307070962626904180ULL, 14565308158529607085ULL, 7804612167412830134ULL, 11197002641182899202ULL}, - {5681082236069360781ULL, 11354469612480482261ULL, 10740484893427922886ULL, 4093428096946105430ULL}, - {16936346349005670285ULL, 16111331879026478134ULL, 281576863978497861ULL, 4843225515675739317ULL} - }; - - unsigned long long expectedMontgomeryMultiplyModOrderResults[8][4] = { - {1178600784049730938ULL,13475129099769568773ULL,8171380610981515619ULL,8889798462048389782ULL,}, - {9346893806433251032ULL,3783576366952291632ULL,9006661425833189295ULL,2561156787602149305ULL,}, - {15012255874803770290ULL,11566062810664104635ULL,4497422501535458145ULL,2875434161571900946ULL,}, - {12004931297526373125ULL,10222857380028780508ULL,17154413062382081055ULL,5158706721726943589ULL,}, - {9724623868153589743ULL,17410218506619807138ULL,7496133043478274651ULL,7229774243864893754ULL,}, - {10156145653863087884ULL,16403847498912163678ULL,18326820829694769537ULL,90612319098335675ULL,}, - {2160566501146101694ULL,16888000406840707060ULL,8270191582668357443ULL,1911769260568884212ULL,}, - {6774298701428010308ULL,11825708701777781499ULL,11766967071579472107ULL,229574109642630470ULL,}, - }; - - for (int i = 0; i < 8; i++) - { - unsigned long long result[4] = { 0 }; - // (a * b) mod n - Montgomery_multiply_mod_order(a[i], b[i], result); - - for (int k = 0; k < 4; k++) - { - EXPECT_EQ(result[k], expectedMontgomeryMultiplyModOrderResults[i][k]) << " at [" << k << "]"; - } - } -} - -TEST(TestFourQ, TestGenerateKeys) -{ - // Data generate from clang-compiled qubic-cli - unsigned char computorSeeds[][56] = { - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "nhtighfbfdvgxnxrwxnbfmisknawppoewsycodciozvqpeqegttwofg", - "fcvgljppwwwjhrawxeywxqdgssttiihcmikxbnnunugldvcitkhcrfl", - "eijytgswxqzfkotmqvwulivpbximuhmgydvnaozwszguqflpfvltqge", - }; - unsigned char expectedPrivateIDs[][ID_SIZE] = { - "cctwbaulwuyhybijykxrmxnyrvzbalwryiiahltfwanuafhyfhepcjjgvaec", - "qfldkxspcgsnbgnccmsjftuhxvlfrmarkqrvqjvjaebwoasbytasvcffdfwd", - "mqlgaugwpdaphhqsacmqdcioomybxkaisrwyefyisayrikqjlckwkpdhuqlc", - "qwqmcjhdlzphgabvnjbedsgwrgpbvplcipmxkzuogglbhzfjiytunaeactsn", - }; - unsigned char expectedPublicIDs[][ID_SIZE] = { - "bzbqfllbncxemglobhuvftluplvcpquassilfaboffbcadqssupnwlzbqexk", - "lsgscfhdoahhlbdmlyzrfkvsfrqbbuznganescizcetyxkcdhljhemofxcwb", - "zsvpltnzfdyjzetanimltroldybdzoctvfguybpbvdxbndsrhyreppgccspo", - "xcfqbuwxxtufpfwyteglgchgnqyanubfbkpwtivfobxybgaqcgiqmzlgscwe" - }; - - unsigned char computorSubseeds[32]; - unsigned char computorPrivateKeys[32]; - unsigned char computorPublicKeys[32]; - char privakeyKeyId[ID_SIZE]; - char publicKeyId[ID_SIZE]; - - int numberOfTests = sizeof(computorSeeds) / sizeof(computorSeeds[0]); - - for (int i = 0; i < numberOfTests; ++i) - { - getSubseed(computorSeeds[i], computorSubseeds); - getPrivateKey(computorSubseeds, computorPrivateKeys); - getPublicKey(computorPrivateKeys, computorPublicKeys); - - getIDChar(computorPrivateKeys, privakeyKeyId, true); - getIDChar(computorPublicKeys, publicKeyId, true); - // Verification - for (int k = 0; k < ID_SIZE; ++k) - { - EXPECT_EQ(expectedPrivateIDs[i][k], privakeyKeyId[k]) << " at [" << i << "][" << k << "]"; - EXPECT_EQ(expectedPublicIDs[i][k], publicKeyId[k]) << " at [" << i << "][" << k << "]"; - } - } -} - -// sign(const unsigned char* subseed, const unsigned char* publicKey, const unsigned char* messageDigest, unsigned char* signature) -TEST(TestFourQ, TestSign) -{ - // For sign and verification, some constants need to be set -#ifdef __AVX512F__ - initAVX512FourQConstants(); -#endif - - const std::string subSeedsStr[] = { - "4ac19e2bf0d3776519aabe31924f7dc2589b3d0e7411a65f84c9b16df72c038e", - "e8217c5b40aa91df662803ce4dbf18722e35f1097ac68fb5da10643a825799e3", - "6d02f48bcb53ac397fc71a9028e4165df9b87044c53e116a0192d7fa83254bb0", - "3cfa1097be482f6e5ce132c2aa657d0fb9d84121048de6f05b90a2cc136bf73a", - "5208dd447a21f3c9911cae547637c580e74fa40d2a9c5e1fb26dcbfa408539d1", - "987b163dc6fd492573b4e18af7016c52a027ced5345fb8904e69037ed2ac1b81", - "d462ab0c83f931c4a87f12953e20bd576be849da5a8f1402c3b176ef92486d05", - "713fc86e289f124bdb2a65f6a37c01e0559a148ebf430f6729c184de76b23a90", - "15cd5b0700000000b168de3a00000000743af15000000000efcdab0000000000" - }; - - const std::string messageDigestsStr[] = { - "94e120a4d3f58c217a53eb9046d9f2c5b11288a9fe340d6ce5a771cf04b82e63", - "77f493b58ea40162dc33f9a718e2543b05f629884d7ca0e31598c45f021ae7c0", - "5cc82fa973101da5bfb3e2448196f0a7d7d3324c86fbbe42907613d5c8c2f1a4", - "c01ae5f2879d11439b30ddae5f4c7b22689f023e17b4955c3b2f05e8d9089af6", - "2f893e70ad52c9186eb4b60dfe137288c4a9e0fb6d34a51897e2365a01b0d443", - "ec09a3f415c2dda5f8419026678ab03524f67ed9817ba230cd24513750e01bc6", - "48b59f32a61dff0e13528e4c7937bdf080c3efa7d221364be87f01d5c60f882a", - "b31e704c8a3f1d02692f05a7d8e5f6c911d370f4a68b2ec3fa4c51d7289003de", - "89d5f92a895987457400219e121e8730f6b248a1fd28bfee017611ef079105b5" - }; - - const std::string expectedSignaturesStr[] = - { - "357d47b1366f33eed311a4458ec7326d35728e9292328a9b7ff8d4ec0f7b0df9323f5d1cd01bd5a380a1a8e4f29ad3ae9c5d94e84f4181a61ca73030d6d11600", - "7d2479a15746839c4c5e1fdf0aadb167974c292ceee80593e18b5135763db63163d8eee5bd309c506f47b16cd1242ddfe985887b19d3943c14ec6ab79a9c1900", - "1b8ed83af3dc12deb1554f48df46bf5bc5e4654f62f97ef20656fae4e965ac87762c9fe6189dfe89192a619bca4a6c390f4e97bb1f926041263f2ba4206b0c00", - "470ad247ff6b2e55d44e9f2a79ce402bfc5e8c5322ed297f71939a9c5398b6fcb5058c05e614d10d90d6bdec8ee4ecc6462cdd54e0ea830fde6be465de3f2900", - "0851db3d4021bdc8cf3816b4672aba2f5f7cd0bf19e779e28ee60241bc4246dabe7442a11953703a44ed1cadd0af9fce683c5a312326341ac7a3e55a18c40100", - "54466ae5ecad45c83798e4e3e02ab40e834bf8d3f4f1628b300601ab87894599a43278efd48be7e9615cd569e656356a9e2307ae257b85a3f1f0f333f2302200", - "6d67294ccd03dc51fdb3bca649b7e060d3cf06c417e7053472ca617b93e5926928a7a48b1791c2487c7e83eeb4919046493709508c0541d1c02e9545401b2100", - "20764a88943fb4e796f81a560bde5e652c82ffb203c00b4846102a268ae68f64cdee6c7a3edbf4de48dd25fd423a4b40e79d97a2a47fd11030b6f30a09130b00", - "9f71d3138ff8a72db3b39883e056ce7f5bfe40de6387e64eff0c17e72bd1862ccd848000be1841725f1da87654235329b685e1c81c939cb0154bbc8d30a20c00", - }; - - constexpr size_t numberOfTests = sizeof(subSeedsStr) / sizeof(subSeedsStr[0]); - m256i subseeds[numberOfTests]; - m256i messageDigests[numberOfTests]; - unsigned char expectedSignatures[numberOfTests][64]; - - for (unsigned long long i = 0; i < numberOfTests; ++i) - { - subseeds[i] = test_utils::hexTo32Bytes(subSeedsStr[i], 32); - messageDigests[i] = test_utils::hexTo32Bytes(messageDigestsStr[i], 32); - test_utils::hexToByte(expectedSignaturesStr[i], 64, expectedSignatures[i]); - } - - for (unsigned long long i = 0; i < numberOfTests; ++i) - { - unsigned char publicKey[32]; - unsigned char privateKey[32]; - getPrivateKey(subseeds[i].m256i_u8, privateKey); - getPublicKey(privateKey, publicKey); - - unsigned char signature[64]; - sign(subseeds[i].m256i_u8, publicKey, messageDigests[i].m256i_u8, signature); - - // Verify functions - bool verifyStatus = verify(publicKey, messageDigests[i].m256i_u8, signature); - - EXPECT_TRUE(verifyStatus); - - for (int k = 0; k < 64; ++k) - { - EXPECT_EQ(expectedSignatures[i][k], signature[k]) << " at [" << i << "][" << k << "]"; - } - } -} diff --git a/test/kangaroo_twelve.cpp b/test/kangaroo_twelve.cpp deleted file mode 100644 index 231317c8a..000000000 --- a/test/kangaroo_twelve.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#define NO_UEFI - -#include "../src/K12/kangaroo_twelve_xkcp.h" -#include "../src/kangaroo_twelve.h" -#include "../src/platform/memory.h" -#include -#include "gtest/gtest.h" - -#include -#include - - -TEST(TestCoreK12, PerformanceDigest32Of1GB) -{ - constexpr size_t bytesPerGigaByte = 1024 * 1024 * 1024; - constexpr size_t repN = 1; - constexpr size_t inputN = bytesPerGigaByte; - constexpr size_t outputN = 32; - - char* inputPtr = new char[inputN]; - for (size_t i = 0; i < 100; ++i) - { - unsigned int pos, val; - _rdrand32_step(&pos); - _rdrand32_step(&val); - inputPtr[pos % inputN] = val & 0xff; - } - char outputArray[outputN]; - - auto startTime = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < repN; ++i) - XKCP::KangarooTwelve((unsigned char *) inputPtr, inputN, (unsigned char*) outputArray, outputN); - auto durationMilliSec = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime); - - double bytePerMilliSec = double(repN * inputN) / double(durationMilliSec.count()); - double gigaBytePerSec = bytePerMilliSec * (1000.0 / bytesPerGigaByte); - std::cout << "K12 of 1 GB to 32 Byte digest: " << gigaBytePerSec << " GB/sec = " << 1.0 / gigaBytePerSec << " sec/GB" << std::endl; - - delete [] inputPtr; -} - -TEST(TestCoreK12, CompareK12Implementations) -{ - constexpr size_t bytesPerGigaByte = 1024 * 1024 * 1024; - constexpr size_t repN = 1; - constexpr size_t inputN = bytesPerGigaByte; - constexpr size_t outputN = 32; - - char* inputPtr = new char[inputN]; - for (size_t i = 0; i < 100; ++i) - { - unsigned int pos, val; - _rdrand32_step(&pos); - _rdrand32_step(&val); - inputPtr[pos % inputN] = val & 0xff; - } - char outputArrayXKCP[outputN]; - - auto startTime = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < repN; ++i) - XKCP::KangarooTwelve((unsigned char *) inputPtr, inputN, (unsigned char*) outputArrayXKCP, outputN); - auto durationMilliSec = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime); - - double bytePerMilliSec = double(repN * inputN) / double(durationMilliSec.count()); - double gigaBytePerSec = bytePerMilliSec * (1000.0 / bytesPerGigaByte); - std::cout << "K12 of 1 GB to 32 Byte digest: " << gigaBytePerSec << " GB/sec = " << 1.0 / gigaBytePerSec << " sec/GB" << std::endl; - std::cout << "Digest of xkcp implementation: "; - for (int i = 0; i < sizeof(outputArrayXKCP); i++){ - std::cout << std::hex << std::setfill('0') << std::setw(2) - << (static_cast(outputArrayXKCP[i]) & 0xff); - } - std::cout << std::endl; - - char outputArray[outputN]; - startTime = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < repN; ++i) - KangarooTwelve((unsigned char *) inputPtr, inputN, (unsigned char*) outputArray, outputN); - durationMilliSec = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime); - - bytePerMilliSec = double(repN * inputN) / double(durationMilliSec.count()); - gigaBytePerSec = bytePerMilliSec * (1000.0 / bytesPerGigaByte); - std::cout << "K12 of 1 GB to 32 Byte digest: " << gigaBytePerSec << " GB/sec = " << 1.0 / gigaBytePerSec << " sec/GB" << std::endl; - std::cout << "Digest of native implementation: "; - - for (int i = 0; i < sizeof(outputArray); i++){ - std::cout << std::hex << std::setfill('0') << std::setw(2) - << (static_cast(outputArray[i]) & 0xff); - } - std::cout << std::endl; - ASSERT_EQ(memcmp(outputArrayXKCP, outputArray, outputN), 0); - delete [] inputPtr; -} diff --git a/test/logging_test.h b/test/logging_test.h deleted file mode 100644 index cefbacf46..000000000 --- a/test/logging_test.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "gtest/gtest.h" - -// workaround for name clash with stdlib -#define system qubicSystemStruct - -// enable all logging that is used in tests -#include "private_settings.h" -#undef LOG_SPECTRUM -#define LOG_SPECTRUM 1 - -// also reduce size of logging tx index by reducing maximum number of ticks per epoch -#include "public_settings.h" -#undef MAX_NUMBER_OF_TICKS_PER_EPOCH -#define MAX_NUMBER_OF_TICKS_PER_EPOCH 3000 - -// Reduce virtual memory size for testing -#undef LOG_BUFFER_PAGE_SIZE -#undef PMAP_LOG_PAGE_SIZE -#undef IMAP_LOG_PAGE_SIZE -#undef VM_NUM_CACHE_PAGE -#define LOG_BUFFER_PAGE_SIZE 10000000ULL -#define PMAP_LOG_PAGE_SIZE 1000000ULL -#define IMAP_LOG_PAGE_SIZE 300ULL -#define VM_NUM_CACHE_PAGE 1 - -#include "logging/logging.h" - -class LoggingTest -{ -public: - LoggingTest() - { - EXPECT_TRUE(qLogger::initLogging()); - } - - ~LoggingTest() - { - qLogger::deinitLogging(); - } -}; diff --git a/test/m256.cpp b/test/m256.cpp deleted file mode 100644 index 8ddae7baf..000000000 --- a/test/m256.cpp +++ /dev/null @@ -1,274 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" -#include "../src/platform/m256.h" - -#include - - -TEST(TestCore256BitClass, ConstructAssignCompare) { - // Basic construction, comparison, and assignment - unsigned char buffer_u8[32]; - for (int8_t i = 0; i < 32; ++i) - buffer_u8[i] = 3 + 2 * i; - - m256i v1(buffer_u8); - for (int8_t i = 0; i < 32; ++i) - EXPECT_EQ(v1.m256i_u8[i], 3 + 2 * i); - EXPECT_TRUE(v1 == buffer_u8); - EXPECT_FALSE(v1 != buffer_u8); - EXPECT_FALSE(isZero(v1)); - - for (int8_t i = 0; i < 32; ++i) - buffer_u8[i] = 250 - 3 * i; - - for (int8_t i = 0; i < 32; ++i) - EXPECT_EQ(v1.m256i_u8[i], 3 + 2 * i); - EXPECT_TRUE(v1 != buffer_u8); - EXPECT_FALSE(v1 == buffer_u8); - EXPECT_FALSE(isZero(v1)); - - m256i v2(buffer_u8); - for (int8_t i = 0; i < 32; ++i) - EXPECT_EQ(v2.m256i_u8[i], 250 - 3 * i); - EXPECT_TRUE(v1 != v2); - EXPECT_FALSE(v1 == v2); - EXPECT_FALSE(isZero(v2)); - - m256i v3(v1); - for (int8_t i = 0; i < 32; ++i) - EXPECT_EQ(v3.m256i_u8[i], 3 + 2 * i); - EXPECT_TRUE(v1 == v3); - EXPECT_FALSE(v1 != v3); - EXPECT_FALSE(isZero(v3)); - - __m256i buffer_intr; - unsigned char* bytes_of_buffer_intr = reinterpret_cast(&buffer_intr); - for (uint8_t i = 0; i < 32; ++i) { - bytes_of_buffer_intr[i] = 90 + i; - } - - m256i v4(buffer_intr); - for (int8_t i = 0; i < 32; ++i) - EXPECT_EQ(v4.m256i_u8[i], 90 + i); - EXPECT_TRUE(v4 == buffer_intr); - EXPECT_FALSE(v4 != buffer_intr); - EXPECT_TRUE(v4 != v3); - EXPECT_FALSE(v4 == v3); - EXPECT_TRUE(v4 != v2); - EXPECT_FALSE(v4 == v2); - EXPECT_TRUE(v4 != v1); - EXPECT_FALSE(v4 == v1); - EXPECT_FALSE(isZero(v4)); - - // Construction, comparison, and assignment involving volatile - volatile m256i v5(v4); - EXPECT_TRUE(v4 == v5); - EXPECT_FALSE(v4 != v5); - EXPECT_FALSE(isZero(v5)); - - m256i v6(v5); - EXPECT_TRUE(v6 == v5); - EXPECT_FALSE(v6 != v5); - EXPECT_FALSE(isZero(v6)); - - EXPECT_TRUE(v1 != v5); - EXPECT_FALSE(v1 == v5); - v1 = v5; - EXPECT_TRUE(v1 == v5); - EXPECT_FALSE(v1 != v5); - EXPECT_FALSE(isZero(v1)); - - EXPECT_TRUE(v2 != v5); - EXPECT_FALSE(v2 == v5); - v5 = v2; - EXPECT_TRUE(v2 == v5); - EXPECT_FALSE(v2 != v5); - EXPECT_FALSE(isZero(v2)); - - v5.m256i_i64[0] = 0; - v5.m256i_i64[1] = 0; - v5.m256i_i64[2] = 0; - v5.m256i_i64[3] = 0; - EXPECT_TRUE(isZero(v5)); - EXPECT_TRUE(v2 != v5); - EXPECT_FALSE(v2 == v5); - v2 = v5; - EXPECT_TRUE(v2 == v5); - EXPECT_FALSE(v2 != v5); - EXPECT_TRUE(isZero(v2)); - - // single-bit difference test - for (int i = 0; i < 32; ++i) - { - for (int j = 0; j < 8; ++j) - { - v2 = v5; - EXPECT_TRUE(isZero(v2)); - EXPECT_TRUE(v2 == v5); - EXPECT_FALSE(v2 != v5); - v2.m256i_u8[i] |= (1 << j); - EXPECT_FALSE(isZero(v2)); - EXPECT_TRUE(v2 != v5); - EXPECT_FALSE(v2 == v5); - } - } - - // self-assignment and comparison - m256i v1_original_state = v1; // Capture original state for later comparison - m256i v5_original_state = v5; - - SUPPRESS_WARNINGS_BEGIN - IGNORE_SELFASSIGNMENT_WARNING - v1 = v1; // This line is intentionally testing self-assignment - SUPPRESS_WARNINGS_END - - EXPECT_TRUE(v1 == v1_original_state); - EXPECT_TRUE(v1 == v1); - EXPECT_FALSE(v1 != v1); - v5 = v5; - EXPECT_TRUE(v5 == v5_original_state); - EXPECT_TRUE(v5 == v5); - EXPECT_FALSE(v5 != v5); - - // Non-aligned assignment and comparison - unsigned char buffer_u8_64[64]; - for (int8_t i = 0; i < 64; ++i) - buffer_u8_64[i] = 7 + i; - for (int8_t i = 0; i < 32; ++i) - { - v1 = buffer_u8_64 + i; - EXPECT_TRUE(v1 == buffer_u8_64 + i); - EXPECT_FALSE(v1 != buffer_u8_64 + i); - EXPECT_FALSE(isZero(v1)); - for (int j = 0; j < 32; ++j) - EXPECT_EQ(v1.m256i_u8[j], 7 + i + j); - } - - // m256i::zero() - EXPECT_FALSE(isZero(v1)); - EXPECT_FALSE(v1 == m256i::zero()); - EXPECT_TRUE(v1 != m256i::zero()); - v1 = m256i::zero(); - EXPECT_TRUE(isZero(v1)); - EXPECT_TRUE(v1 == m256i::zero()); - EXPECT_FALSE(v1 != m256i::zero()); - - // 4 x u64 constructor - m256i v7(1234, 5678, 9012, 3456); - EXPECT_EQ(v7.u64._0, 1234); - EXPECT_EQ(v7.u64._1, 5678); - EXPECT_EQ(v7.u64._2, 9012); - EXPECT_EQ(v7.u64._3, 3456); - EXPECT_EQ(v7.m256i_u64[0], 1234); - EXPECT_EQ(v7.m256i_u64[1], 5678); - EXPECT_EQ(v7.m256i_u64[2], 9012); - EXPECT_EQ(v7.m256i_u64[3], 3456); -} - -TEST(TestCore256BitFunctionsIntrinsicType, isZero) { - EXPECT_TRUE(isZero(m256i(0, 0, 0, 0).getIntrinsicValue())); - EXPECT_FALSE(isZero(m256i(1, 0, 0, 0).getIntrinsicValue())); - EXPECT_FALSE(isZero(m256i(0, 1, 0, 0).getIntrinsicValue())); - EXPECT_FALSE(isZero(m256i(0, 0, 1, 0).getIntrinsicValue())); - EXPECT_FALSE(isZero(m256i(0, 0, 0, 1).getIntrinsicValue())); - EXPECT_FALSE(isZero(m256i(0xffffffffffffffff,0xffffffffffffffff,0xffffffffffffffff,0xffffffffffffffff).getIntrinsicValue())); -} - -TEST(TestCore256BitFunctionsIntrinsicType, isZeroPerformance) -{ - constexpr int N = 50000000; - volatile m256i optimizeBarrierValue(0, 0, 0, 0); - m256i value; - [[maybe_unused]] volatile bool optimizeBarrierResult; - - // measure isZero - auto t0 = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < N; ++i) - { - optimizeBarrierValue.i64._0 = i; - value = optimizeBarrierValue; - optimizeBarrierResult = isZero(value); - } - auto t1 = std::chrono::high_resolution_clock::now(); - auto ms = std::chrono::duration_cast(t1 - t0).count(); - std::cout << N << " x isZero: " << ms << " milliseconds" << std::endl; - - // measure comparison with existing zero m256i - t0 = std::chrono::high_resolution_clock::now(); - m256i zero = m256i::zero(); - for (int i = 0; i < N; ++i) - { - optimizeBarrierValue.i64._0 = i; - value = optimizeBarrierValue; - optimizeBarrierResult = (value == zero); - } - t1 = std::chrono::high_resolution_clock::now(); - ms = std::chrono::duration_cast(t1 - t0).count(); - std::cout << N << " x compare with existing zero instance: " << ms << " milliseconds" << std::endl; - - // measure comparison with new zero m256i - t0 = std::chrono::high_resolution_clock::now(); - volatile m256i zeroVol; - for (int i = 0; i < N; ++i) - { - optimizeBarrierValue.i64._0 = i; - value = optimizeBarrierValue; - zeroVol = m256i::zero(); - optimizeBarrierResult = (value == zeroVol); - } - t1 = std::chrono::high_resolution_clock::now(); - ms = std::chrono::duration_cast(t1 - t0).count(); - std::cout << N << " x compare with new zero instance: " << ms << " milliseconds" << std::endl; -} - - -TEST(TestCore256BitFunctions, operatorEqual) { - EXPECT_TRUE(m256i(0, 0, 0, 0) == m256i(0, 0, 0, 0)); - EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(0, 0, 0, 1)); - EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(0, 0, 1, 0)); - EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(0, 1, 0, 0)); - EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(1, 0, 0, 0)); - - EXPECT_TRUE(m256i(42, 42, 42, 42) == m256i(42, 42, 42, 42)); - EXPECT_FALSE(m256i(0, 42, 42, 42) == m256i(42, 42, 42, 42)); - EXPECT_FALSE(m256i(42, 0, 42, 42) == m256i(42, 42, 42, 42)); - EXPECT_FALSE(m256i(42, 42, 0, 42) == m256i(42, 42, 42, 42)); - EXPECT_FALSE(m256i(42, 42, 42, 0) == m256i(42, 42, 42, 42)); -} - -TEST(TestCore256BitFunctions, operatorNotEqual) { - EXPECT_FALSE(m256i(0, 0, 0, 0) != m256i(0, 0, 0, 0)); - EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(0, 0, 0, 1)); - EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(0, 0, 1, 0)); - EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(0, 1, 0, 0)); - EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(1, 0, 0, 0)); - - EXPECT_FALSE(m256i(42, 42, 42, 42) != m256i(42, 42, 42, 42)); - EXPECT_TRUE(m256i(0, 42, 42, 42) != m256i(42, 42, 42, 42)); - EXPECT_TRUE(m256i(42, 0, 42, 42) != m256i(42, 42, 42, 42)); - EXPECT_TRUE(m256i(42, 42, 0, 42) != m256i(42, 42, 42, 42)); - EXPECT_TRUE(m256i(42, 42, 42, 0) != m256i(42, 42, 42, 42)); -} - -TEST(TestCore256BitFunctions, operatorLessThan) { - EXPECT_FALSE(m256i(0, 0, 0, 0) < m256i(0, 0, 0, 0)); - EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(0, 0, 0, 1)); - EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(0, 0, 1, 0)); - EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(0, 1, 0, 0)); - EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(1, 0, 0, 0)); - EXPECT_FALSE(m256i(0, 0, 0, 1) < m256i(0, 0, 0, 0)); - EXPECT_FALSE(m256i(0, 0, 1, 0) < m256i(0, 0, 0, 0)); - EXPECT_FALSE(m256i(0, 1, 0, 0) < m256i(0, 0, 0, 0)); - EXPECT_FALSE(m256i(1, 0, 0, 0) < m256i(0, 0, 0, 0)); - - EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 100, 100, 100)); - EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 100, 100, 1)); - EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 100, 1, 100)); - EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 1, 100, 100)); - EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(1, 100, 100, 100)); - EXPECT_TRUE(m256i(100, 100, 100, 1) < m256i(100, 100, 100, 100)); - EXPECT_TRUE(m256i(100, 100, 1, 100) < m256i(100, 100, 100, 100)); - EXPECT_TRUE(m256i(100, 1, 100, 100) < m256i(100, 100, 100, 100)); - EXPECT_TRUE(m256i(1, 100, 100, 100) < m256i(100, 100, 100, 100)); -} diff --git a/test/math_lib.cpp b/test/math_lib.cpp deleted file mode 100644 index 6769b830e..000000000 --- a/test/math_lib.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "gtest/gtest.h" - -#include -#include "../src/contracts/math_lib.h" - -TEST(TestCoreMathLib, Max) { - EXPECT_EQ(math_lib::max(0, 0), 0); - EXPECT_EQ(math_lib::max(10, 0), 10); - EXPECT_EQ(math_lib::max(0, 20), 20); - EXPECT_EQ(math_lib::max(-10, 0), 0); - EXPECT_EQ(math_lib::max(0, -20), 0); - - EXPECT_EQ(math_lib::max(0.0, 0.0), 0.0); - EXPECT_EQ(math_lib::max(10.0, 0.0), 10.0); - EXPECT_EQ(math_lib::max(0.0, 20.0), 20.0); - EXPECT_EQ(math_lib::max(-10.0, 0.0), 0.0); - EXPECT_EQ(math_lib::max(0.0, -20.0), 0.0); -} - -TEST(TestCoreMathLib, Min) { - EXPECT_EQ(math_lib::min(0, 0), 0); - EXPECT_EQ(math_lib::min(10, 0), 0); - EXPECT_EQ(math_lib::min(0, 20), 0); - EXPECT_EQ(math_lib::min(-10, 0), -10); - EXPECT_EQ(math_lib::min(0, -20), -20); - - EXPECT_EQ(math_lib::min(0.0, 0.0), 0.0); - EXPECT_EQ(math_lib::min(10.0, 0.0), 0.0); - EXPECT_EQ(math_lib::min(0.0, 20.0), 0.0); - EXPECT_EQ(math_lib::min(-10.0, 0.0), -10.0); - EXPECT_EQ(math_lib::min(0.0, -20.0), -20.0); -} - -TEST(TestCoreMathLib, Abs) { - EXPECT_EQ(math_lib::abs(0), 0); - EXPECT_EQ(math_lib::abs(10), 10); - EXPECT_EQ(math_lib::abs(-1110), 1110); - EXPECT_EQ(math_lib::abs(-987654ll), 987654llu); - - EXPECT_EQ(math_lib::abs(0.0), 0.0); - EXPECT_EQ(math_lib::abs(-0.0), 0.0); - EXPECT_EQ(math_lib::abs(-1230.0f), 1230.0f); - EXPECT_EQ(math_lib::abs(INFINITY), INFINITY); - EXPECT_EQ(math_lib::abs(-INFINITY), INFINITY); - EXPECT_EQ(math_lib::abs(FP_NAN), FP_NAN); -} - -// div() and mod() are defined in qpi.h - -template -void testDivUp() -{ - EXPECT_EQ(int(math_lib::divUp(T(0), T(0))), 0); - EXPECT_EQ(int(math_lib::divUp(T(1), T(0))), 0); - EXPECT_EQ(int(math_lib::divUp(T(0), T(1))), 0); - EXPECT_EQ(int(math_lib::divUp(T(1), T(1))), 1); - EXPECT_EQ(int(math_lib::divUp(T(2), T(1))), 2); - EXPECT_EQ(int(math_lib::divUp(T(3), T(1))), 3); - EXPECT_EQ(int(math_lib::divUp(T(0), T(2))), 0); - EXPECT_EQ(int(math_lib::divUp(T(1), T(2))), 1); - EXPECT_EQ(int(math_lib::divUp(T(2), T(2))), 1); - EXPECT_EQ(int(math_lib::divUp(T(3), T(2))), 2); - EXPECT_EQ(int(math_lib::divUp(T(4), T(2))), 2); - EXPECT_EQ(int(math_lib::divUp(T(5), T(2))), 3); - EXPECT_EQ(int(math_lib::divUp(T(0), T(3))), 0); - EXPECT_EQ(int(math_lib::divUp(T(1), T(3))), 1); - EXPECT_EQ(int(math_lib::divUp(T(2), T(3))), 1); - EXPECT_EQ(int(math_lib::divUp(T(3), T(3))), 1); - EXPECT_EQ(int(math_lib::divUp(T(4), T(3))), 2); - EXPECT_EQ(int(math_lib::divUp(T(5), T(3))), 2); - EXPECT_EQ(int(math_lib::divUp(T(6), T(3))), 2); - EXPECT_EQ(int(math_lib::divUp(T(7), T(3))), 3); - EXPECT_EQ(int(math_lib::divUp(T(20), T(19))), 2); - EXPECT_EQ(int(math_lib::divUp(T(20), T(20))), 1); - EXPECT_EQ(int(math_lib::divUp(T(20), T(21))), 1); - EXPECT_EQ(int(math_lib::divUp(T(20), T(22))), 1); - EXPECT_EQ(int(math_lib::divUp(T(50), T(24))), 3); - EXPECT_EQ(int(math_lib::divUp(T(50), T(25))), 2); - EXPECT_EQ(int(math_lib::divUp(T(50), T(26))), 2); - EXPECT_EQ(int(math_lib::divUp(T(50), T(27))), 2); -} - -TEST(TestCoreMathLib, DivUp) { - testDivUp(); - testDivUp(); - testDivUp(); - testDivUp(); -} diff --git a/test/network_messages.cpp b/test/network_messages.cpp deleted file mode 100644 index 7994e4b0b..000000000 --- a/test/network_messages.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "gtest/gtest.h" - -#define NETWORK_MESSAGES_WITHOUT_CORE_DEPENDENCIES -#include "../src/network_messages/all.h" - - -TEST(TestCoreRequestResponseHeader, TestSize) { - RequestResponseHeader hdr; - memset(& hdr, 0, sizeof(hdr)); - EXPECT_EQ(0, hdr.size()); - EXPECT_EQ(0, hdr.type()); - EXPECT_EQ(0, hdr.dejavu()); - - EXPECT_TRUE(hdr.checkAndSetSize(1)); - EXPECT_EQ(1, hdr.size()); - - hdr.setSize<10>(); - EXPECT_EQ(10, hdr.size()); - - EXPECT_TRUE(hdr.checkAndSetSize(13579864)); - EXPECT_EQ(13579864, hdr.size()); - - hdr.setSize<9876543>(); - EXPECT_EQ(9876543, hdr.size()); - - EXPECT_TRUE(hdr.checkAndSetSize(0xffffff)); - EXPECT_EQ(0xffffff, hdr.size()); - - // maximum size is 0xffffff = 16777215 - EXPECT_FALSE(hdr.checkAndSetSize(RequestResponseHeader::max_size + 1)); - EXPECT_FALSE(hdr.checkAndSetSize(RequestResponseHeader::max_size * 2)); -} - -TEST(TestCoreRequestResponseHeader, TestPayload) { - RequestResponseHeader hdr; - memset(&hdr, 0, sizeof(hdr)); - EXPECT_EQ(hdr.getPayload(), ((char*)&hdr) + sizeof(RequestResponseHeader)); - - EXPECT_TRUE(hdr.checkAndSetSize(1234 + sizeof(RequestResponseHeader))); - EXPECT_TRUE(hdr.checkPayloadSize(1234)); - EXPECT_FALSE(hdr.checkPayloadSize(1235)); - EXPECT_TRUE(hdr.checkPayloadSizeMinMax(1234, 1234)); - EXPECT_TRUE(hdr.checkPayloadSizeMinMax(123, 1234)); - EXPECT_TRUE(hdr.checkPayloadSizeMinMax(1234, 12346)); - EXPECT_TRUE(hdr.checkPayloadSizeMinMax(123, 12346)); - EXPECT_FALSE(hdr.checkPayloadSizeMinMax(1235, 1235)); - EXPECT_FALSE(hdr.checkPayloadSizeMinMax(12, 13)); - EXPECT_FALSE(hdr.checkPayloadSizeMinMax(13, 12)); - EXPECT_EQ(hdr.getPayloadSize(), 1234); -} - -TEST(TestCoreCommonDef, IPv4Address) { - const unsigned char ip_char_array[4] = { 1, 2, 3, 4 }; - const IPv4Address& ip_ref = *reinterpret_cast(ip_char_array); - EXPECT_EQ(ip_ref.u8[0], 1); - EXPECT_EQ(ip_ref.u8[1], 2); - EXPECT_EQ(ip_ref.u8[2], 3); - EXPECT_EQ(ip_ref.u8[3], 4); - EXPECT_EQ(ip_ref.u32, 0x04030201); - - IPv4Address ip2 {{100, 200, 32, 4}}; - EXPECT_TRUE(ip_ref != ip2); - EXPECT_FALSE(ip_ref == ip2); - - ip2 = ip_ref; - EXPECT_TRUE(ip_ref == ip2); - EXPECT_FALSE(ip_ref != ip2); -} diff --git a/test/oracle_engine.cpp b/test/oracle_engine.cpp deleted file mode 100644 index a44c84e70..000000000 --- a/test/oracle_engine.cpp +++ /dev/null @@ -1,1306 +0,0 @@ -#define NO_UEFI - -#include "oracle_testing.h" - -#include "platform/random.h" - - -struct OracleEngineTest : public LoggingTest -{ - OracleEngineTest() - { - EXPECT_TRUE(initSpectrum()); - EXPECT_TRUE(commonBuffers.init(1, 1024 * 1024)); - EXPECT_TRUE(initSpecialEntities()); - EXPECT_TRUE(initContractExec()); - EXPECT_TRUE(ts.init()); - EXPECT_TRUE(OI::initOracleInterfaces()); - - // init computors - for (int computorIndex = 0; computorIndex < NUMBER_OF_COMPUTORS; computorIndex++) - { - broadcastedComputors.computors.publicKeys[computorIndex] = m256i(computorIndex * 2, 42, 13, 1337); - } - - // setup tick and time - system.tick = 1000; - etalonTick.year = 25; - etalonTick.month = 12; - etalonTick.day = 15; - etalonTick.hour = 16; - etalonTick.minute = 51; - etalonTick.second = 12; - ts.beginEpoch(system.tick); - } - - ~OracleEngineTest() - { - deinitSpectrum(); - commonBuffers.deinit(); - deinitContractExec(); - ts.deinit(); - } -}; - -template -struct OracleEngineWithInitAndDeinit : public OracleEngine -{ - OracleEngineWithInitAndDeinit(const m256i* ownComputorPublicKeys) - { - this->init(ownComputorPublicKeys); - } - - ~OracleEngineWithInitAndDeinit() - { - this->deinit(); - } - - void checkPendingState(int64_t queryId, uint16_t totalCommitTxExecuted, uint16_t ownCommitTxExecuted, uint8_t expectedStatus) const - { - uint32_t queryIndex; - EXPECT_TRUE(this->queryIdToIndex->get(queryId, queryIndex)); - EXPECT_LT(queryIndex, this->oracleQueryCount); - const OracleQueryMetadata& oqm = this->queries[queryIndex]; - EXPECT_EQ(oqm.status, expectedStatus); - EXPECT_TRUE(oqm.status == ORACLE_QUERY_STATUS_PENDING || oqm.status == ORACLE_QUERY_STATUS_COMMITTED); - const OracleReplyState& replyState = this->replyStates[oqm.statusVar.pending.replyStateIndex]; - EXPECT_EQ((int)totalCommitTxExecuted, (int)replyState.totalCommits); - EXPECT_EQ((int)ownCommitTxExecuted, (int)replyState.ownReplyCommitExecCount); - EXPECT_EQ(this->getOracleQueryStatus(queryId), expectedStatus); - } - - void checkStatus(int64_t queryId, uint8_t expectedStatus) const - { - uint32_t queryIndex; - EXPECT_TRUE(this->queryIdToIndex->get(queryId, queryIndex)); - EXPECT_LT(queryIndex, this->oracleQueryCount); - const OracleQueryMetadata& oqm = this->queries[queryIndex]; - EXPECT_EQ(oqm.status, expectedStatus); - } - - // Test findFirstQueryIndexOfTick(). Ticks must be sorted! - void testFindFirstQueryIndexOfTick(const std::vector& ticks) - { - // setup pseudo-queries - this->oracleQueryCount = 0; - for (auto tick : ticks) - this->queries[this->oracleQueryCount++].queryTick = tick; - - // test function with all values in range - uint32_t minValue = (ticks.empty()) ? 0 : ticks.front() - 1; - uint32_t maxValue = (ticks.empty()) ? 2 : ticks.back() + 1; - for (uint32 value = minValue; value <= maxValue; ++value) - { - auto it = std::find(ticks.begin(), ticks.end(), value); - uint32_t expectedIndex = (it == ticks.end()) ? UINT32_MAX : uint32(it - ticks.begin()); - EXPECT_EQ(this->findFirstQueryIndexOfTick(value), expectedIndex); - } - - this->reset(); - } -}; - -static void dummyNotificationProc(const QPI::QpiContextProcedureCall&, void* state, void* input, void* output, void* locals) -{ -} - -TEST(OracleEngine, ContractQuerySuccess) -{ - OracleEngineTest test; - - // simulate three nodes: one with 400 computor IDs, one with 200, and one with 76 - const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; - OracleEngineWithInitAndDeinit<400> oracleEngine1(allCompPubKeys); - OracleEngineWithInitAndDeinit<200> oracleEngine2(allCompPubKeys + 400); - OracleEngineWithInitAndDeinit<76> oracleEngine3(allCompPubKeys + 600); - - OI::Price::OracleQuery priceQuery; - priceQuery.oracle = m256i(1, 2, 3, 4); - priceQuery.currency1 = m256i(2, 3, 4, 5); - priceQuery.currency2 = m256i(3, 4, 5, 6); - priceQuery.timestamp = QPI::DateAndTime::now(); - QPI::uint32 interfaceIndex = 0; - QPI::uint16 contractIndex = 1; - QPI::uint32 timeout = 30000; - const QPI::uint32 notificationProcId = 12345; - EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 128, 128, 1 })); - - //------------------------------------------------------------------------- - // start contract query / check message to OM node - QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); - EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, 0)); - checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); - EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); - EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); - - //------------------------------------------------------------------------- - // get query contract data - OI::Price::OracleQuery priceQueryReturned; - EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); - EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); - - //------------------------------------------------------------------------- - // process simulated reply from OM node - struct - { - OracleMachineReply metadata; - OI::Price::OracleReply data; - } priceOracleMachineReply; - - priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; - priceOracleMachineReply.metadata.oracleQueryId = queryId; - priceOracleMachineReply.data.numerator = 1234; - priceOracleMachineReply.data.denominator = 1; - - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - oracleEngine3.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - // duplicate from other node - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - // other value from other node - priceOracleMachineReply.data.numerator = 1233; - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - //------------------------------------------------------------------------- - // create reply commit tx (with local computor index 0 / global computor index 0) - uint8_t txBuffer[MAX_TRANSACTION_SIZE]; - auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, 0, system.tick + 3, 0), UINT32_MAX); - { - EXPECT_EQ((int)replyCommitTx->inputType, (int)OracleReplyCommitTransactionPrefix::transactionType()); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[0]); - EXPECT_TRUE(isZero(replyCommitTx->destinationPublicKey)); - EXPECT_EQ(replyCommitTx->tick, system.tick + 3); - EXPECT_EQ((int)replyCommitTx->inputSize, (int)sizeof(OracleReplyCommitTransactionItem)); - } - - // second call in the same tick: no commits for tx - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, 0, system.tick + 3, 0), 0); - - // process commit tx - system.tick += 3; - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - - // no reveal yet - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - // no notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - - //------------------------------------------------------------------------- - // create and process enough reply commit tx to trigger reveal tx - - // create tx of node 3 computers and process in all nodes - for (int i = 600; i < 676; ++i) - { - EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, i, i - 600, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); - const int txFromNode3 = i - 600; - oracleEngine1.checkPendingState(queryId, txFromNode3 + 1, 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, txFromNode3 + 2, 1, ORACLE_QUERY_STATUS_PENDING); - oracleEngine2.checkPendingState(queryId, txFromNode3 + 1, 0, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, txFromNode3 + 2, 0, ORACLE_QUERY_STATUS_PENDING); - oracleEngine3.checkPendingState(queryId, txFromNode3 + 1, txFromNode3 + 0, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, txFromNode3 + 2, txFromNode3 + 1, ORACLE_QUERY_STATUS_PENDING); - } - - // create tx of node 2 computers and process in all nodes - for (int i = 400; i < 600; ++i) - { - EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, i, i - 400, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); - const int txFromNode2 = i - 400; - oracleEngine1.checkPendingState(queryId, txFromNode2 + 77, 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, txFromNode2 + 78, 1, ORACLE_QUERY_STATUS_PENDING); - oracleEngine2.checkPendingState(queryId, txFromNode2 + 77, txFromNode2 + 0, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, txFromNode2 + 78, txFromNode2 + 1, ORACLE_QUERY_STATUS_PENDING); - oracleEngine3.checkPendingState(queryId, txFromNode2 + 77, 76, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, txFromNode2 + 78, 76, ORACLE_QUERY_STATUS_PENDING); - } - - // create tx of node 1 computers and process in all nodes - for (int i = 1; i < 400; ++i) - { - bool expectStatusCommitted = (i + 276) >= 451; - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, i, i, system.tick + 3, 0), ((expectStatusCommitted) ? 0 : UINT32_MAX)); - if (!expectStatusCommitted) - { - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); - const int txFromNode1 = i; - uint8_t newStatus = (txFromNode1 + 276 < 450) ? ORACLE_QUERY_STATUS_PENDING : ORACLE_QUERY_STATUS_COMMITTED; - oracleEngine1.checkPendingState(queryId, txFromNode1 + 276, txFromNode1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, txFromNode1 + 277, txFromNode1 + 1, newStatus); - oracleEngine2.checkPendingState(queryId, txFromNode1 + 276, 200, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, txFromNode1 + 277, 200, newStatus); - oracleEngine3.checkPendingState(queryId, txFromNode1 + 276, 76, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, txFromNode1 + 277, 76, newStatus); - } - else - { - oracleEngine1.checkPendingState(queryId, 451, 175, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine2.checkPendingState(queryId, 451, 200, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine3.checkPendingState(queryId, 451, 76, ORACLE_QUERY_STATUS_COMMITTED); - } - } - - //------------------------------------------------------------------------- - // reply reveal tx - - // success for one tx - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); - - // second call does not provide the same tx again - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - system.tick += 3; - auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; - const unsigned int txIndex = 10; - addOracleTransactionToTickStorage(replyRevealTx, txIndex); - oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndex); - - //------------------------------------------------------------------------- - // notifications - const OracleNotificationData* notification = oracleEngine1.getNotification(); - EXPECT_NE(notification, nullptr); - EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); - EXPECT_EQ(notification->procedureId, notificationProcId); - EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); - const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; - EXPECT_EQ(notificationInput->queryId, replyRevealTx->queryId); - EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_SUCCESS); - EXPECT_EQ(notificationInput->subscriptionId, 0); - EXPECT_EQ(notificationInput->reply.numerator, 1234); - EXPECT_EQ(notificationInput->reply.denominator, 1); - - // no additional notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); - - OI::Price::OracleReply reply; - EXPECT_TRUE(oracleEngine1.getOracleReply(queryId, &reply, sizeof(reply))); - EXPECT_TRUE(compareMem(&reply, ¬ificationInput->reply, sizeof(reply)) == 0); - - // oracleEngine2 did not process reveal -> no success / reply - EXPECT_EQ(oracleEngine2.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_COMMITTED); - EXPECT_FALSE(oracleEngine2.getOracleReply(queryId, &reply, sizeof(reply))); - - //------------------------------------------------------------------------- - // revenue - OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); - OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); - OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - // first 451 commit messages got through and are all correct: - // - all of node 3 (computor 600-675) - // - all of node 2 (computor 400-599) - // - first 451-276 of node 1 (computor 0-174) - bool gotCommit = (i >= 400) || (i <= 174); - EXPECT_EQ(rev1.computorRevPoints[i], (gotCommit) ? 1 : 0); - - // no reveal processed in node 2 and 3 - EXPECT_EQ(rev2.computorRevPoints[i], 0); - EXPECT_EQ(rev3.computorRevPoints[i], 0); - } - - // check that oracle engine is in consistent state - oracleEngine1.checkStateConsistencyWithAssert(); - oracleEngine2.checkStateConsistencyWithAssert(); - oracleEngine3.checkStateConsistencyWithAssert(); -} - -TEST(OracleEngine, ContractQueryUnresolvable) -{ - // 2 nodes send 200 commits each with agreeing digest - // 1 node sends 276 commits with different digest - // -> no quroum can be reached and status changes from pending to unresolvable directly - // -> no reveal / nobody gets revenue - - OracleEngineTest test; - - // simulate three nodes: two with 200 computor IDs each, one with 276 IDs - const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; - OracleEngineWithInitAndDeinit<200> oracleEngine1(allCompPubKeys); - OracleEngineWithInitAndDeinit<200> oracleEngine2(allCompPubKeys + 200); - OracleEngineWithInitAndDeinit<276> oracleEngine3(allCompPubKeys + 400); - - - OI::Price::OracleQuery priceQuery; - priceQuery.oracle = m256i(10, 20, 30, 40); - priceQuery.currency1 = m256i(20, 30, 40, 50); - priceQuery.currency2 = m256i(30, 40, 50, 60); - priceQuery.timestamp = QPI::DateAndTime::now(); - QPI::uint32 interfaceIndex = 0; - QPI::uint16 contractIndex = 2; - QPI::uint32 timeout = 120000; - const QPI::uint32 notificationProcId = 12345; - EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 1024, 128, 1 })); - - //------------------------------------------------------------------------- - // start contract query / check message to OM node - QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); - EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, 0)); - EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); - EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); - checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); - - //------------------------------------------------------------------------- - // get query contract data - OI::Price::OracleQuery priceQueryReturned; - EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); - EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); - - //------------------------------------------------------------------------- - // process simulated reply from OM nodes - struct - { - OracleMachineReply metadata; - OI::Price::OracleReply data; - } priceOracleMachineReply; - - // reply received/committed by node 1 and 2 - priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; - priceOracleMachineReply.metadata.oracleQueryId = queryId; - priceOracleMachineReply.data.numerator = 1234; - priceOracleMachineReply.data.denominator = 1; - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - // reply received/committed by node 3 - priceOracleMachineReply.data.numerator = 1233; - priceOracleMachineReply.data.denominator = 1; - oracleEngine3.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - - //------------------------------------------------------------------------- - // create and process reply commits of node 3 computers and process in all nodes - uint8_t txBuffer[MAX_TRANSACTION_SIZE]; - auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; - for (int ownCompIdx = 0; ownCompIdx < 200; ++ownCompIdx) - { - int allCompIdx = ownCompIdx; - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); - - allCompIdx = ownCompIdx + 200; - EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); - - allCompIdx = ownCompIdx + 400; - EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - } - - // create/process transactions that contradict with majority digest and turn status into unresolvable - for (int allCompIdx = 600; allCompIdx < 676; ++allCompIdx) - { - int ownCompIdx = allCompIdx - 400; - int unknownVotes = 676 - allCompIdx; - bool moreTxExpected = (unknownVotes > 450 - 400); - EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), moreTxExpected ? UINT32_MAX : 0); - if (moreTxExpected) - { - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - } - - if (unknownVotes > 451 - 400) - { - oracleEngine1.checkPendingState(queryId, allCompIdx + 1, 200, ORACLE_QUERY_STATUS_PENDING); - oracleEngine2.checkPendingState(queryId, allCompIdx + 1, 200, ORACLE_QUERY_STATUS_PENDING); - oracleEngine3.checkPendingState(queryId, allCompIdx + 1, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - } - else - { - oracleEngine1.checkStatus(queryId, ORACLE_QUERY_STATUS_UNRESOLVABLE); - oracleEngine2.checkStatus(queryId, ORACLE_QUERY_STATUS_UNRESOLVABLE); - oracleEngine3.checkStatus(queryId, ORACLE_QUERY_STATUS_UNRESOLVABLE); - } - } - - //------------------------------------------------------------------------- - // notifications - const OracleNotificationData* notification = oracleEngine1.getNotification(); - EXPECT_NE(notification, nullptr); - EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); - EXPECT_EQ(notification->procedureId, notificationProcId); - EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); - const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; - EXPECT_EQ(notificationInput->queryId, queryId); - EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_UNRESOLVABLE); - EXPECT_EQ(notificationInput->subscriptionId, 0); - EXPECT_EQ(notificationInput->reply.numerator, 0); - EXPECT_EQ(notificationInput->reply.denominator, 0); - - // no additional notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_UNRESOLVABLE); - - //------------------------------------------------------------------------- - // revenue - OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); - OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); - OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - // no reveal - EXPECT_EQ(rev1.computorRevPoints[i], 0); - EXPECT_EQ(rev2.computorRevPoints[i], 0); - EXPECT_EQ(rev3.computorRevPoints[i], 0); - } - - // check that oracle engine is in consistent state - oracleEngine1.checkStateConsistencyWithAssert(); - oracleEngine2.checkStateConsistencyWithAssert(); - oracleEngine3.checkStateConsistencyWithAssert(); -} - -TEST(OracleEngine, ContractQueryWrongKnowledgeProof) -{ - // 3 nodes send 451 commits with agreeing digest, but 150 of them have - // a wrong knowledge proof -> status gets unresolvable, but the computors - // with correct knowledge proof get a revenue point - - OracleEngineTest test; - - // simulate three nodes: two with 200 computor IDs each, one with 276 IDs - const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; - OracleEngineWithInitAndDeinit<200> oracleEngine1(allCompPubKeys); - OracleEngineWithInitAndDeinit<200> oracleEngine2(allCompPubKeys + 200); - OracleEngineWithInitAndDeinit<276> oracleEngine3(allCompPubKeys + 400); - - - OI::Price::OracleQuery priceQuery; - priceQuery.oracle = m256i(10, 20, 30, 40); - priceQuery.currency1 = m256i(20, 30, 40, 50); - priceQuery.currency2 = m256i(30, 40, 50, 60); - priceQuery.timestamp = QPI::DateAndTime::now(); - QPI::uint32 interfaceIndex = 0; - QPI::uint16 contractIndex = 2; - QPI::uint32 timeout = 120000; - const QPI::uint32 notificationProcId = 12345; - EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 1024, 128, 1 })); - - //------------------------------------------------------------------------- - // start contract query / check message to OM node - QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); - EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, 0)); - EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); - EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); - checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); - - //------------------------------------------------------------------------- - // get query contract data - OI::Price::OracleQuery priceQueryReturned; - EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); - EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); - - //------------------------------------------------------------------------- - // process simulated reply from OM nodes - struct - { - OracleMachineReply metadata; - OI::Price::OracleReply data; - } priceOracleMachineReply; - - // reply received/committed - priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; - priceOracleMachineReply.metadata.oracleQueryId = queryId; - priceOracleMachineReply.data.numerator = 1234; - priceOracleMachineReply.data.denominator = 1; - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - oracleEngine3.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - //------------------------------------------------------------------------- - // create and process reply commits of node 3 computers and process in all nodes - uint8_t txBuffer[MAX_TRANSACTION_SIZE]; - auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; - for (int ownCompIdx = 0; ownCompIdx < 200; ++ownCompIdx) - { - int allCompIdx = ownCompIdx; - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - uint8_t expectedStatus = (3 * ownCompIdx + 1 < 451) ? ORACLE_QUERY_STATUS_PENDING : ORACLE_QUERY_STATUS_COMMITTED; - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx + 1, expectedStatus); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, expectedStatus); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, expectedStatus); - - if (3 * ownCompIdx + 1 == 451) - { - // After status switched to committed, getReplyCommitTransaction() won't return more tx because these - // would be to late to get revenue anyway. - allCompIdx = ownCompIdx + 200; - EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), 0); - allCompIdx = ownCompIdx + 400; - EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), 0); - break; - } - - allCompIdx = ownCompIdx + 200; - EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); - - allCompIdx = ownCompIdx + 400; - EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, ownCompIdx, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); - - // manipulate knowledge proof of computor 400-600 to simulate that computors just echo the commit of - // other computors without actually having the oracle reply - auto* commit = reinterpret_cast(replyCommitTx->inputPtr()); - commit->replyKnowledgeProof.u64._3 += 2; - - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); - } - - //------------------------------------------------------------------------- - // reply reveal tx - - // success for one tx - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); - - // second call does not provide the same tx again - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_COMMITTED); - - system.tick += 3; - auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; - const unsigned int txIndex = 10; - addOracleTransactionToTickStorage(replyRevealTx, txIndex); - oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndex); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_UNRESOLVABLE); - - //------------------------------------------------------------------------- - // notifications - const OracleNotificationData* notification = oracleEngine1.getNotification(); - EXPECT_NE(notification, nullptr); - EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); - EXPECT_EQ(notification->procedureId, notificationProcId); - EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); - const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; - EXPECT_EQ(notificationInput->queryId, queryId); - EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_UNRESOLVABLE); - EXPECT_EQ(notificationInput->subscriptionId, 0); - EXPECT_EQ(notificationInput->reply.numerator, 0); - EXPECT_EQ(notificationInput->reply.denominator, 0); - - // no additional notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_UNRESOLVABLE); - - //------------------------------------------------------------------------- - // revenue - OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); - OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); - OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - // In reveal, computors with correct knowledge proof get a point and those - // with wrong knowledge proof don't get one: - // - first 151 of node 1 got through and get rev (0-150) - // - first 150 of node 2 got through and get rev (200-349) - // - first 150 of node 3 get no rev due to wrong knowledge proof - // - other don't get rev, because no commit got through - bool node1rev = i <= 150; - bool node2rev = i >= 200 && i <= 349; - uint64_t expectedRevPoints = (node1rev || node2rev) ? 1 : 0; - EXPECT_EQ(rev1.computorRevPoints[i], expectedRevPoints); - - // no reveal - EXPECT_EQ(rev2.computorRevPoints[i], 0); - EXPECT_EQ(rev3.computorRevPoints[i], 0); - } - - // check that oracle engine is in consistent state - oracleEngine1.checkStateConsistencyWithAssert(); - oracleEngine2.checkStateConsistencyWithAssert(); - oracleEngine3.checkStateConsistencyWithAssert(); -} - -TEST(OracleEngine, ContractQueryTimeout) -{ - // no response from OM node - // -> pending to timeout directly - // -> no reveal / nobody gets revenue - - OracleEngineTest test; - - // simulate one node - const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; - OracleEngineWithInitAndDeinit<676> oracleEngine1(allCompPubKeys); - - OI::Price::OracleQuery priceQuery; - priceQuery.oracle = m256i(10, 20, 30, 40); - priceQuery.currency1 = m256i(20, 30, 40, 50); - priceQuery.currency2 = m256i(30, 40, 50, 60); - priceQuery.timestamp = QPI::DateAndTime::now(); - QPI::uint32 interfaceIndex = 0; - QPI::uint16 contractIndex = 2; - QPI::uint32 timeout = 10000; - const QPI::uint32 notificationProcId = 12345; - EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 1024, 128, 1 })); - - //------------------------------------------------------------------------- - // start contract query / check message to OM node - QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); - checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); - - //------------------------------------------------------------------------- - // get query contract data - OI::Price::OracleQuery priceQueryReturned; - EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); - EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); - - //------------------------------------------------------------------------- - // timeout: no response from OM node - ++system.tick; - ++etalonTick.hour; - oracleEngine1.processTimeouts(); - - //------------------------------------------------------------------------- - // notifications - const OracleNotificationData* notification = oracleEngine1.getNotification(); - EXPECT_NE(notification, nullptr); - EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); - EXPECT_EQ(notification->procedureId, notificationProcId); - EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); - const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; - EXPECT_EQ(notificationInput->queryId, queryId); - EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_TIMEOUT); - EXPECT_EQ(notificationInput->subscriptionId, 0); - EXPECT_EQ(notificationInput->reply.numerator, 0); - EXPECT_EQ(notificationInput->reply.denominator, 0); - - // no additional notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_TIMEOUT); - - //------------------------------------------------------------------------- - // revenue - OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - // no reveal - EXPECT_EQ(rev1.computorRevPoints[i], 0); - } - - // check that oracle engine is in consistent state - oracleEngine1.checkStateConsistencyWithAssert(); -} - -template -static void checkReplyCommitTransactions( - OracleEngine& oracleEngine, int globalCompIdxBegin, int globalCompIdxEnd, - const std::vector& queryIds, QPI::sint64 queryIdWithoutReply) -{ - uint8_t txBuffer[MAX_TRANSACTION_SIZE]; - auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; - - // create tx of node 3 computers and process in all nodes - for (int globalCompIdx = globalCompIdxBegin; globalCompIdx < globalCompIdxEnd; ++globalCompIdx) - { - std::set pendingCommitQueryIds; - pendingCommitQueryIds.insert(queryIds.begin(), queryIds.end()); - - unsigned int retCode = 0; - do - { - retCode = oracleEngine.getReplyCommitTransaction(txBuffer, globalCompIdx, globalCompIdx - globalCompIdxBegin, system.tick + 3, retCode); - if (!retCode) - break; - unsigned short commitCount = replyCommitTx->inputSize / sizeof(OracleReplyCommitTransactionItem); - auto* commits = reinterpret_cast(replyCommitTx->inputPtr()); - for (unsigned short i = 0; i < commitCount; ++i) - { - pendingCommitQueryIds.erase(commits[i].queryId); - } - } while (retCode != UINT32_MAX); - - // only the query without OM reply is not returned - EXPECT_EQ(pendingCommitQueryIds.size(), 1); - EXPECT_TRUE(pendingCommitQueryIds.contains(queryIdWithoutReply)); - } -} - -TEST(OracleEngine, MultiContractQuerySuccess) -{ - OracleEngineTest test; - - // simulate three nodes: one with 350 computor IDs, one with 250, and one with 76 - const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; - OracleEngineWithInitAndDeinit<350> oracleEngine1(allCompPubKeys); - OracleEngineWithInitAndDeinit<250> oracleEngine2(allCompPubKeys + 350); - OracleEngineWithInitAndDeinit<76> oracleEngine3(allCompPubKeys + 600); - - QPI::uint16 contractIndex = 4; - QPI::uint32 timeout = 50000; - const QPI::uint32 notificationProcId = 12345; - EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 128, 128, 1 })); - - constexpr int queryCount = 25; - QPI::uint16 interfaceIndex = OI::Mock::oracleInterfaceIndex; - OI::Mock::OracleQuery mockQuery; - - //------------------------------------------------------------------------- - // start contract query / check message to OM node / check query - std::vector queryIds; - for (int i = 0; i < queryCount; ++i) - { - mockQuery.value = i + 1000; - QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &mockQuery, sizeof(mockQuery), timeout, notificationProcId); - EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, i)); - EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &mockQuery, sizeof(mockQuery), timeout, notificationProcId)); - EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &mockQuery, sizeof(mockQuery), timeout, notificationProcId)); - - checkNetworkMessageOracleMachineQuery(queryId, timeout, mockQuery); - - OI::Mock::OracleQuery mockQueryReturned; - EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &mockQueryReturned, sizeof(mockQueryReturned))); - EXPECT_EQ(memcmp(&mockQueryReturned, &mockQuery, sizeof(mockQuery)), 0); - - queryIds.push_back(queryId); - } - - //------------------------------------------------------------------------- - // process simulated reply from OM node - struct - { - OracleMachineReply metadata; - OI::Mock::OracleReply data; - } oracleMachineReply; - - for (int i = 0; i < queryCount; ++i) - { - oracleMachineReply.metadata.oracleMachineErrorFlags = 0; - oracleMachineReply.metadata.oracleQueryId = queryIds[i]; - oracleMachineReply.data.echoedValue = 1000 + i; - oracleMachineReply.data.doubledValue = (1000 + i) * 2; - - // the first 3 queries don't get OM reply all oracleEngines - if (i != 0) - oracleEngine1.processOracleMachineReply(&oracleMachineReply.metadata, sizeof(oracleMachineReply)); - if (i != 1) - oracleEngine2.processOracleMachineReply(&oracleMachineReply.metadata, sizeof(oracleMachineReply)); - if (i != 2) - oracleEngine3.processOracleMachineReply(&oracleMachineReply.metadata, sizeof(oracleMachineReply)); - } - - //------------------------------------------------------------------------- - // create and process reply commit transactions - - // get all commit tx for checking that correct set of commits is returned - - checkReplyCommitTransactions(oracleEngine1, 0, 350, queryIds, queryIds[0]); - checkReplyCommitTransactions(oracleEngine2, 350, 600, queryIds, queryIds[1]); - checkReplyCommitTransactions(oracleEngine3, 600, 676, queryIds, queryIds[2]); - - // advance few ticks in order to get commit tx returned again for processing the commits - system.tick += 5; - - uint8_t txBuffer[MAX_TRANSACTION_SIZE]; - auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; - - unsigned int txIndexInTickData = 0; - - int globalCompIdxBeginEnd[] = { 0, 350, 600, 676 }; - for (int oracleEngineIdx = 0; oracleEngineIdx < 3; ++oracleEngineIdx) - { - for (int globalCompIdx = globalCompIdxBeginEnd[oracleEngineIdx]; - globalCompIdx < globalCompIdxBeginEnd[oracleEngineIdx + 1]; - ++globalCompIdx) - { - unsigned int retCode = 0; - do - { - if (oracleEngineIdx == 0) - retCode = oracleEngine1.getReplyCommitTransaction( - txBuffer, globalCompIdx, globalCompIdx - globalCompIdxBeginEnd[oracleEngineIdx], - system.tick + 3, retCode); - else if (oracleEngineIdx == 1) - retCode = oracleEngine2.getReplyCommitTransaction( - txBuffer, globalCompIdx, globalCompIdx - globalCompIdxBeginEnd[oracleEngineIdx], - system.tick + 3, retCode); - else - retCode = oracleEngine3.getReplyCommitTransaction( - txBuffer, globalCompIdx, globalCompIdx - globalCompIdxBeginEnd[oracleEngineIdx], - system.tick + 3, retCode); - if (!retCode) - break; - - // store commit tx in tick storage for processing tx later - addOracleTransactionToTickStorage(replyCommitTx, txIndexInTickData); - txIndexInTickData++; - } while (retCode != UINT32_MAX); - } - - // each engine send its txs in a different tick here - ++system.tick; - txIndexInTickData = 0; - } - - // process commit txs - ts.checkStateConsistencyWithAssert(); - for (int tick = 0; tick < 3; ++tick) - { - const unsigned long long* tsTickTransactionOffsets = ts.tickTransactionOffsets.getByTickInCurrentEpoch(system.tick); - const unsigned long long* tsTickTransactionOffsets2 = ts.tickTransactionOffsets.getByTickInCurrentEpoch(system.tick + 1); - const unsigned long long* tsTickTransactionOffsets3 = ts.tickTransactionOffsets.getByTickInCurrentEpoch(system.tick + 2); - for (txIndexInTickData = 0; txIndexInTickData < NUMBER_OF_TRANSACTIONS_PER_TICK; ++txIndexInTickData) - { - const unsigned long long offset = tsTickTransactionOffsets[txIndexInTickData]; - if (offset) - { - const auto* commitTx = (OracleReplyCommitTransactionPrefix*)ts.tickTransactions.ptr(offset); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(commitTx)); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(commitTx)); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(commitTx)); - } - } - ++system.tick; - } - - // check status - oracleEngine1.checkPendingState(queryIds[0], 326, 0, ORACLE_QUERY_STATUS_PENDING); - oracleEngine2.checkPendingState(queryIds[0], 326, 250, ORACLE_QUERY_STATUS_PENDING); - oracleEngine3.checkPendingState(queryIds[0], 326, 76, ORACLE_QUERY_STATUS_PENDING); - - oracleEngine1.checkPendingState(queryIds[1], 426, 350, ORACLE_QUERY_STATUS_PENDING); - oracleEngine2.checkPendingState(queryIds[1], 426, 0, ORACLE_QUERY_STATUS_PENDING); - oracleEngine3.checkPendingState(queryIds[1], 426, 76, ORACLE_QUERY_STATUS_PENDING); - - oracleEngine1.checkPendingState(queryIds[2], 600, 350, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine2.checkPendingState(queryIds[2], 600, 250, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine3.checkPendingState(queryIds[2], 600, 0, ORACLE_QUERY_STATUS_COMMITTED); - - for (int i = 3; i < queryCount; ++i) - { - oracleEngine1.checkPendingState(queryIds[i], 676, 350, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine2.checkPendingState(queryIds[i], 676, 250, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine3.checkPendingState(queryIds[i], 676, 76, ORACLE_QUERY_STATUS_COMMITTED); - } - - //------------------------------------------------------------------------- - // reply reveal tx - - std::set pendingRevealQueryIds; - pendingRevealQueryIds.insert(queryIds.begin() + 2, queryIds.end()); - - // generate all reveal of oracleEngine3 - auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; - unsigned int retCode = 0; - std::vector revealTxs; - txIndexInTickData = 0; - while ((retCode = oracleEngine3.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, retCode)) != 0) - { - oracleEngine1.announceExpectedRevealTransaction(replyRevealTx); - oracleEngine2.announceExpectedRevealTransaction(replyRevealTx); - oracleEngine3.announceExpectedRevealTransaction(replyRevealTx); - - // save pointer to reveal tx in tick storage for processing tx later - revealTxs.push_back((OracleReplyRevealTransactionPrefix*)addOracleTransactionToTickStorage(replyRevealTx, txIndexInTickData)); - txIndexInTickData++; - } - - // process reveal tx - system.tick += 3; - for (txIndexInTickData = 0; txIndexInTickData < revealTxs.size(); ++txIndexInTickData) - { - const auto* revealTx = revealTxs[txIndexInTickData]; - pendingRevealQueryIds.erase(revealTx->queryId); - - oracleEngine1.processOracleReplyRevealTransaction(revealTx, txIndexInTickData); - oracleEngine2.processOracleReplyRevealTransaction(revealTx, txIndexInTickData); - oracleEngine3.processOracleReplyRevealTransaction(revealTx, txIndexInTickData); - } - - // no reveal possible for oracleEngine3 in query 2, because it didn't get OM reply - EXPECT_EQ(pendingRevealQueryIds.size(), 1); - EXPECT_TRUE(pendingRevealQueryIds.contains(queryIds[2])); - - // reveal query 2 using oracleEngine2 - EXPECT_EQ(oracleEngine2.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); - EXPECT_EQ(replyRevealTx->queryId, queryIds[2]); - EXPECT_EQ(oracleEngine2.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); - addOracleTransactionToTickStorage(replyRevealTx, txIndexInTickData); - system.tick += 3; - oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndexInTickData); - oracleEngine2.processOracleReplyRevealTransaction(replyRevealTx, txIndexInTickData); - oracleEngine3.processOracleReplyRevealTransaction(replyRevealTx, txIndexInTickData); - - // nothing to reveal for oracleEngine1 - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - //------------------------------------------------------------------------- - // notifications - - std::set pendingNotificationQueryIds; - pendingNotificationQueryIds.insert(queryIds.begin() + 2, queryIds.end()); - - for (int i = 2; i < queryCount; ++i) - { - const OracleNotificationData* notification = oracleEngine1.getNotification(); - EXPECT_NE(notification, nullptr); - EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); - EXPECT_EQ(notification->procedureId, notificationProcId); - EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); - const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; - EXPECT_NE(notificationInput->queryId, 0); - EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_SUCCESS); - EXPECT_EQ(notificationInput->subscriptionId, 0); - - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(notificationInput->queryId), ORACLE_QUERY_STATUS_SUCCESS); - OI::Mock::OracleQuery query; - EXPECT_TRUE(oracleEngine1.getOracleQuery(notificationInput->queryId, &query, sizeof(query))); - - EXPECT_EQ(notificationInput->reply.echoedValue, query.value); - EXPECT_EQ(notificationInput->reply.doubledValue, query.value * 2); - - OI::Mock::OracleReply reply; - EXPECT_TRUE(oracleEngine1.getOracleReply(notificationInput->queryId, &reply, sizeof(reply))); - EXPECT_TRUE(compareMem(&reply, ¬ificationInput->reply, sizeof(reply)) == 0); - - pendingNotificationQueryIds.erase(notificationInput->queryId); - } - - // no additional notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - EXPECT_EQ(pendingNotificationQueryIds.size(), 0); - - //------------------------------------------------------------------------- - // revenue - OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); - OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); - OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - bool hadFastestCommits = (i < 600); - uint64_t expectedRevPoints = (hadFastestCommits) ? queryCount - 2 : 0; - EXPECT_EQ(rev1.computorRevPoints[i], expectedRevPoints); - EXPECT_EQ(rev2.computorRevPoints[i], expectedRevPoints); - EXPECT_EQ(rev3.computorRevPoints[i], expectedRevPoints); - } - - // check that oracle engine is in consistent state - oracleEngine1.checkStateConsistencyWithAssert(); - oracleEngine2.checkStateConsistencyWithAssert(); - oracleEngine3.checkStateConsistencyWithAssert(); -} - -/* -Tests: -- error conditions -*/ - -struct PriceQueryTransaction : public OracleUserQueryTransactionPrefix -{ - OI::Price::OracleQuery query; - uint8_t signature[SIGNATURE_SIZE]; -}; - -static PriceQueryTransaction getPriceQueryTransaction(const OI::Price::OracleQuery& query, const m256i& sourcePublicKey, int64_t fee, uint32_t timeout) -{ - PriceQueryTransaction tx; - tx.amount = fee; - tx.destinationPublicKey = m256i::zero(); - tx.inputSize = OracleUserQueryTransactionPrefix::minInputSize() + sizeof(query); - tx.inputType = OracleUserQueryTransactionPrefix::transactionType(); - tx.oracleInterfaceIndex = OI::Price::oracleInterfaceIndex; - tx.sourcePublicKey = sourcePublicKey; - tx.tick = system.tick; - tx.timeoutMilliseconds = timeout; - tx.query = query; - setMem(tx.signature, SIGNATURE_SIZE, 0); // keep signature uninitialized - return tx; -} - -TEST(OracleEngine, UserQuerySuccess) -{ - OracleEngineTest test; - - const id USER1(123, 456, 789, 876); - increaseEnergy(USER1, 10000000000); - - // simulate three nodes: one with 400 computor IDs, one with 200, and one with 76 - const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; - OracleEngineWithInitAndDeinit<400> oracleEngine1(allCompPubKeys); - OracleEngineWithInitAndDeinit<200> oracleEngine2(allCompPubKeys + 400); - OracleEngineWithInitAndDeinit<76> oracleEngine3(allCompPubKeys + 600); - - OI::Price::OracleQuery priceQuery; - priceQuery.oracle = m256i(42, 13, 100, 1000); - priceQuery.currency1 = m256i(20, 30, 40, 50); - priceQuery.currency2 = m256i(300, 400, 500, 600); - priceQuery.timestamp = QPI::DateAndTime::now(); - QPI::uint32 interfaceIndex = 0; - QPI::uint32 timeout = 40000; - QPI::sint64 fee = OI::Price::getQueryFee(priceQuery); - PriceQueryTransaction priceQueryTx = getPriceQueryTransaction(priceQuery, USER1, fee, timeout); - unsigned int priceQueryTxIndex = 15; - addOracleTransactionToTickStorage(&priceQueryTx, priceQueryTxIndex); - - const QPI::uint32 notificationProcId = 12345; - EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 128, 128, 1 })); - - //------------------------------------------------------------------------- - // start user query / check message to OM node - QPI::sint64 queryId = oracleEngine1.startUserQuery(&priceQueryTx, priceQueryTxIndex); - EXPECT_EQ(queryId, getUserOracleQueryId(system.tick, priceQueryTxIndex)); - checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); - EXPECT_EQ(queryId, oracleEngine2.startUserQuery(&priceQueryTx, priceQueryTxIndex)); - EXPECT_EQ(queryId, oracleEngine3.startUserQuery(&priceQueryTx, priceQueryTxIndex)); - - //------------------------------------------------------------------------- - // get query contract data - OI::Price::OracleQuery priceQueryReturned; - EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); - EXPECT_EQ(compareMem(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); - - //------------------------------------------------------------------------- - // process simulated reply from OM node - struct - { - OracleMachineReply metadata; - OI::Price::OracleReply data; - } priceOracleMachineReply; - - priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; - priceOracleMachineReply.metadata.oracleQueryId = queryId; - priceOracleMachineReply.data.numerator = 1234; - priceOracleMachineReply.data.denominator = 1; - - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - // test: no reply to oracle engine 3! - - // duplicate from other node - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - - // other value from other node - priceOracleMachineReply.data.numerator = 1233; - oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); - priceOracleMachineReply.data.numerator = 1234; - - //------------------------------------------------------------------------- - // create reply commit tx (with local computor index 0 / global computor index 0) - uint8_t txBuffer[MAX_TRANSACTION_SIZE]; - auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, 0, system.tick + 3, 0), UINT32_MAX); - { - EXPECT_EQ((int)replyCommitTx->inputType, (int)OracleReplyCommitTransactionPrefix::transactionType()); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[0]); - EXPECT_TRUE(isZero(replyCommitTx->destinationPublicKey)); - EXPECT_EQ(replyCommitTx->tick, system.tick + 3); - EXPECT_EQ((int)replyCommitTx->inputSize, (int)sizeof(OracleReplyCommitTransactionItem)); - } - - // second call in the same tick: no commits for tx - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, 0, system.tick + 3, 0), 0); - - // process commit tx - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - - // no reveal yet - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - // no notifications - EXPECT_EQ(oracleEngine1.getNotification(), nullptr); - - //------------------------------------------------------------------------- - // create and process enough reply commit tx to trigger reveal tx - - // create tx of node 3 computers? -> no commit tx because reply data is not available in node 3 (no OM reply) - for (int i = 600; i < 676; ++i) - { - EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, i, i - 600, system.tick + 3, 0), 0); - } - - // create tx of node 2 computers and process in all nodes - for (int i = 400; i < 600; ++i) - { - EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, i, i - 400, system.tick + 3, 0), UINT32_MAX); - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); - const int txFromNode2 = i - 400; - oracleEngine1.checkPendingState(queryId, txFromNode2 + 1, 1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, txFromNode2 + 2, 1, ORACLE_QUERY_STATUS_PENDING); - oracleEngine2.checkPendingState(queryId, txFromNode2 + 1, txFromNode2 + 0, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, txFromNode2 + 2, txFromNode2 + 1, ORACLE_QUERY_STATUS_PENDING); - oracleEngine3.checkPendingState(queryId, txFromNode2 + 1, 0, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, txFromNode2 + 2, 0, ORACLE_QUERY_STATUS_PENDING); - } - - // create tx of node 1 computers and process in all nodes - for (int i = 1; i < 400; ++i) - { - bool expectStatusCommitted = (i + 200) >= 451; - EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, i, i, system.tick + 3, 0), ((expectStatusCommitted) ? 0 : UINT32_MAX)); - if (!expectStatusCommitted) - { - EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); - const int txFromNode1 = i; - uint8_t newStatus = (txFromNode1 + 200 < 450) ? ORACLE_QUERY_STATUS_PENDING : ORACLE_QUERY_STATUS_COMMITTED; - oracleEngine1.checkPendingState(queryId, txFromNode1 + 200, txFromNode1, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine1.checkPendingState(queryId, txFromNode1 + 201, txFromNode1 + 1, newStatus); - oracleEngine2.checkPendingState(queryId, txFromNode1 + 200, 200, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine2.checkPendingState(queryId, txFromNode1 + 201, 200, newStatus); - oracleEngine3.checkPendingState(queryId, txFromNode1 + 200, 0, ORACLE_QUERY_STATUS_PENDING); - EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); - oracleEngine3.checkPendingState(queryId, txFromNode1 + 201, 0, newStatus); - } - else - { - oracleEngine1.checkPendingState(queryId, 451, 251, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine2.checkPendingState(queryId, 451, 200, ORACLE_QUERY_STATUS_COMMITTED); - oracleEngine3.checkPendingState(queryId, 451, 0, ORACLE_QUERY_STATUS_COMMITTED); - } - } - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_COMMITTED); - - //------------------------------------------------------------------------- - // reply reveal tx - - // success for one tx - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); - - // second call does not provide the same tx again - EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - // node 3 is in committed state but cannot generate reveal tx, because it did not receive OM reply - EXPECT_EQ(oracleEngine3.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); - - system.tick += 3; - auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; - const unsigned int txIndex = 10; - addOracleTransactionToTickStorage(replyRevealTx, txIndex); - oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndex); - oracleEngine2.processOracleReplyRevealTransaction(replyRevealTx, txIndex); - oracleEngine3.processOracleReplyRevealTransaction(replyRevealTx, txIndex); - - //------------------------------------------------------------------------- - // status - EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); - EXPECT_EQ(oracleEngine2.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); - EXPECT_EQ(oracleEngine3.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); - - OI::Price::OracleReply reply; - EXPECT_TRUE(oracleEngine1.getOracleReply(queryId, &reply, sizeof(reply))); - EXPECT_TRUE(compareMem(&reply, &priceOracleMachineReply.data, sizeof(reply)) == 0); - EXPECT_TRUE(oracleEngine2.getOracleReply(queryId, &reply, sizeof(reply))); - EXPECT_TRUE(compareMem(&reply, &priceOracleMachineReply.data, sizeof(reply)) == 0); - EXPECT_TRUE(oracleEngine3.getOracleReply(queryId, &reply, sizeof(reply))); - EXPECT_TRUE(compareMem(&reply, &priceOracleMachineReply.data, sizeof(reply)) == 0); - - // check that oracle engine is in consistent state - oracleEngine1.checkStateConsistencyWithAssert(); - oracleEngine2.checkStateConsistencyWithAssert(); - oracleEngine3.checkStateConsistencyWithAssert(); -} - -TEST(OracleEngine, FindFirstQueryIndexOfTick) -{ - OracleEngineTest test; - OracleEngineWithInitAndDeinit<676> oracleEngine(broadcastedComputors.computors.publicKeys); - oracleEngine.testFindFirstQueryIndexOfTick({ 1 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 3 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 2 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 3 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 2 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 3 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 3, 3 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 1 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 2 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 3 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 4 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 2, 4 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 3, 4 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 4, 8 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 1, 8, 8, 8 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 108, 109 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 100, 100, 105, 108, 109 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 105, 108, 109 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 108, 108, 109 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 108, 109, 109 }); - oracleEngine.testFindFirstQueryIndexOfTick({ 100, 100, 100, 100, 100, 105, 105, 105, 108, 109, 109 }); - - // random test - std::vector ticks; - uint32_t ticksWithQueries = random(100) + 1; - uint32_t prevTick = 100; - for (uint32_t i = 0; i < ticksWithQueries; ++i) - { - const uint32_t tick = prevTick + random(10); - const uint32_t queriesInTick = random(100) + 1; - for (uint32_t j = 0; j < queriesInTick; ++j) - { - ticks.push_back(tick); - } - prevTick = tick; - } - oracleEngine.testFindFirstQueryIndexOfTick(ticks); -} diff --git a/test/oracle_testing.h b/test/oracle_testing.h deleted file mode 100644 index fd278abe1..000000000 --- a/test/oracle_testing.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -// Include this first, to ensure "logging/logging.h" isn't included before the custom LOG_BUFFER_SIZE has been defined -#include "logging_test.h" - -#undef MAX_NUMBER_OF_TICKS_PER_EPOCH -#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 -#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH -#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 5 -#include "ticking/tick_storage.h" - -#include "gtest/gtest.h" - -#include "oracle_core/oracle_engine.h" -#include "contract_core/qpi_ticking_impl.h" -#include "contract_core/qpi_spectrum_impl.h" - - -union EnqueuedNetworkMessage -{ - RequestResponseHeader header; - - struct - { - RequestResponseHeader header; - OracleMachineQuery queryMetadata; - unsigned char queryData[MAX_ORACLE_QUERY_SIZE]; - } omQuery; -}; - -GLOBAL_VAR_DECL EnqueuedNetworkMessage enqueuedNetworkMessage; - -template -static void checkNetworkMessageOracleMachineQuery(QPI::uint64 expectedOracleQueryId, QPI::uint32 expectedTimeout, const typename OracleInterface::OracleQuery& expectedQuery) -{ - EXPECT_EQ(enqueuedNetworkMessage.header.type(), OracleMachineQuery::type()); - EXPECT_GT(enqueuedNetworkMessage.header.size(), sizeof(RequestResponseHeader) + sizeof(OracleMachineQuery)); - QPI::uint32 queryDataSize = enqueuedNetworkMessage.header.size() - sizeof(RequestResponseHeader) - sizeof(OracleMachineQuery); - EXPECT_LE(queryDataSize, (QPI::uint32)MAX_ORACLE_QUERY_SIZE); - EXPECT_EQ(queryDataSize, sizeof(typename OracleInterface::OracleQuery)); - EXPECT_EQ(enqueuedNetworkMessage.omQuery.queryMetadata.oracleInterfaceIndex, OracleInterface::oracleInterfaceIndex); - EXPECT_EQ(enqueuedNetworkMessage.omQuery.queryMetadata.oracleQueryId, expectedOracleQueryId); - EXPECT_EQ(enqueuedNetworkMessage.omQuery.queryMetadata.timeoutInMilliseconds, expectedTimeout); - const auto* q = (const OracleInterface::OracleQuery*)enqueuedNetworkMessage.omQuery.queryData; - EXPECT_TRUE(compareMem(q, &expectedQuery, sizeof(OracleInterface::OracleQuery)) == 0); -} - -static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data) -{ - EXPECT_EQ(peer, (Peer*)0x1); - EXPECT_LE(dataSize, sizeof(OracleMachineQuery) + MAX_ORACLE_QUERY_SIZE); - EXPECT_TRUE(enqueuedNetworkMessage.header.checkAndSetSize(sizeof(RequestResponseHeader) + dataSize)); - enqueuedNetworkMessage.header.setType(type); - enqueuedNetworkMessage.header.setDejavu(dejavu); - copyMem(&enqueuedNetworkMessage.omQuery.queryMetadata, data, dataSize); -} - -static inline QPI::uint64 getContractOracleQueryId(QPI::uint32 tick, QPI::uint32 indexInTick) -{ - return ((QPI::uint64)tick << 31) | (indexInTick + NUMBER_OF_TRANSACTIONS_PER_TICK); -} - -static inline QPI::uint64 getUserOracleQueryId(QPI::uint32 tick, QPI::uint32 indexInTick) -{ - ASSERT(indexInTick < NUMBER_OF_TRANSACTIONS_PER_TICK); - return ((QPI::uint64)tick << 31) | indexInTick; -} - -static const Transaction* addOracleTransactionToTickStorage(const Transaction* tx, unsigned int txIndex) -{ - ASSERT(txIndex < NUMBER_OF_TRANSACTIONS_PER_TICK); - Transaction* tsTx = nullptr; - const unsigned int txSize = tx->totalSize(); - auto* offsets = ts.tickTransactionOffsets.getByTickInCurrentEpoch(tx->tick); - if (ts.nextTickTransactionOffset + txSize <= ts.tickTransactions.storageSpaceCurrentEpoch) - { - EXPECT_EQ(offsets[txIndex], 0); - offsets[txIndex] = ts.nextTickTransactionOffset; - tsTx = ts.tickTransactions(ts.nextTickTransactionOffset); - copyMem(tsTx, tx, txSize); - ts.nextTickTransactionOffset += txSize; - } - return tsTx; -} diff --git a/test/packages.config b/test/packages.config deleted file mode 100644 index a450605d2..000000000 --- a/test/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/test/pending_txs_pool.cpp b/test/pending_txs_pool.cpp deleted file mode 100644 index b191a5994..000000000 --- a/test/pending_txs_pool.cpp +++ /dev/null @@ -1,544 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -// workaround for name clash with stdlib -#define system qubicSystemStruct - -#include "../src/contract_core/contract_def.h" -#include "../src/contract_core/contract_exec.h" -#include "../src/contract_core/qpi_spectrum_impl.h" - -#include "../src/public_settings.h" -#undef PENDING_TXS_POOL_NUM_TICKS -#define PENDING_TXS_POOL_NUM_TICKS 50ULL -#undef NUMBER_OF_TRANSACTIONS_PER_TICK -#define NUMBER_OF_TRANSACTIONS_PER_TICK 128ULL -#include "../src/ticking/pending_txs_pool.h" - -#include -#include - -static constexpr unsigned int NUM_INITIALIZED_ENTITIES = 200U; - -class TestPendingTxsPool : public PendingTxsPool -{ - unsigned char transactionBuffer[MAX_TRANSACTION_SIZE]; -public: - TestPendingTxsPool() - { - // we need the spectrum for tx priority calculation - EXPECT_TRUE(initSpectrum()); - memset(spectrum, 0, spectrumSizeInBytes); - for (unsigned int i = 0; i < NUM_INITIALIZED_ENTITIES; i++) - { - // create NUM_INITIALIZED_ENTITIES entities with balance > 0 to get desired txs priority - spectrum[i].incomingAmount = i + 1; - spectrum[i].outgoingAmount = 0; - spectrum[i].publicKey = m256i{0, 0, 0, i + 1 }; - - // create NUM_INITIALIZED_ENTITIES entities with balance = 0 for testing - spectrum[NUM_INITIALIZED_ENTITIES + i].incomingAmount = 0; - spectrum[NUM_INITIALIZED_ENTITIES + i].outgoingAmount = 0; - spectrum[NUM_INITIALIZED_ENTITIES + i].publicKey = m256i{ 0, 0, 0, NUM_INITIALIZED_ENTITIES + i + 1 }; - } - updateSpectrumInfo(); - commonBuffers.init(1, sizeof(*txsPriorities)); - } - - ~TestPendingTxsPool() - { - deinitSpectrum(); - commonBuffers.deinit(); - } - - static constexpr unsigned int getMaxNumTxsPerTick() - { - return maxNumTxsPerTick; - } - - bool addTransaction(unsigned int tick, long long amount, unsigned int inputSize, const m256i* dest = nullptr, const m256i* src = nullptr) - { - Transaction* transaction = (Transaction*)transactionBuffer; - transaction->amount = amount; - if (dest == nullptr) - transaction->destinationPublicKey.setRandomValue(); - else - transaction->destinationPublicKey.assign(*dest); - if (src == nullptr) - transaction->sourcePublicKey.setRandomValue(); - else - transaction->sourcePublicKey.assign(*src); - transaction->inputSize = inputSize; - transaction->inputType = 0; - transaction->tick = tick; - - return add(transaction); - } - - unsigned int addTickTransactions(unsigned int tick, unsigned long long seed, unsigned int maxTransactions) - { - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - unsigned int numTransactionsAdded = 0; - - // add transactions of tick - unsigned int transactionNum = gen64() % (maxTransactions + 1); - for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) - { - unsigned int inputSize = gen64() % MAX_INPUT_SIZE; - long long amount = gen64() % MAX_AMOUNT; - m256i srcPublicKey = m256i{ 0, 0, 0, (gen64() % NUM_INITIALIZED_ENTITIES) + 1 }; - if (addTransaction(tick, amount, inputSize, /*dest=*/nullptr, &srcPublicKey)) - numTransactionsAdded++; - } - checkStateConsistencyWithAssert(); - - return numTransactionsAdded; - } - - void checkTickTransactions(unsigned int tick, unsigned long long seed, unsigned int maxTransactions) - { - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - // check transactions of tick - unsigned int transactionNum = gen64() % (maxTransactions + 1); - - for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) - { - unsigned int expectedInputSize = gen64() % MAX_INPUT_SIZE; - long long expectedAmount = gen64() % MAX_AMOUNT; - m256i expectedSrcPublicKey = m256i{ 0, 0, 0, (gen64() % NUM_INITIALIZED_ENTITIES) + 1 }; - - Transaction* tp = getTx(tick, transaction); - - ASSERT_NE(tp, nullptr); - - EXPECT_TRUE(tp->checkValidity()); - EXPECT_EQ(tp->tick, tick); - EXPECT_EQ(static_cast(tp->inputSize), expectedInputSize); - EXPECT_EQ(tp->amount, expectedAmount); - EXPECT_TRUE(tp->sourcePublicKey == expectedSrcPublicKey); - - m256i* digest = getDigest(tick, transaction); - - ASSERT_NE(digest, nullptr); - - m256i tpDigest; - KangarooTwelve(tp, tp->totalSize(), &tpDigest, 32); - EXPECT_EQ(*digest, tpDigest); - } - } -}; - - -TEST(TestPendingTxsPool, EpochTransition) -{ - TestPendingTxsPool pendingTxsPool; - - unsigned long long seed = 42; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - // 5x test with running 3 epoch transitions - for (int testIdx = 0; testIdx < 6; ++testIdx) - { - // first, test case of having no transactions - unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - constexpr unsigned int firstEpochTicks = PENDING_TXS_POOL_NUM_TICKS; - // second epoch start will reset the pool completely because secondEpochTick0 is not contained - constexpr unsigned int secondEpochTicks = PENDING_TXS_POOL_NUM_TICKS / 2; - // thirdEpochTick0 will be contained with newInitialIndex >= buffersBeginIndex - constexpr unsigned int thirdEpochTicks = PENDING_TXS_POOL_NUM_TICKS / 2 + PENDING_TXS_POOL_NUM_TICKS / 4; - // fourthEpochTick0 will be contained with newInitialIndex < buffersBeginIndex - const unsigned int firstEpochTick0 = gen64() % 10000000; - const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; - const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; - const unsigned int fourthEpochTick0 = thirdEpochTick0 + thirdEpochTicks; - unsigned long long firstEpochSeeds[firstEpochTicks]; - unsigned long long secondEpochSeeds[secondEpochTicks]; - unsigned long long thirdEpochSeeds[thirdEpochTicks]; - for (int i = 0; i < firstEpochTicks; ++i) - firstEpochSeeds[i] = gen64(); - for (int i = 0; i < secondEpochTicks; ++i) - secondEpochSeeds[i] = gen64(); - for (int i = 0; i < thirdEpochTicks; ++i) - thirdEpochSeeds[i] = gen64(); - unsigned int numAdded = 0; - - // first epoch - pendingTxsPool.beginEpoch(firstEpochTick0); - pendingTxsPool.checkStateConsistencyWithAssert(); - - // add ticks transactions - for (int i = 0; i < firstEpochTicks; ++i) - numAdded = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - - // check ticks transactions - for (int i = 0; i < firstEpochTicks; ++i) - pendingTxsPool.checkTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - - pendingTxsPool.checkStateConsistencyWithAssert(); - - // Epoch transistion - pendingTxsPool.beginEpoch(secondEpochTick0); - pendingTxsPool.checkStateConsistencyWithAssert(); - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(secondEpochTick0), 0); - - // add ticks transactions - for (int i = 0; i < secondEpochTicks; ++i) - numAdded = pendingTxsPool.addTickTransactions(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); - - // check ticks transactions - for (int i = 0; i < secondEpochTicks; ++i) - pendingTxsPool.checkTickTransactions(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); - - // add a transaction for the next epoch - numAdded = pendingTxsPool.addTickTransactions(thirdEpochTick0 + 1, thirdEpochSeeds[1], maxTransactions); - - pendingTxsPool.checkStateConsistencyWithAssert(); - - // Epoch transistion - pendingTxsPool.beginEpoch(thirdEpochTick0); - pendingTxsPool.checkStateConsistencyWithAssert(); - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(thirdEpochTick0), numAdded); - - // add ticks transactions - for (int i = 2; i < thirdEpochTicks; ++i) - numAdded = pendingTxsPool.addTickTransactions(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); - - // check ticks transactions - for (int i = 1; i < thirdEpochTicks; ++i) - pendingTxsPool.checkTickTransactions(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); - - // add a transaction for the next epoch - numAdded = pendingTxsPool.addTickTransactions(fourthEpochTick0 + 1, /*seed=*/42, maxTransactions); - - pendingTxsPool.checkStateConsistencyWithAssert(); - - // Epoch transistion - pendingTxsPool.beginEpoch(fourthEpochTick0); - pendingTxsPool.checkStateConsistencyWithAssert(); - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(fourthEpochTick0), numAdded); - - pendingTxsPool.deinit(); - } -} - -TEST(TestPendingTxsPool, TotalNumberOfPendingTxs) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 1337; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - // 5x test with running 1 epoch - for (int testIdx = 0; testIdx < 6; ++testIdx) - { - // first, test case of having no transactions - unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - const int firstEpochTicks = (gen64() % (4 * PENDING_TXS_POOL_NUM_TICKS)) + 1; - const unsigned int firstEpochTick0 = gen64() % 10000000; - unsigned long long firstEpochSeeds[4 * PENDING_TXS_POOL_NUM_TICKS]; - for (int i = 0; i < firstEpochTicks; ++i) - firstEpochSeeds[i] = gen64(); - - // first epoch - pendingTxsPool.beginEpoch(firstEpochTick0); - - // add ticks transactions - std::vector numTransactionsAdded(firstEpochTicks); - std::vector numPendingTransactions(firstEpochTicks, 0); - for (int i = firstEpochTicks - 1; i >= 0; --i) - { - numTransactionsAdded[i] = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - if (i > 0) - { - numPendingTransactions[i - 1] = numPendingTransactions[i] + numTransactionsAdded[i]; - } - } - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), (unsigned int)numTransactionsAdded[0] + numPendingTransactions[0]); - for (int i = 0; i < firstEpochTicks; ++i) - { - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 + i), (unsigned int)numPendingTransactions[i]); - } - - pendingTxsPool.deinit(); - } -} - -TEST(TestPendingTxsPool, NumberOfPendingTickTxs) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 67534; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - // 5x test with running 1 epoch - for (int testIdx = 0; testIdx < 6; ++testIdx) - { - // first, test case of having no transactions - unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - constexpr unsigned int firstEpochTicks = PENDING_TXS_POOL_NUM_TICKS; - const unsigned int firstEpochTick0 = gen64() % 10000000; - unsigned long long firstEpochSeeds[firstEpochTicks]; - for (int i = 0; i < firstEpochTicks; ++i) - firstEpochSeeds[i] = gen64(); - - // first epoch - pendingTxsPool.beginEpoch(firstEpochTick0); - - // add ticks transactions - std::vector numTransactionsAdded(firstEpochTicks); - for (int i = firstEpochTicks - 1; i >= 0; --i) - { - numTransactionsAdded[i] = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - } - - EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0 - 1), 0); - for (int i = 0; i < firstEpochTicks; ++i) - { - EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0 + i), (unsigned int)numTransactionsAdded[i]); - } - - pendingTxsPool.deinit(); - } -} - -TEST(TestPendingTxsPool, IncrementFirstStoredTick) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 84129; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - // 5x test with running 1 epoch - for (int testIdx = 0; testIdx < 6; ++testIdx) - { - // first, test case of having no transactions - unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - const int firstEpochTicks = (gen64() % (4 * PENDING_TXS_POOL_NUM_TICKS)) + 1; - const unsigned int firstEpochTick0 = gen64() % 10000000; - unsigned long long firstEpochSeeds[4 * PENDING_TXS_POOL_NUM_TICKS]; - for (int i = 0; i < firstEpochTicks; ++i) - firstEpochSeeds[i] = gen64(); - - // first epoch - pendingTxsPool.beginEpoch(firstEpochTick0); - - // add ticks transactions - std::vector numTransactionsAdded(firstEpochTicks); - std::vector numPendingTransactions(firstEpochTicks, 0); - for (int i = firstEpochTicks - 1; i >= 0; --i) - { - numTransactionsAdded[i] = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - if (i > 0) - { - numPendingTransactions[i - 1] = numPendingTransactions[i] + numTransactionsAdded[i]; - } - } - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), (unsigned int)numTransactionsAdded[0] + numPendingTransactions[0]); - for (int i = 0; i < firstEpochTicks; ++i) - { - pendingTxsPool.incrementFirstStoredTick(); - for (int tx = 0; tx < numTransactionsAdded[i]; ++tx) - { - EXPECT_EQ(pendingTxsPool.getTx(firstEpochTick0 + i, 0), nullptr); - EXPECT_EQ(pendingTxsPool.getDigest(firstEpochTick0 + i, 0), nullptr); - } - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 + i), (unsigned int)numPendingTransactions[i]); - } - - pendingTxsPool.deinit(); - } -} - -TEST(TestPendingTxsPool, TxsPrioritizationMoreThanMaxTxs) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 9532; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - const unsigned int firstEpochTick0 = gen64() % 10000000; - unsigned int numAdditionalTxs = 64; - - pendingTxsPool.beginEpoch(firstEpochTick0); - - // add more than `pendingTxsPool.getMaxNumTxsPerTick()` with increasing priority - // (entities were set up in a way that u64._0 of the public key corresponds to their balance) - m256i srcPublicKey = m256i::zero(); - for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick() + numAdditionalTxs; ++t) - { - srcPublicKey.u64._3 = t + 1; - EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, /*amount=*/t + 1, /*inputSize=*/0, /*dest=*/nullptr, &srcPublicKey)); - } - - // adding lower priority tx does not work - srcPublicKey.u64._3 = 1; - EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, /*amount=*/1, /*inputSize=*/0, /*dest=*/nullptr, &srcPublicKey)); - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), pendingTxsPool.getMaxNumTxsPerTick()); - EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0), pendingTxsPool.getMaxNumTxsPerTick()); - - for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick(); ++t) - { - if (t < numAdditionalTxs) - EXPECT_EQ(pendingTxsPool.getTx(firstEpochTick0, t)->amount, pendingTxsPool.getMaxNumTxsPerTick() + t + 1); - else - EXPECT_EQ(pendingTxsPool.getTx(firstEpochTick0, t)->amount, t + 1); - } - - pendingTxsPool.deinit(); -} - -TEST(TestPendingTxsPool, RejectDuplicateTxs) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 9532; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - const unsigned int firstEpochTick0 = gen64() % 10000000; - constexpr unsigned int numTxs = pendingTxsPool.getMaxNumTxsPerTick() / 2; - - pendingTxsPool.beginEpoch(firstEpochTick0); - - // try to add duplicate transactions: same dest, src, amount, tick, input size/type - m256i dest{ 562, 789, 234, 121 }; - m256i src{ 0, 0, 0, NUM_INITIALIZED_ENTITIES / 3 }; - long long amount = 1; - - // first add should succeed, all others should fail - EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, amount, /*inputSize=*/0, &dest, &src)); - for (unsigned int t = 1; t < numTxs; ++t) - EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, amount, /*inputSize=*/0, &dest, &src)); - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), 1); - EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0), 1); - - Transaction* tx = pendingTxsPool.getTx(firstEpochTick0, 0); - EXPECT_TRUE(tx->checkValidity()); - EXPECT_EQ(tx->amount, amount); - EXPECT_EQ(tx->tick, firstEpochTick0); - EXPECT_EQ(static_cast(tx->inputSize), 0U); - EXPECT_TRUE(tx->destinationPublicKey == dest); - EXPECT_TRUE(tx->sourcePublicKey == src); - - pendingTxsPool.deinit(); -} - -TEST(TestPendingTxsPool, ProtocolLevelTxsMaxPriority) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 9532; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - const unsigned int firstEpochTick0 = gen64() % 10000000; - - pendingTxsPool.beginEpoch(firstEpochTick0); - - // fill the PendingTxsPool completely for tick `firstEpochTick0` - m256i srcPublicKey = m256i::zero(); - for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick(); ++t) - { - srcPublicKey.u64._3 = t + 1; - EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); - } - - EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), pendingTxsPool.getMaxNumTxsPerTick()); - EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0), pendingTxsPool.getMaxNumTxsPerTick()); - - Transaction tx { - .sourcePublicKey = m256i{ 0, 0, 0, (gen64() % NUM_INITIALIZED_ENTITIES) + 1 }, - .destinationPublicKey = m256i::zero(), - .amount = 0, .tick = firstEpochTick0, - .inputType = VOTE_COUNTER_INPUT_TYPE, - .inputSize = 0, - }; - - EXPECT_TRUE(pendingTxsPool.add(&tx)); - - tx.inputType = CustomMiningSolutionTransaction::transactionType(); - - EXPECT_TRUE(pendingTxsPool.add(&tx)); - - pendingTxsPool.deinit(); -} - -TEST(TestPendingTxsPool, TxsWithSrcBalance0AreRejected) -{ - TestPendingTxsPool pendingTxsPool; - unsigned long long seed = 3452; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - for (int testIdx = 0; testIdx < 6; ++testIdx) - { - pendingTxsPool.init(); - pendingTxsPool.checkStateConsistencyWithAssert(); - - const unsigned int firstEpochTick0 = gen64() % 10000000; - - pendingTxsPool.beginEpoch(firstEpochTick0); - - // partially fill the PendingTxsPool for tick `firstEpochTick0` - m256i srcPublicKey = m256i::zero(); - for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick() / 2; ++t) - { - srcPublicKey.u64._3 = t + 1; - EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); - } - - // public key with balance 0 - srcPublicKey.u64._3 = NUM_INITIALIZED_ENTITIES + 1 + (gen64() % NUM_INITIALIZED_ENTITIES); - EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); - - // non-existant public key - srcPublicKey = m256i{ 0, gen64() % MAX_AMOUNT, 0, 0}; - EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); - - pendingTxsPool.deinit(); - } -} \ No newline at end of file diff --git a/test/platform.cpp b/test/platform.cpp deleted file mode 100644 index 5bc997dd3..000000000 --- a/test/platform.cpp +++ /dev/null @@ -1,501 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" -#include "../src/platform/read_write_lock.h" -#include "../src/platform/stack_size_tracker.h" -#include "../src/platform/custom_stack.h" -#include "../src/platform/profiling.h" - -#include "common_buffers.h" -#include - -TEST(TestCoreReadWriteLock, SimpleSingleThread) -{ - ReadWriteLock l; - l.reset(); - - // Aquire lock for multiple readers - constexpr int numReaders = 10; - for (int i = 0; i < numReaders; ++i) - { - EXPECT_TRUE(l.tryAcquireRead()); - EXPECT_FALSE(l.tryAcquireWrite()); - } - - // Release read locks - for (int i = 0; i < numReaders; ++i) - { - l.releaseRead(); - } - - // Aquire lock for writer - EXPECT_TRUE(l.tryAcquireWrite()); - EXPECT_FALSE(l.tryAcquireWrite()); - EXPECT_FALSE(l.tryAcquireRead()); - - // Release write lock - l.releaseWrite(); - - l.acquireRead(); - l.releaseRead(); - - l.acquireWrite(); - l.releaseWrite(); -} - - -StackSizeTracker stackSizeTracker; - -template -void recursion(int i) -{ - // use volatile and get address to make sure this is not optimized away and kept on stack - volatile unsigned long long data[stackVarSize]; - data[0] = (unsigned long long)(volatile unsigned long long*)data; - - unsigned long long oldSize = stackSizeTracker.maxStackSize(); - stackSizeTracker.update(); - unsigned long long newSize = stackSizeTracker.maxStackSize(); - - EXPECT_NE(newSize, StackSizeTracker::uninitialized); - EXPECT_GT(newSize, oldSize); - - if (i > 0) - recursion(i - 1); -} - -template -unsigned long long testStackSizeTracker(int recursionLevel) -{ - INIT_STACK_SIZE_TRACKER(stackSizeTracker); - long long data[10]; data[0] = 0; - stackSizeTracker.update(); - auto size = stackSizeTracker.maxStackSize(); - { - // this does not change size -> shows that storage of all stack variables of function - // is reserved, independently of scope - long long data2[10]; data2[0] = 0; - stackSizeTracker.update(); - size = stackSizeTracker.maxStackSize(); - } - recursion(recursionLevel); - size = stackSizeTracker.maxStackSize(); - stackSizeTracker.reset(); - return size; -} - -// Test that max. measured stack size is kept if tracker is reused with other stack top address -// (relevant if restarted processor/thread gets other stack address) -void testStackSizeTrackerKeepSizeCheck(unsigned long long prevSize) -{ - // check that max size is kept on reuse - INIT_STACK_SIZE_TRACKER(stackSizeTracker); - EXPECT_EQ(stackSizeTracker.maxStackSize(), prevSize); -} -void testStackSizeTrackerKeepSize() -{ - // first use - stackSizeTracker.reset(); - INIT_STACK_SIZE_TRACKER(stackSizeTracker); - UPDATE_STACK_SIZE_TRACKER(stackSizeTracker); - testStackSizeTrackerKeepSizeCheck(stackSizeTracker.maxStackSize()); -} - - -TEST(TestCoreStackSizeTracker, SimpleTest) -{ - // Return uninit if not not both functions are called - DEFINE_STACK_SIZE_TRACKER(s1); - EXPECT_EQ(s1.maxStackSize(), StackSizeTracker::uninitialized); - INIT_STACK_SIZE_TRACKER(s1); - EXPECT_EQ(s1.maxStackSize(), StackSizeTracker::uninitialized); - DEFINE_STACK_SIZE_TRACKER(s2); - EXPECT_EQ(s2.maxStackSize(), StackSizeTracker::uninitialized); - UPDATE_STACK_SIZE_TRACKER(s2); - EXPECT_EQ(s2.maxStackSize(), StackSizeTracker::uninitialized); - - // Test that more recursion and more data per function lead to bigger stack size - EXPECT_LT(testStackSizeTracker<10>(0), testStackSizeTracker<10>(1)); - EXPECT_LT(testStackSizeTracker<10>(5), testStackSizeTracker<10>(10)); - EXPECT_LT(testStackSizeTracker<10>(10), testStackSizeTracker<20>(10)); - EXPECT_LT(testStackSizeTracker<30>(20), testStackSizeTracker<30>(40)); - - // Test that max. measured stack size is kept if tracker is reused with other stack top address - testStackSizeTrackerKeepSize(); -} - - -void customStackTest1(void* data) -{ - EXPECT_EQ(data, (void*)5); -} - -void customStackTest2(void* data) -{ - int recursionLevel = (int)(unsigned long long)data; - testStackSizeTracker<10>(recursionLevel); -} - - -TEST(TestCoreCustomStack, SimpleTest) -{ - CustomStack s; - s.init(); - EXPECT_EQ(s.maxStackUsed(), 0); - EXPECT_TRUE(s.alloc(128*1024)); - EXPECT_EQ(s.maxStackUsed(), 0); - - // run function with small stack requirements on custom stack - s.setupFunction(customStackTest1, (void*)5); - CustomStack::runFunction(&s); - auto size1 = s.maxStackUsed(); - EXPECT_GT(size1, 0u); - - // run recursive function with small stack requirements on custom stack - s.setupFunction(customStackTest2, (void*)1); - CustomStack::runFunction(&s); - auto size2 = s.maxStackUsed(); - EXPECT_GT(size2, size1); - - // run recursive function with medium stack requirements on custom stack - s.setupFunction(customStackTest2, (void*)5); - CustomStack::runFunction(&s); - auto size3 = s.maxStackUsed(); - EXPECT_GT(size3, size2); - - // run recursive function with large stack requirements on custom stack - s.setupFunction(customStackTest2, (void*)10); - CustomStack::runFunction(&s); - auto size4 = s.maxStackUsed(); - EXPECT_GT(size4, size3); -} - -void recursiveProfilingTest(int depth) -{ - ProfilingScope profScope(__FUNCTION__, __LINE__); - if (depth > 0) - { - recursiveProfilingTest(depth - 1); - recursiveProfilingTest(depth - 2); - } - else - { - sleepMicroseconds(10); - } -} - -void iterativeProfilingTest(int n) -{ - ProfilingScope profScope(__FUNCTION__, __LINE__); - for (int i = 0; i < n; ++i) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - for (int j = 0; j < n; ++j) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - for (int k = 0; k < n; ++k) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - sleepMicroseconds(10); - } - } - } -} - -TEST(TestCoreProfiling, SleepTest) -{ - ProfilingStopwatch profStopwatch(__FUNCTION__, __LINE__); - profStopwatch.start(); - - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - testStackSizeTrackerKeepSize(); - } - - for (int i = 0; i < 1000; ++i) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - testStackSizeTrackerKeepSize(); - } - - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - recursiveProfilingTest(9); - } - - profStopwatch.stop(); - - for (int i = 0; i < 10; ++i) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - recursiveProfilingTest(4); - - // calling start() multiple times is no problem (last time is relevant for measuring) - profStopwatch.start(); - } - - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - iterativeProfilingTest(5); - profStopwatch.stop(); - } - - for (int i = 0; i < 5; ++i) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - iterativeProfilingTest(3); - } - - gProfilingDataCollector.writeToFile(); - - EXPECT_FALSE(gProfilingDataCollector.init(2)); - gProfilingDataCollector.clear(); - gProfilingDataCollector.deinit(); - gProfilingDataCollector.clear(); - - //gProfilingDataCollector.writeToFile(); -} - -static ProfilingStopwatch gProfStopwatch(__FILE__, __LINE__); - -TEST(TestCoreProfiling, AddMeasurementSpeedTest) -{ - for (unsigned long long i = 0; i < 10000; ++i) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - gProfStopwatch.start(); - for (unsigned long long j = 0; j < 10000; ++j) - { - ProfilingScope profScope(__FUNCTION__, __LINE__); - for (volatile unsigned long long k = 0; k < 10; ++k) - { - } - } - gProfStopwatch.stop(); - } - - gProfilingDataCollector.writeToFile(); -} - -void checkTicksToMicroseconds(int type, unsigned long long ticks, unsigned long long frequency) -{ - ::frequency = frequency; - unsigned long long microsecondsInt = ProfilingDataCollector::ticksToMicroseconds(ticks); - long double microsecondsFloat = long double(ticks) * long double(1000000) / long double(frequency); - long double diff = std::abs(microsecondsFloat - microsecondsInt); - if (type == 0) - { - // no overflow - EXPECT_LT(diff, 1.0); - } - else if (type == 1) - { - // overflow in calculation -> tolerate inaccuracy - EXPECT_LT(diff, 1000000.0); - } - else - { - // overflow in result -> expect max value - EXPECT_EQ(microsecondsInt, 0xffffffffffffffffllu); - } - ::frequency = 0; -} - -TEST(TestCoreProfiling, CheckTicksToMicroseconds) -{ - // non-overflow cases - checkTicksToMicroseconds(0, 10000, 100000000); - checkTicksToMicroseconds(0, 100000000, 10000); - checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu, 1000000llu); - checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu, 1000000llu + 1); - checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu, 1000000llu - 1); - checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu - 1, 1000000llu); - checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu - 1, 1000000llu + 1); - checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu - 1, 1000000llu - 1); - - // overflow in calculation - checkTicksToMicroseconds(1, 0xffffffffffffffffllu / 1000000llu + 1, 1000000llu); - checkTicksToMicroseconds(1, 0xffffffffffffffffllu / 1000000llu + 1, 1000000llu + 1); - checkTicksToMicroseconds(1, 0xffffffffffffffffllu, 1000000000llu); - checkTicksToMicroseconds(1, 0xffffffffffffffllu, 1000000000llu); - checkTicksToMicroseconds(1, 0xffffffffffffffffllu, 1234567890llu); - checkTicksToMicroseconds(1, 0xffffffffffffffllu, 1234567890llu); - - // overflow in result (low frequency) - checkTicksToMicroseconds(2, 0xffffffffffffffffllu, 1000); - checkTicksToMicroseconds(2, 0xffffffffffffffllu, 1000); - checkTicksToMicroseconds(2, 0xffffffffffffffffllu, 12345); - checkTicksToMicroseconds(2, 0xffffffffffffffffllu, 123456); -} - -static volatile bool waitingForAcquire = false; - -static void acquireAndReleaseCommonBuffer() -{ - waitingForAcquire = true; - void* buf = commonBuffers.acquireBuffer(64); - waitingForAcquire = false; - EXPECT_NE(buf, nullptr); - sleepMilliseconds(100); - commonBuffers.releaseBuffer(buf); -} - -TEST(TestCoreCommonBuffers, OneBuffer) -{ - if (!frequency) - initTimeStampCounter(); - - // init fail: 0 buffers / size 0 - EXPECT_FALSE(commonBuffers.init(0, 1024)); - EXPECT_FALSE(commonBuffers.init(1, 0)); - - // init success - EXPECT_TRUE(commonBuffers.init(1, 1024)); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // acquire fail: requested buffer size too big - void* buf = commonBuffers.acquireBuffer(2000); - EXPECT_EQ(buf, nullptr); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // acquire success - buf = commonBuffers.acquireBuffer(1024); - EXPECT_NE(buf, nullptr); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - - // release success - commonBuffers.releaseBuffer(buf); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // check stats - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); - EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 0); - - // invalid release (double free) - commonBuffers.releaseBuffer(buf); - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 1); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // invalid release (invalid pointer) - commonBuffers.releaseBuffer((void*)0x123456); - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 2); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // acquire success - buf = commonBuffers.acquireBuffer(512); - EXPECT_NE(buf, nullptr); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - - // acquire in other thread has to wait - auto thread = std::thread(acquireAndReleaseCommonBuffer); - sleepMilliseconds(100); - EXPECT_TRUE(waitingForAcquire); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - - // release buffer -> other thread can acquire it - commonBuffers.releaseBuffer(buf); - sleepMilliseconds(50); // after 50 ms the other thread should have acquired the buffer - EXPECT_FALSE(waitingForAcquire); - thread.join(); // after ending the thread, the buffer is release again - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 1); - - // free all buffers, reset state - commonBuffers.deinit(); - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); - EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 0); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); -} - -TEST(TestCoreCommonBuffers, ThreeBuffers) -{ - if (!frequency) - initTimeStampCounter(); - - // init success - EXPECT_TRUE(commonBuffers.init(3, 1024)); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // acquire fail: requested buffer size too big - void* buf = commonBuffers.acquireBuffer(2000); - EXPECT_EQ(buf, nullptr); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // acquire success - buf = commonBuffers.acquireBuffer(1024); - EXPECT_NE(buf, nullptr); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - - // release - commonBuffers.releaseBuffer(buf); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // acquire three buffer success - void* buf2 = commonBuffers.acquireBuffer(1024); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - void* buf3 = commonBuffers.acquireBuffer(42); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 2); - buf = commonBuffers.acquireBuffer(123); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); - EXPECT_NE(buf, nullptr); - EXPECT_NE(buf2, nullptr); - EXPECT_NE(buf3, nullptr); - - // release three buffers - commonBuffers.releaseBuffer(buf); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 2); - commonBuffers.releaseBuffer(buf2); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - commonBuffers.releaseBuffer(buf3); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - // check stats - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); - EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 1); // heuristic is off by one (in facto no waiting needed) - - // invalid release (double free) - commonBuffers.releaseBuffer(buf); - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 1); - - // invalid release (invalid pointer) - commonBuffers.releaseBuffer((void*)0x123456); - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 2); - - // acquire three buffers - buf = commonBuffers.acquireBuffer(512); - buf2 = commonBuffers.acquireBuffer(1024); - buf3 = commonBuffers.acquireBuffer(987); - EXPECT_NE(buf, nullptr); - EXPECT_NE(buf2, nullptr); - EXPECT_NE(buf3, nullptr); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); - - // acquire in two other thread has to wait - auto thread1 = std::thread(acquireAndReleaseCommonBuffer); - auto thread2 = std::thread(acquireAndReleaseCommonBuffer); - sleepMilliseconds(100); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); - EXPECT_TRUE(waitingForAcquire); - - // release 2 buffers -> other threads can acquire them - commonBuffers.releaseBuffer(buf); - commonBuffers.releaseBuffer(buf2); - sleepMilliseconds(50); - EXPECT_FALSE(waitingForAcquire); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); - thread1.join(); - thread2.join(); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); - - commonBuffers.releaseBuffer(buf3); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); - - EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 2); - - // free all buffers, reset state - commonBuffers.deinit(); - EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); - EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 0); - EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); -} diff --git a/test/qpi.cpp b/test/qpi.cpp deleted file mode 100644 index ca0b62c9c..000000000 --- a/test/qpi.cpp +++ /dev/null @@ -1,2313 +0,0 @@ -#define NO_UEFI - -#include "contract_testing.h" - -#include - -// changing offset simulates changed computor set with changed epoch -void initComputors(unsigned short computorIdOffset) -{ - for (unsigned short computorIndex = 0; computorIndex < NUMBER_OF_COMPUTORS; ++computorIndex) - { - broadcastedComputors.computors.publicKeys[computorIndex] = QPI::id(computorIndex + computorIdOffset, 9, 8, 7); - } -} - -TEST(TestCoreQPI, SafeMath) -{ - { - sint64 a = -1000000000000LL; // This is valid - negative signed integer - sint64 b = 2; // Positive signed integer - EXPECT_EQ(smul(a, b), -2000000000000); - } - - { - uint64_t a = 1000000; - uint64_t b = 2000000; - uint64_t expected_ok = 2000000000000ULL; - EXPECT_EQ(smul(a, b), expected_ok); - } - { - sint64 a = INT64_MIN; // -9223372036854775808 - sint64 b = -1; // -1 - EXPECT_EQ(smul(a, b), INT64_MAX); - } - { - uint64_t a = 123456789ULL; - uint64_t b = 987654321ULL; - - // Case: Multiplication by zero. - EXPECT_EQ(smul(a, 0ULL), 0ULL); - EXPECT_EQ(smul(0ULL, b), 0ULL); - - // Case: Multiplication by one. - EXPECT_EQ(smul(a, 1ULL), a); - EXPECT_EQ(smul(1ULL, b), b); - } - { - // Case: A clear overflow case. - // UINT64_MAX is approximately 1.84e19. - uint64_t c = 4000000000ULL; - uint64_t d = 5000000000ULL; // c * d is 2e19, which overflows. - EXPECT_EQ(smul(c, d), UINT64_MAX); - } - { - // Case: Test the exact boundary of overflow. - uint64_t max_val = UINT64_MAX; - uint64_t divisor = 2; - uint64_t limit = max_val / divisor; - - // This should not overflow. - EXPECT_EQ(smul(limit, divisor), limit * 2); - - // This should overflow and clamp. - EXPECT_EQ(smul(limit + 1, divisor), UINT64_MAX); - } - { - // Case: A simple multiplication that does not overflow. - int64_t e = 1000000; - int64_t f = -2000000; - EXPECT_EQ(smul(e, f), -2000000000000LL); - } - { - // Case: Positive * Positive, causing overflow. - int64_t a = INT64_MAX / 2; - int64_t b = 3; - EXPECT_EQ(smul(a, b), INT64_MAX); - } - { - int64_t a = INT64_MAX / 2; - int64_t b = 3; - int64_t c = -3; - int64_t d = INT64_MIN / 2; - - // Case: Positive * Negative, causing underflow. - EXPECT_EQ(smul(a, c), INT64_MIN); - - // Case: Negative * Positive, causing underflow. - EXPECT_EQ(smul(d, b), INT64_MIN); - } - { - // Case: Negative * Negative, causing overflow. - int64_t c = -3; - int64_t d = INT64_MIN / 2; - EXPECT_EQ(smul(d, c), INT64_MAX); - } - { - // --- Unsigned 32-bit Tests --- - // No Overflow - uint32_t a_u32 = 60000; - uint32_t b_u32 = 60000; - EXPECT_EQ(smul(a_u32, b_u32), 3600000000U); - - // Overflow - uint32_t c_u32 = 70000; - uint32_t d_u32 = 70000; // 70000*70000 = 4,900,000,000 which is > UINT32_MAX (~4.29e9) - EXPECT_EQ(smul(c_u32, d_u32), UINT32_MAX); - - // Boundary - uint32_t limit_u32 = UINT32_MAX / 2; - uint32_t divisor_u32 = 2; - EXPECT_EQ(smul(limit_u32, divisor_u32), limit_u32 * 2); - EXPECT_EQ(smul(limit_u32 + 1, divisor_u32), UINT32_MAX); - - // --- Signed 32-bit Tests --- - // No Overflow - int32_t a_s32 = 10000; - int32_t b_s32 = -10000; - EXPECT_EQ(smul(a_s32, b_s32), -100000000); - - // Positive Overflow - int32_t c_s32 = INT32_MAX / 2; - int32_t d_s32 = 3; - EXPECT_EQ(smul(c_s32, d_s32), INT32_MAX); - - // Underflow - int32_t e_s32 = INT32_MIN / 2; - int32_t f_s32 = 3; - EXPECT_EQ(smul(e_s32, f_s32), INT32_MIN); - - // Negative * Negative, causing overflow. - int32_t g_s32 = -3; - int32_t h_s32 = INT32_MIN / 2; - EXPECT_EQ(smul(h_s32, g_s32), INT32_MAX); - } -} - -TEST(TestCoreQPI, Array) -{ - //QPI::array mustFail; // should raise compile error - - QPI::Array uint8_4; - EXPECT_EQ(uint8_4.capacity(), 4); - //uint8_4.setMem(QPI::id(1, 2, 3, 4)); // should raise compile error - uint8_4.setAll(2); - EXPECT_EQ(uint8_4.get(0), 2); - EXPECT_EQ(uint8_4.get(1), 2); - EXPECT_EQ(uint8_4.get(2), 2); - EXPECT_EQ(uint8_4.get(3), 2); - EXPECT_TRUE(uint8_4.rangeEquals(0, 4, 2)); - EXPECT_TRUE(isArraySorted(uint8_4)); - EXPECT_FALSE(isArraySortedWithoutDuplicates(uint8_4)); - uint8_4.set(3, 1); - EXPECT_FALSE(isArraySorted(uint8_4)); - EXPECT_FALSE(isArraySortedWithoutDuplicates(uint8_4)); - uint8_4.setRange(1, 3, 0); - EXPECT_EQ(uint8_4.get(0), 2); - EXPECT_EQ(uint8_4.get(1), 0); - EXPECT_EQ(uint8_4.get(2), 0); - EXPECT_EQ(uint8_4.get(3), 1); - EXPECT_TRUE(uint8_4.rangeEquals(1, 3, 0)); - EXPECT_FALSE(isArraySorted(uint8_4)); - EXPECT_FALSE(isArraySortedWithoutDuplicates(uint8_4)); - for (int i = 0; i < uint8_4.capacity(); ++i) - uint8_4.set(i, i+1); - for (int i = 0; i < uint8_4.capacity(); ++i) - EXPECT_EQ(uint8_4.get(i), i+1); - EXPECT_FALSE(uint8_4.rangeEquals(0, 4, 2)); - EXPECT_TRUE(isArraySorted(uint8_4)); - EXPECT_TRUE(isArraySortedWithoutDuplicates(uint8_4)); - - QPI::Array uint64_4; - uint64_4.setMem(QPI::id(101, 102, 103, 104)); - for (int i = 0; i < uint64_4.capacity(); ++i) - EXPECT_EQ(uint64_4.get(i), i + 101); - //uint64_4.setMem(uint8_4); // should raise compile error - - QPI::Array uint16_2; - EXPECT_EQ(uint8_4.capacity(), 4); - //uint16_2.setMem(QPI::id(1, 2, 3, 4)); // should raise compile error - uint16_2.setAll(12345); - EXPECT_EQ((int)uint16_2.get(0), 12345); - EXPECT_EQ((int)uint16_2.get(1), 12345); - for (int i = 0; i < uint16_2.capacity(); ++i) - uint16_2.set(i, i + 987); - for (int i = 0; i < uint16_2.capacity(); ++i) - EXPECT_EQ((int)uint16_2.get(i), i + 987); - uint16_2.setMem(uint8_4); - for (int i = 0; i < uint16_2.capacity(); ++i) - EXPECT_EQ((int)uint16_2.get(i), (int)(((2*i+2) << 8) | (2*i + 1))); -} - -TEST(TestCoreQPI, BitArray) -{ - //QPI::BitArray<0> mustFail; - - QPI::BitArray<1> b1; - EXPECT_EQ(b1.capacity(), 1); - b1.setAll(0); - EXPECT_EQ(b1.get(0), 0); - b1.setAll(1); - EXPECT_EQ(b1.get(0), 1); - b1.setAll(true); - EXPECT_EQ(b1.get(0), 1); - b1.set(0, 1); - EXPECT_EQ(b1.get(0), 1); - b1.set(0, 0); - EXPECT_EQ(b1.get(0), 0); - b1.set(0, true); - EXPECT_EQ(b1.get(0), 1); - - b1.setAll(0); - QPI::BitArray<1> b1_2; - b1_2.setAll(0); - QPI::BitArray<1> b1_3; - b1_3.setAll(1); - EXPECT_TRUE(b1 == b1_2); - EXPECT_TRUE(b1 != b1_3); - EXPECT_FALSE(b1 == b1_3); - - QPI::BitArray<64> b64; - EXPECT_EQ(b64.capacity(), 64); - b64.setMem(0x11llu); - EXPECT_EQ(b64.get(0), 1); - EXPECT_EQ(b64.get(1), 0); - EXPECT_EQ(b64.get(2), 0); - EXPECT_EQ(b64.get(3), 0); - EXPECT_EQ(b64.get(4), 1); - EXPECT_EQ(b64.get(5), 0); - EXPECT_EQ(b64.get(6), 0); - EXPECT_EQ(b64.get(7), 0); - QPI::Array llu1; - llu1.setMem(b64); - EXPECT_EQ(llu1.get(0), 0x11llu); - b64.setAll(0); - llu1.setMem(b64); - EXPECT_EQ(llu1.get(0), 0x0); - b64.setAll(1); - llu1.setMem(b64); - EXPECT_EQ(llu1.get(0), 0xffffffffffffffffllu); - - - b64.setMem(0x11llu); - QPI::BitArray<64> b64_2; - EXPECT_EQ(b64.capacity(), 64); - b64_2.setMem(0x11llu); - QPI::BitArray<64> b64_3; - EXPECT_EQ(b64.capacity(), 64); - b64_3.setMem(0x55llu); - EXPECT_TRUE(b64 == b64_2); - EXPECT_TRUE(b64 != b64_3); - EXPECT_FALSE(b64 == b64_3); - - //QPI::BitArray<96> b96; // must trigger compile error - - QPI::BitArray<128> b128; - EXPECT_EQ(b128.capacity(), 128); - QPI::Array llu2; - llu2.setAll(0x4llu); - EXPECT_EQ(llu2.get(0), 0x4llu); - EXPECT_EQ(llu2.get(1), 0x4llu); - b128.setMem(llu2); - for (int i = 0; i < 2; ++i) - { - for (int j = 0; j < 64; ++j) - { - EXPECT_EQ(b128.get(i * 64 + j), j == 2); - } - } - b128.set(0, 1); - b128.set(2, 0); - llu2.setMem(b128); - EXPECT_EQ(llu2.get(0), 0x1llu); - EXPECT_EQ(llu2.get(1), 0x4llu); - for (int i = 0; i < b128.capacity(); ++i) - { - b128.set(i, i % 2 == 0); - EXPECT_EQ(b128.get(i), i % 2 == 0); - } - llu2.setMem(b128); - EXPECT_EQ(llu2.get(0), 0x5555555555555555llu); - EXPECT_EQ(llu2.get(1), 0x5555555555555555llu); - for (int i = 0; i < b128.capacity(); ++i) - { - EXPECT_EQ(b128.get(i), i % 2 == 0); - } - - b128.setAll(1); - QPI::BitArray<128> b128_2; - QPI::BitArray<128> b128_3; - b128_2.setAll(1); - b128_3.setAll(0); - EXPECT_TRUE(b128 == b128_2); - EXPECT_TRUE(b128 != b128_3); - EXPECT_FALSE(b128 == b128_3); -} - -TEST(TestCoreQPI, Div) { - EXPECT_EQ(QPI::div(0, 0), 0); - EXPECT_EQ(QPI::div(10, 0), 0); - EXPECT_EQ(QPI::div(0, 10), 0); - EXPECT_EQ(QPI::div(20, 19), 1); - EXPECT_EQ(QPI::div(20, 20), 1); - EXPECT_EQ(QPI::div(20, 21), 0); - EXPECT_EQ(QPI::div(20, 22), 0); - EXPECT_EQ(QPI::div(50, 24), 2); - EXPECT_EQ(QPI::div(50, 25), 2); - EXPECT_EQ(QPI::div(50, 26), 1); - EXPECT_EQ(QPI::div(50, 27), 1); - EXPECT_EQ(QPI::div(-2, 0), 0); - EXPECT_EQ(QPI::div(-2, 1), -2); - EXPECT_EQ(QPI::div(-2, 2), -1); - EXPECT_EQ(QPI::div(-2, 3), 0); - EXPECT_EQ(QPI::div(2, -3), 0); - EXPECT_EQ(QPI::div(2, -2), -1); - EXPECT_EQ(QPI::div(2, -1), -2); - - EXPECT_EQ(QPI::div(0.0, 0.0), 0.0); - EXPECT_EQ(QPI::div(50.0, 0.0), 0.0); - EXPECT_EQ(QPI::div(0.0, 50.0), 0.0); - EXPECT_EQ(QPI::div(-25.0, 50.0), -0.5); - EXPECT_EQ(QPI::div(-25.0, -0.5), 50.0); -} - -TEST(TestCoreQPI, Mod) { - EXPECT_EQ(QPI::mod(0, 0), 0); - EXPECT_EQ(QPI::mod(10, 0), 0); - EXPECT_EQ(QPI::mod(0, 10), 0); - EXPECT_EQ(QPI::mod(20, 19), 1); - EXPECT_EQ(QPI::mod(20, 20), 0); - EXPECT_EQ(QPI::mod(20, 21), 20); - EXPECT_EQ(QPI::mod(20, 22), 20); - EXPECT_EQ(QPI::mod(50, 23), 4); - EXPECT_EQ(QPI::mod(50, 24), 2); - EXPECT_EQ(QPI::mod(50, 25), 0); - EXPECT_EQ(QPI::mod(50, 26), 24); - EXPECT_EQ(QPI::mod(50, 27), 23); - EXPECT_EQ(QPI::mod(-2, 0), 0); - EXPECT_EQ(QPI::mod(-2, 1), 0); - EXPECT_EQ(QPI::mod(-2, 2), 0); - EXPECT_EQ(QPI::mod(-2, 3), -2); - EXPECT_EQ(QPI::mod(2, -3), 2); - EXPECT_EQ(QPI::mod(2, -2), 0); - EXPECT_EQ(QPI::mod(2, -1), 0); -} - -TEST(TestCoreQPI, IdFromCharacters) -{ - using namespace QPI::Ch; - - QPI::id test('t', 'e', s, t, '!'); - EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("test!")); - - test = QPI::id(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, space, dot, comma, colon, semicolon, null); - EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("abcdefghijklmnopqrstuvwxyz .,:;")); - - test = QPI::id(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, slash, backslash); - EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("ABCDEFGHIJKLMNOPQRSTUVWXYZ/\\")); - - test = QPI::id(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9); - EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("0123456789")); - - test = QPI::id(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); - EXPECT_TRUE(isZero(test)); - - test = QPI::id(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, space); - EXPECT_EQ(test.i8._31, ' '); - test.m256i_i8[31] = 0; - EXPECT_TRUE(isZero(test)); -} - - -struct ContractExecInitDeinitGuard -{ - ContractExecInitDeinitGuard() - { - EXPECT_TRUE(initContractExec()); - } - ~ContractExecInitDeinitGuard() - { - deinitContractExec(); - } -}; - -TEST(TestCoreQPI, ProposalAndVotingByComputors) -{ - ContractExecInitDeinitGuard initDeinitGuard; - QpiContextUserProcedureCall qpi(0, QPI::id(1, 2, 3, 4), 123); - QPI::ProposalAndVotingByComputors pv; - initComputors(0); - - // Memory must be zeroed to work, which is done in contract states on init - QPI::setMemory(pv, 0); - - // vote index is computor index - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - EXPECT_EQ(pv.getVoteIndex(qpi, qpi.computor(i)), i); - EXPECT_EQ(pv.getVoterId(qpi, i), qpi.computor(i)); - EXPECT_EQ(pv.getVoteCount(qpi, i), 1); - } - for (int i = NUMBER_OF_COMPUTORS; i < 800; ++i) - { - QPI::id testId(i, 9, 8, 7); - EXPECT_EQ(pv.getVoteIndex(qpi, testId), QPI::INVALID_VOTE_INDEX); - EXPECT_EQ(pv.getVoterId(qpi, i), QPI::NULL_ID); - EXPECT_EQ(pv.getVoteCount(qpi, i), 0); - } - EXPECT_EQ(pv.getVoteIndex(qpi, qpi.originator()), QPI::INVALID_VOTE_INDEX); - - // valid proposers are computors - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - EXPECT_TRUE(pv.isValidProposer(qpi, qpi.computor(i))); - for (int i = NUMBER_OF_COMPUTORS; i < 2 * NUMBER_OF_COMPUTORS; ++i) - EXPECT_FALSE(pv.isValidProposer(qpi, QPI::id(i, 9, 8, 7))); - EXPECT_FALSE(pv.isValidProposer(qpi, QPI::NULL_ID)); - EXPECT_FALSE(pv.isValidProposer(qpi, qpi.originator())); - - // no existing proposals - for (int i = 0; i < 2*NUMBER_OF_COMPUTORS; ++i) - { - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, qpi.computor(i)), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(pv.getProposerId(qpi, i), NULL_ID); - } - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); - - // fill all slots - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - int j = NUMBER_OF_COMPUTORS - 1 - i; - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, qpi.computor(j)), i); - } - - // proposals now available - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - int j = NUMBER_OF_COMPUTORS - 1 - i; - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, qpi.computor(j)), i); - EXPECT_EQ(pv.getProposerId(qpi, i), qpi.computor(j)); - } - - // using other ID fails if full (new computor ID after epoch change) - QPI::id newId(9, 8, 7, 6); - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, newId), (int)QPI::INVALID_PROPOSAL_INDEX); - - // reusing all slots should work - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - int j = NUMBER_OF_COMPUTORS - 1 - i; - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, qpi.computor(j)), i); - } - - // free one slot - pv.freeProposalByIndex(qpi, 0); - - // using other ID now works (new computor ID after epoch change) - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, newId), 0); - - // check content - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, newId), 0); - for (int i = 1; i < NUMBER_OF_COMPUTORS; ++i) - { - int j = NUMBER_OF_COMPUTORS - 1 - i; - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, qpi.computor(j)), i); - } -} - -static void sortContractShareVector(std::vector> & shareholders) -{ - std::sort(shareholders.begin(), shareholders.end(), - [](const std::pair& a, const std::pair& b) - { - return a.first < b.first; - }); -} - -template -static void checkShareholderVotingRights(const QpiContextUserProcedureCall& qpi, const ProposalAndVotingByShareholders& pv, uint16 proposalIdx, std::vector>& shareholderVec) -{ - unsigned int voterIdx = 0; - for (const auto& ownerSharesPair : shareholderVec) - { - const m256i owner = ownerSharesPair.first; - const auto shareCount = ownerSharesPair.second; - EXPECT_EQ(pv.getVoteIndex(qpi, owner, proposalIdx), voterIdx); - for (unsigned int i = 0; i < shareCount; ++i) - { - EXPECT_EQ(pv.getVoterId(qpi, voterIdx, proposalIdx), owner); - EXPECT_EQ(pv.getVoteCount(qpi, voterIdx, proposalIdx), shareCount - i); - ++voterIdx; - } - } - EXPECT_EQ(voterIdx, NUMBER_OF_COMPUTORS); - EXPECT_EQ(pv.getVoteIndex(qpi, NULL_ID, proposalIdx), INVALID_VOTE_INDEX); - EXPECT_EQ(pv.getVoteIndex(qpi, id(12345678, 901234, 5678, 90), proposalIdx), INVALID_VOTE_INDEX); - EXPECT_EQ(pv.getVoterId(qpi, voterIdx, proposalIdx), NULL_ID); - EXPECT_EQ(pv.getVoteCount(qpi, voterIdx, proposalIdx), 0); -} - -TEST(TestCoreQPI, ProposalAndVotingByShareholders) -{ - ContractTesting test; - test.initEmptyUniverse(); - QpiContextUserProcedureCall qpi(QX_CONTRACT_INDEX, QPI::id(1, 2, 3, 4), 123); - ProposalAndVotingByShareholders<3, MSVAULT_ASSET_NAME> pv; - initComputors(0); - - // Memory must be zeroed to work, which is done in contract states on init - setMemory(pv, 0); - - // create contract shares - std::vector> initialPossessorShares{ - {id(100, 2, 3, 4), 10}, - {id(1, 2, 3, 4), 200}, - {id(1, 2, 2, 1), 65}, - {id(1, 2, 3, 1), 1}, - {id(0, 0, 0, 1), 400}, - }; - issueContractShares(MSVAULT_CONTRACT_INDEX, initialPossessorShares); - sortContractShareVector(initialPossessorShares); - - // no existing proposals - for (int i = 0; i < initialPossessorShares.size(); ++i) - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[i].first), (int)INVALID_PROPOSAL_INDEX); - - // valid proposers are current shareholders - for (int i = 0; i < initialPossessorShares.size(); ++i) - EXPECT_TRUE(pv.isValidProposer(qpi, initialPossessorShares[i].first)); - for (int i = 0; i < 2 * NUMBER_OF_COMPUTORS; ++i) - { - EXPECT_FALSE(pv.isValidProposer(qpi, QPI::id(i, 9, 8, 7))); - EXPECT_EQ(pv.getProposerId(qpi, i), NULL_ID); - } - EXPECT_FALSE(pv.isValidProposer(qpi, QPI::NULL_ID)); - - // add proposal - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[0].first), 0); - for (int i = 0; i < initialPossessorShares.size(); ++i) - { - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[i].first), (i == 0) ? 0 : (int)INVALID_PROPOSAL_INDEX); - EXPECT_EQ(pv.getProposerId(qpi, i), (i == 0) ? initialPossessorShares[i].first : NULL_ID); - } - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); - - // check voting rights (voters are shareholders at time of creating/updating proposal) - checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); - - // transfer some shares - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(20, 2, 3, 5)), 199); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(30, 2, 3, 5)), 198); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(40, 2, 3, 5)), 197); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(50, 2, 3, 5)), 196); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 16, id(1, 2, 3, 5)), 180); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 60, id(1, 2, 3, 1)), 120); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 39, id(1, 2, 3, 0)), 81); - EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(1, 2, 3, 2)), 80); - std::vector> changedPossessorShares{ - {id(0, 0, 0, 1), 400}, - {id(1, 2, 2, 1), 65}, - {id(1, 2, 3, 0), 39}, - {id(1, 2, 3, 1), 61}, - {id(1, 2, 3, 2), 1}, - {id(1, 2, 3, 4), 80}, - {id(1, 2, 3, 5), 16}, - {id(20, 2, 3, 5), 1}, - {id(30, 2, 3, 5), 1}, - {id(40, 2, 3, 5), 1}, - {id(50, 2, 3, 5), 1}, - {id(100, 2, 3, 4), 10}, - }; - - // assetsEndEpoch() and as.indexLists.rebuild(), which are called at the end of each epoch, lead to speeding up creating a new proposal (by requiring less sorting operations) - as.indexLists.rebuild(); - - // add another proposal (has changed set of voters) - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[1].first), 1); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), 1); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), (int)INVALID_PROPOSAL_INDEX); - - // check voting rights (voters are shareholders at time of creating/updating proposal) - checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); - checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares); - - // add third proposal (has changed set of voters) - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[2].first), 2); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), 1); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), 2); - - // check voting rights (voters are shareholders at time of creating/updating proposal) - checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); - checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares); - checkShareholderVotingRights(qpi, pv, 2, changedPossessorShares); - - // transfer some shares - qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 80, id(1, 2, 2, 0)); - std::vector> changedPossessorShares2{ - {id(0, 0, 0, 1), 400}, - {id(1, 2, 2, 0), 80}, - {id(1, 2, 2, 1), 65}, - {id(1, 2, 3, 0), 39}, - {id(1, 2, 3, 1), 61}, - {id(1, 2, 3, 2), 1}, - //{id(1, 2, 3, 4), 0}, - {id(1, 2, 3, 5), 16}, - {id(20, 2, 3, 5), 1}, - {id(30, 2, 3, 5), 1}, - {id(40, 2, 3, 5), 1}, - {id(50, 2, 3, 5), 1}, - {id(100, 2, 3, 4), 10}, - }; - - // all slots filled -> adding proposal using other ID fails - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[3].first), (int)INVALID_PROPOSAL_INDEX); - - // free one slot - pv.freeProposalByIndex(qpi, 1); - - // using other ID now works - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[3].first), 1); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), (int)INVALID_PROPOSAL_INDEX); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), 2); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[3].first), 1); - - // check voting rights (voters are shareholders at time of creating/updating proposal) - checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); - checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares2); - checkShareholderVotingRights(qpi, pv, 2, changedPossessorShares); - - // reusing all slots should work (overwriting proposals sets voters) - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[0].first), 0); - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[1].first), (int)INVALID_PROPOSAL_INDEX); - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[2].first), 2); - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[3].first), 1); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), (int)INVALID_PROPOSAL_INDEX); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), 2); - EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[3].first), 1); - - // check voting rights (voters are shareholders at time of creating/updating proposal) - checkShareholderVotingRights(qpi, pv, 0, changedPossessorShares2); - checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares2); - checkShareholderVotingRights(qpi, pv, 2, changedPossessorShares2); -} - -TEST(TestCoreQPI, ProposalAndVotingByShareholdersTestSorting) -{ - constexpr int shareholderFactor = 512; // set 2 to run more tests - constexpr int shareholdersWithTwoRecordsStep = 5; // set 1 to run more tests - - static constexpr uint64 MSVAULT_ASSET_NAME = 23727827095802701; - for (int shareholders = 1; shareholders <= 512; shareholders *= shareholderFactor) - { - for (int shareholdersWithTwoRecords = min(shareholders, 4); shareholdersWithTwoRecords > 0; shareholdersWithTwoRecords -= shareholdersWithTwoRecordsStep) - { - ContractTesting test; - test.initEmptyUniverse(); - QpiContextUserProcedureCall qpi(QX_CONTRACT_INDEX, QPI::id(1, 2, 3, 4), 123); - ProposalAndVotingByShareholders<3, MSVAULT_ASSET_NAME> pv; - initComputors(0); - - // Memory must be zeroed to work, which is done in contract states on init - setMemory(pv, 0); - - // create contract shares - std::vector> initialPossessorShares; - std::vector twoRecords; - for (int i = 0; i < shareholders; ++i) - { - initialPossessorShares.push_back({ id::randomValue(), 1 }); - } - for (int i = 0; i < shareholdersWithTwoRecords; ++i) - { - // randomly select entries that shall have two records - int idx = (int)initialPossessorShares[i].first.u64._0 % initialPossessorShares.size(); - initialPossessorShares[idx].second = 2; - twoRecords.push_back(idx); - } - issueContractShares(MSVAULT_CONTRACT_INDEX, initialPossessorShares, false); - - // transfer assset management rights in order to create two records - Asset asset{ NULL_ID, MSVAULT_ASSET_NAME }; - for (int i = 0; i < shareholdersWithTwoRecords; ++i) - { - const id& possessorId = initialPossessorShares[twoRecords[i]].first; - AssetPossessionIterator it(asset, { possessorId, QX_CONTRACT_INDEX }, { possessorId, QX_CONTRACT_INDEX }); - EXPECT_TRUE(transferShareManagementRights(it.ownershipIndex(), it.possessionIndex(), MSVAULT_CONTRACT_INDEX, MSVAULT_CONTRACT_INDEX, 1, nullptr, nullptr, true)); - } - - // add proposal - EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[0].first), 0); - - // check voting rights (voters are shareholders at time of creating/updating proposal) - sortContractShareVector(initialPossessorShares); - checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); - } - } -} - -// Test internal class ProposalWithAllVoteData that stores valid proposals along with its votes -template -void testProposalWithAllVoteDataOptionVotes( - QPI::ProposalWithAllVoteData& pwav, - const ProposalT& proposal, - QPI::sint64 numOptions -) -{ - ASSERT_TRUE(numOptions >= 2); - ASSERT_TRUE(proposal.checkValidity()); - ASSERT_TRUE(QPI::ProposalTypes::isValid(proposal.type)); - if (proposal.type == QPI::ProposalTypes::VariableScalarMean) - ASSERT_TRUE(QPI::ProposalTypes::optionCount(proposal.type) == 0); - else - ASSERT_TRUE(QPI::ProposalTypes::optionCount(proposal.type) == numOptions); - - // set proposal / clear votes - EXPECT_TRUE(pwav.set(proposal)); - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_EQ(pwav.getVoteValue(i), QPI::NO_VOTE_VALUE); - - // test that out-of-range voter indices cannot be set - EXPECT_FALSE(pwav.setVoteValue(numVoters, 0)); - EXPECT_FALSE(pwav.setVoteValue(numVoters, QPI::NO_VOTE_VALUE)); - EXPECT_FALSE(pwav.setVoteValue(numVoters+1, 123)); - - // test that out-of-range vote values cannot be set - EXPECT_FALSE(pwav.setVoteValue(0, -123456)); - EXPECT_FALSE(pwav.setVoteValue(0, -1)); - EXPECT_FALSE(pwav.setVoteValue(0, numOptions)); - EXPECT_FALSE(pwav.setVoteValue(0, 123456)); - - // set and check valid vote range - for (QPI::uint32 j = 0; j < numOptions; ++j) - { - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_TRUE(pwav.setVoteValue(i, (j + i) % numOptions)); - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_EQ(pwav.getVoteValue(i), (j + i) % numOptions); - - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_TRUE(pwav.setVoteValue(i, (j + numVoters - i) % numOptions)); - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_EQ(pwav.getVoteValue(i), (j + numVoters - i) % numOptions); - } - - // clear vote - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_TRUE(pwav.setVoteValue(i, QPI::NO_VOTE_VALUE)); - for (QPI::uint32 i = 0; i < numVoters; ++i) - EXPECT_EQ(pwav.getVoteValue(i), QPI::NO_VOTE_VALUE); -} - -// Test internal class ProposalWithAllVoteData that stores valid proposals along with its votes -template -void testProposalWithAllVoteData() -{ - typedef QPI::ProposalDataV1 ProposalT; - QPI::ProposalWithAllVoteData pwav; - ProposalT proposal; - - // YesNo proposal - proposal.type = QPI::ProposalTypes::YesNo; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // ThreeOption proposal - proposal.type = QPI::ProposalTypes::ThreeOptions; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); - - // Proposal with 8 options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 8); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 8); - - // TransferYesNo proposal - proposal.type = QPI::ProposalTypes::TransferYesNo; - proposal.data.transfer.destination = QPI::id(1, 2, 3, 4); - proposal.data.transfer.amounts.setAll(0); - proposal.data.transfer.amounts.set(0, 1234); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // TransferTwoAmounts - proposal.type = QPI::ProposalTypes::TransferTwoAmounts; - proposal.data.transfer.amounts.set(1, 12345); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); - - // TransferThreeAmounts - proposal.type = QPI::ProposalTypes::TransferThreeAmounts; - proposal.data.transfer.amounts.set(2, 123456); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 4); - - // TransferFourAmounts - proposal.type = QPI::ProposalTypes::TransferFourAmounts; - proposal.data.transfer.amounts.set(3, 1234567); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 5); - - // TransferInEpochYesNo proposal - proposal.type = QPI::ProposalTypes::TransferInEpochYesNo; - proposal.data.transferInEpoch.destination = QPI::id(1, 2, 3, 4); - proposal.data.transferInEpoch.amount = 10; - proposal.data.transferInEpoch.targetEpoch = 123; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // fail: test TransferInEpoch proposal with too many or too few options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::TransferInEpoch, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::TransferInEpoch, 3); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - - // VariableYesNo proposal - proposal.type = QPI::ProposalTypes::VariableYesNo; - proposal.data.variableOptions.variable = 42; - proposal.data.variableOptions.values.set(0, 987); - proposal.data.variableOptions.values.setRange(1, proposal.data.variableOptions.values.capacity(), 0); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // VariableTwoValues proposal - proposal.type = QPI::ProposalTypes::VariableTwoValues; - proposal.data.variableOptions.values.set(1, 9876); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); - - // VariableThreeValues proposal - proposal.type = QPI::ProposalTypes::VariableThreeValues; - proposal.data.variableOptions.values.set(2, 98765); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 4); - - // VariableFourValues proposal - proposal.type = QPI::ProposalTypes::VariableFourValues; - proposal.data.variableOptions.values.set(3, 987654); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 5); - - // fail: test variable proposal with too many or too few options (0 options means scalar) - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 6); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - - // fail: wrong sorting with class Variable - proposal.type = QPI::ProposalTypes::VariableFourValues; - for (int i = 0; i < 4; ++i) - proposal.data.variableOptions.values.set(i, 20 - i); - EXPECT_FALSE(proposal.checkValidity()); - - // VariableScalarMean proposal - proposal.type = QPI::ProposalTypes::VariableScalarMean; - proposal.data.variableScalar.variable = 42; - proposal.data.variableScalar.minValue = 0; - proposal.data.variableScalar.maxValue = 25; - proposal.data.variableScalar.proposedValue = 1; - if (supportScalarVotes) - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 26); - else - EXPECT_FALSE(pwav.set(proposal)); - - // MultiVariablesYesNo proposal - proposal.type = QPI::ProposalTypes::MultiVariablesYesNo; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // MultiVariablesThreeOptions proposal - proposal.type = QPI::ProposalTypes::MultiVariablesThreeOptions; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); - - // MultiVariablesFourOptions proposal - proposal.type = QPI::ProposalTypes::MultiVariablesFourOptions; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 4); - - // MultiVariables proposal with 8 options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::MultiVariables, 8); - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 8); - - // fail: test MultiVariables proposal with too many or too few options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::MultiVariables, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::MultiVariables, 9); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); -} - -TEST(TestCoreQPI, ProposalWithAllVoteDataWithScalarVoteSupport) -{ - testProposalWithAllVoteData(); -} - -TEST(TestCoreQPI, ProposalWithAllVoteDataWithoutScalarVoteSupport) -{ - testProposalWithAllVoteData(); -} - -TEST(TestCoreQPI, ProposalWithAllVoteDataYesNoProposals) -{ - // Using ProposalDataYesNo saves storage space by only supporting yes/no choices - // (or up to 3 options for proposal classes that don't store option values) - ContractExecInitDeinitGuard initDeinitGuard; - typedef QPI::ProposalDataYesNo ProposalT; - QPI::ProposalWithAllVoteData pwav; - ProposalT proposal; - - // YesNo proposal - proposal.type = QPI::ProposalTypes::YesNo; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // ThreeOption proposal (accepted for general proposal, because it does not cost anything) - proposal.type = QPI::ProposalTypes::ThreeOptions; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); - - // Proposal with 4 options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 4); - EXPECT_FALSE(proposal.checkValidity()); - - // Proposal with 8 options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 8); - EXPECT_FALSE(proposal.checkValidity()); - - // TransferYesNo proposal - proposal.type = QPI::ProposalTypes::TransferYesNo; - proposal.data.transfer.destination = QPI::id(1, 2, 3, 4); - proposal.data.transfer.amount = 1234; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // TransferTwoAmounts - proposal.type = QPI::ProposalTypes::TransferTwoAmounts; - EXPECT_FALSE(proposal.checkValidity()); - - // TransferThreeAmounts - proposal.type = QPI::ProposalTypes::TransferThreeAmounts; - EXPECT_FALSE(proposal.checkValidity()); - - // fail: TransferInEpochYesNo proposal not supported due to lack of storage - proposal.type = QPI::ProposalTypes::TransferInEpochYesNo; - EXPECT_FALSE(proposal.checkValidity()); - - // VariableYesNo proposal - proposal.type = QPI::ProposalTypes::VariableYesNo; - proposal.data.variableOptions.variable = 42; - proposal.data.variableOptions.value = 987; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // VariableTwoValues proposal - proposal.type = QPI::ProposalTypes::VariableTwoValues; - EXPECT_FALSE(proposal.checkValidity()); - - // VariableThreeValues proposal - proposal.type = QPI::ProposalTypes::VariableThreeValues; - EXPECT_FALSE(proposal.checkValidity()); - - // fail: test variable proposal with too many or too few options (0 options means scalar) - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 6); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - EXPECT_FALSE(proposal.checkValidity()); - - // VariableScalarMean proposal - proposal.type = QPI::ProposalTypes::VariableScalarMean; - EXPECT_FALSE(proposal.checkValidity()); - - // MultiVariablesYesNo proposal - proposal.type = QPI::ProposalTypes::MultiVariablesYesNo; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); - - // MultiVariablesThreeOptions proposal (accepted for multiple variables proposal, because it does not cost anything) - proposal.type = QPI::ProposalTypes::MultiVariablesThreeOptions; - testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); - - // MultiVariablesFourOptions proposal - proposal.type = QPI::ProposalTypes::MultiVariablesFourOptions; - EXPECT_FALSE(proposal.checkValidity()); -} - -template -void expectNoVotes( - const QPI::QpiContextFunctionCall& qpi, - const ProposalVotingType& pv, - QPI::uint16 proposalIndex -) -{ - QPI::ProposalSingleVoteDataV1 vote; - for (QPI::uint32 i = 0; i < pv->maxVotes; ++i) - { - EXPECT_TRUE(qpi(*pv).getVote(proposalIndex, i, vote)); - EXPECT_EQ(vote.voteValue, QPI::NO_VOTE_VALUE); - } - - QPI::ProposalSummarizedVotingDataV1 votingSummaryReturned; - EXPECT_TRUE(qpi(*pv).getVotingSummary(proposalIndex, votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 0); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); -} - -template -bool isReturnedProposalAsExpected( - const QPI::QpiContextFunctionCall& qpi, - const QPI::ProposalDataV1& proposalReturnedByGet, const QPI::ProposalDataV1& proposalSet) -{ - bool expected = - proposalReturnedByGet.tick == qpi.tick() - && proposalReturnedByGet.epoch == qpi.epoch() - && proposalReturnedByGet.type == proposalSet.type - && proposalReturnedByGet.supportScalarVotes == proposalSet.supportScalarVotes - && (memcmp(&proposalReturnedByGet.data.transfer, &proposalSet.data.transfer, sizeof(proposalSet.data.transfer)) == 0) - && (memcmp(&proposalReturnedByGet.data.variableOptions, &proposalSet.data.variableOptions, sizeof(proposalSet.data.variableOptions)) == 0) - && (memcmp(&proposalReturnedByGet.data.variableScalar, &proposalSet.data.variableScalar, sizeof(proposalSet.data.variableScalar)) == 0) - && (memcmp(&proposalReturnedByGet.url, &proposalSet.url, sizeof(proposalSet.url)) == 0); - return expected; -} - -bool operator==(const QPI::ProposalSingleVoteDataV1& p1, const QPI::ProposalSingleVoteDataV1& p2) -{ - return memcmp(&p1, &p2, sizeof(p1)) == 0; -} - -bool operator==(const QPI::ProposalMultiVoteDataV1& p1, const QPI::ProposalMultiVoteDataV1& p2) -{ - return memcmp(&p1, &p2, sizeof(p1)) == 0; -} - - -template -void setProposalWithSuccessCheck(const QPI::QpiContextProcedureCall& qpi, const ProposalVotingType& pv, const QPI::id& proposerId, const ProposalDataType& proposal) -{ - ProposalDataType proposalReturned; - EXPECT_NE((int)qpi(*pv).setProposal(proposerId, proposal), (int)QPI::INVALID_PROPOSAL_INDEX); - QPI::uint16 proposalIdx = qpi(*pv).proposalIndex(proposerId); - EXPECT_NE((int)proposalIdx, (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(qpi(*pv).proposerId(proposalIdx), proposerId); - EXPECT_TRUE(qpi(*pv).getProposal(proposalIdx, proposalReturned)); - EXPECT_TRUE(isReturnedProposalAsExpected(qpi, proposalReturned, proposal)); - expectNoVotes(qpi, pv, proposalIdx); -} - -template -void setProposalExpectFailure(const QPI::QpiContextProcedureCall& qpi, const ProposalVotingType& pv, const QPI::id& proposerId, const ProposalDataType& proposal) -{ - EXPECT_EQ((int)qpi(*pv).setProposal(proposerId, proposal), (int)QPI::INVALID_PROPOSAL_INDEX); -} - -template -void voteWithValidVoter( - const QPI::QpiContextProcedureCall& qpi, - ProposalVotingType& pv, - const QPI::id& voterId, - QPI::uint16 proposalIndex, - QPI::uint16 proposalType, - QPI::uint32 proposalTick, - QPI::sint64 voteValue -) -{ - QPI::uint32 voterIdx = qpi(pv).voteIndex(voterId); - EXPECT_NE(voterIdx, QPI::INVALID_VOTE_INDEX); - QPI::id voterIdReturned = qpi(pv).voterId(voterIdx); - EXPECT_EQ(voterIdReturned, voterId); - - QPI::ProposalSingleVoteDataV1 voteReturnedBefore; - bool oldVoteAvailable = qpi(pv).getVote(proposalIndex, voterIdx, voteReturnedBefore); - - QPI::ProposalSingleVoteDataV1 vote; - vote.proposalIndex = proposalIndex; - vote.proposalType = proposalType; - vote.proposalTick = proposalTick; - vote.voteValue = voteValue; - EXPECT_EQ(qpi(pv).vote(voterId, vote), successExpected); - - QPI::ProposalSingleVoteDataV1 voteReturned; - if (successExpected) - { - EXPECT_TRUE(qpi(pv).getVote(vote.proposalIndex, voterIdx, voteReturned)); - EXPECT_TRUE(vote == voteReturned); - - typename ProposalVotingType::ProposalDataType proposalReturned; - EXPECT_TRUE(qpi(pv).getProposal(vote.proposalIndex, proposalReturned)); - EXPECT_TRUE(proposalReturned.type == voteReturned.proposalType); - EXPECT_TRUE(proposalReturned.tick == voteReturned.proposalTick); - } - else if (oldVoteAvailable) - { - EXPECT_TRUE(qpi(pv).getVote(vote.proposalIndex, voterIdx, voteReturned)); - EXPECT_TRUE(voteReturnedBefore == voteReturned); - } -} - -template -void voteWithValidVoterMultiVote( - const QPI::QpiContextProcedureCall& qpi, - ProposalVotingType& pv, - const QPI::id& voterId, - QPI::uint16 proposalIndex, - QPI::uint16 proposalType, - QPI::uint32 proposalTick, - QPI::sint64 voteValue, - QPI::uint32 voteCount = 0, // for first voteValue, 0 means all - QPI::sint64 voteValue2 = 0, - QPI::uint32 voteCount2 = 0, - QPI::sint64 voteValue3 = 0, - QPI::uint32 voteCount3 = 0 -) -{ - QPI::uint32 voterIdx = qpi(pv).voteIndex(voterId); - EXPECT_NE(voterIdx, QPI::INVALID_VOTE_INDEX); - QPI::id voterIdReturned = qpi(pv).voterId(voterIdx); - EXPECT_EQ(voterIdReturned, voterId); - - QPI::ProposalMultiVoteDataV1 voteReturnedBefore; - bool oldVoteAvailable = qpi(pv).getVotes(proposalIndex, voterId, voteReturnedBefore); - - // set all votes of voter (1 in computor voting) with multi-data voting function - QPI::ProposalMultiVoteDataV1 vote; - vote.proposalIndex = proposalIndex; - vote.proposalType = proposalType; - vote.proposalTick = proposalTick; - vote.voteValues.setAll(0); - vote.voteCounts.setAll(0); - vote.voteValues.set(0, voteValue); - if (voteCount != 0) - { - vote.voteCounts.set(0, voteCount); - vote.voteValues.set(1, voteValue2); - vote.voteCounts.set(1, voteCount2); - vote.voteValues.set(2, voteValue3); - vote.voteCounts.set(2, voteCount3); - } - EXPECT_EQ(qpi(pv).vote(voterId, vote), successExpected); - - QPI::ProposalMultiVoteDataV1 voteReturned; - if (successExpected) - { - EXPECT_TRUE(qpi(pv).getVotes(vote.proposalIndex, voterId, voteReturned)); - EXPECT_EQ((int)vote.proposalIndex, (int)voteReturned.proposalIndex); - EXPECT_EQ((int)vote.proposalType, (int)voteReturned.proposalType); - EXPECT_EQ(vote.proposalTick, voteReturned.proposalTick); - int totalVoteCount = qpi(pv).voteCount(voterIdx, proposalIndex); - if (voteCount == 0) - { - for (int i = 0; i < voteReturned.voteCounts.capacity(); ++i) - { - if (voteReturned.voteValues.get(i) == voteValue) - { - EXPECT_EQ(voteReturned.voteCounts.get(i), totalVoteCount); - totalVoteCount = 0; // for duplicates (e.g. value 0), expect count 0 - } - else - { - EXPECT_EQ(voteReturned.voteCounts.get(i), 0); - } - } - } - else - { - std::set voteInputIdx = { 0, 1, 2 }; - for (int i = 0; i < voteReturned.voteCounts.capacity(); ++i) - { - if (voteReturned.voteCounts.get(i) != 0) - { - bool found = false; - for (int j = 0; j < 4; ++j) - { - if (vote.voteValues.get(j) == voteReturned.voteValues.get(i)) - { - EXPECT_EQ(vote.voteCounts.get(j), voteReturned.voteCounts.get(i)); - EXPECT_TRUE(voteInputIdx.contains(j)); - voteInputIdx.erase(j); - found = true; - break; - } - } - EXPECT_TRUE(found); - } - } - EXPECT_TRUE(voteInputIdx.empty() || voteCount3 == 0); - } - - typename ProposalVotingType::ProposalDataType proposalReturned; - EXPECT_TRUE(qpi(pv).getProposal(vote.proposalIndex, proposalReturned)); - EXPECT_TRUE(proposalReturned.type == voteReturned.proposalType); - EXPECT_TRUE(proposalReturned.tick == voteReturned.proposalTick); - } - else if (oldVoteAvailable) - { - EXPECT_TRUE(qpi(pv).getVotes(vote.proposalIndex, voterId, voteReturned)); - EXPECT_TRUE(voteReturnedBefore == voteReturned); - } -} - - -template -void voteWithInvalidVoter( - const QPI::QpiContextProcedureCall& qpi, - ProposalVotingType& pv, - const QPI::id& voterId, - QPI::uint16 proposalIndex, - QPI::uint16 proposalType, - QPI::uint32 proposalTick, - QPI::sint64 voteValue -) -{ - QPI::ProposalSingleVoteDataV1 vote; - vote.proposalIndex = proposalIndex; - vote.proposalType = proposalType; - vote.proposalTick = proposalTick; - vote.voteValue = voteValue; - EXPECT_FALSE(qpi(pv).vote(voterId, vote)); - - QPI::ProposalMultiVoteDataV1 vote2; - vote2.proposalIndex = proposalIndex; - vote2.proposalType = proposalType; - vote2.proposalTick = proposalTick; - vote2.voteValues.setAll(0); - vote2.voteValues.set(0, voteValue); - vote2.voteCounts.setAll(0); - EXPECT_FALSE(qpi(pv).vote(voterId, vote2)); -} - - -template -int countActiveProposals( - const QPI::QpiContextFunctionCall& qpi, - const ProposalVotingType& pv -) -{ - int activeProposals = 0; - QPI::sint32 idx = -1; - while ((idx = qpi(*pv).nextProposalIndex(idx)) >= 0) - ++activeProposals; - return activeProposals; -} - -template -int countFinishedProposals( - const QPI::QpiContextFunctionCall& qpi, - const ProposalVotingType& pv -) -{ - int finishedProposals = 0; - QPI::sint32 idx = -1; - while ((idx = qpi(*pv).nextFinishedProposalIndex(idx)) >= 0) - ++finishedProposals; - return finishedProposals; -} - -template -void testProposalVotingComputorsV1() -{ - ContractExecInitDeinitGuard initDeinitGuard; - - system.tick = 123456789; - system.epoch = 12345; - initComputors(0); - - typedef std::conditional< - proposalByComputorsOnly, - QPI::ProposalAndVotingByComputors<200>, // Allow less proposals than NUMBER_OF_COMPUTORS to check handling of full arrays - QPI::ProposalByAnyoneVotingByComputors<200> - >::type ProposerAndVoterHandling; - - QpiContextUserProcedureCall qpi(0, QPI::id(1,2,3,4), 123); - auto * pv = new QPI::ProposalVoting< - ProposerAndVoterHandling, - QPI::ProposalDataV1>; - - // Memory must be zeroed to work, which is done in contract states on init - QPI::setMemory(*pv, 0); - - // fail: get before proposals have been set - QPI::ProposalDataV1 proposalReturned; - QPI::ProposalSingleVoteDataV1 voteDataReturned; - QPI::ProposalSummarizedVotingDataV1 votingSummaryReturned; - for (int i = 0; i < pv->maxProposals; ++i) - { - proposalReturned.type = 42; // test that additional error indicator is set 0 - EXPECT_FALSE(qpi(*pv).getProposal(i, proposalReturned)); - EXPECT_EQ((int)proposalReturned.type, 0); - - voteDataReturned.proposalType = 42; // test that additional error indicator is set 0 - EXPECT_FALSE(qpi(*pv).getVote(i, 0, voteDataReturned)); - EXPECT_EQ((int)voteDataReturned.proposalType, 0); - - votingSummaryReturned.totalVotesAuthorized = 42; // test that additional error indicator is set 0 - EXPECT_FALSE(qpi(*pv).getVotingSummary(i, votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, 0); - } - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), -1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(123456), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(0), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(123456), -1); - - // fail: get with invalid proposal index - EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals, proposalReturned)); - EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals + 1, proposalReturned)); - EXPECT_FALSE(qpi(*pv).getVote(pv->maxProposals, 0, voteDataReturned)); - EXPECT_FALSE(qpi(*pv).getVote(pv->maxProposals + 1, 0, voteDataReturned)); - EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals, votingSummaryReturned)); - EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals + 1, votingSummaryReturned)); - - // fail: no proposals for given IDs and invalid input - EXPECT_EQ((int)qpi(*pv).proposalIndex(QPI::NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); // always equal - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(0)), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(qpi(*pv).proposerId(QPI::INVALID_PROPOSAL_INDEX), QPI::NULL_ID); // always equal - EXPECT_EQ(qpi(*pv).proposerId(pv->maxProposals), QPI::NULL_ID); // always equal - EXPECT_EQ(qpi(*pv).proposerId(0), QPI::NULL_ID); - EXPECT_EQ(qpi(*pv).proposerId(1), QPI::NULL_ID); - - // okay: voters are available independently of proposals (= computors) - for (int i = 0; i < pv->maxVotes; ++i) - { - EXPECT_EQ(qpi(*pv).voteIndex(qpi.computor(i)), i); - EXPECT_EQ(qpi(*pv).voteCount(i), 1); - EXPECT_EQ(qpi(*pv).voterId(i), qpi.computor(i)); - } - - // fail: IDs / indices of non-voters - EXPECT_EQ(qpi(*pv).voteIndex(qpi.originator()), QPI::INVALID_VOTE_INDEX); - EXPECT_EQ(qpi(*pv).voteIndex(QPI::NULL_ID), QPI::INVALID_VOTE_INDEX); - EXPECT_EQ(qpi(*pv).voteCount(QPI::INVALID_VOTE_INDEX), 0); - EXPECT_EQ(qpi(*pv).voteCount(1000), 0); - EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes), QPI::NULL_ID); - EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes + 1), QPI::NULL_ID); - - // okay: set proposal for computor 0 - QPI::ProposalDataV1 proposal; - proposal.url.set(0, 0); - proposal.epoch = qpi.epoch(); - proposal.type = QPI::ProposalTypes::YesNo; - setProposalWithSuccessCheck(qpi, pv, qpi.computor(0), proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(0)), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote although no proposal is available at proposal index - voteWithValidVoter(qpi, *pv, qpi.computor(0), 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithValidVoter(qpi, *pv, qpi.computor(0), 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - - // fail: vote with wrong type - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); - - // fail: vote with non-computor - voteWithInvalidVoter(qpi, *pv, qpi.originator(), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - - // fail: vote with invalid value - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); - - // fail: vote with wrong tick - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()-1, 0); - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()-1, 0); - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()+1, 0); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()+1, 0); - - // okay: correct votes in proposalIndex 0 - expectNoVotes(qpi, pv, 0); - for (int i = 0; i < pv->maxVotes; ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); - voteWithValidVoter(qpi, *pv, qpi.computor(i), 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); - } - voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - EXPECT_TRUE(qpi(*pv).getVotingSummary(0, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 0); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes - 1); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), pv->maxVotes / 2 - 1); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), pv->maxVotes / 2); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 1); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 1); - - if (proposalByComputorsOnly) - { - // fail: originator id(1,2,3,4) is no computor (see custom qpi.computor() above) - setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); - } - else - { - // okay if anyone is allowed to set proposal - setProposalWithSuccessCheck(qpi, pv, qpi.originator(), proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // clear proposal again - EXPECT_TRUE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.originator()))); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); - } - - // fail: invalid type (more options than supported) - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 9); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // fail: invalid type (less options than supported) - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 0); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // okay: set proposal for computor 2 / other ID (proposal index 1, first use) - QPI::id secondNonComputorId(12345, 6789, 987, 654); - QPI::id secondProposer = (proposalByComputorsOnly) ? qpi.computor(2) : secondNonComputorId; - proposal.type = QPI::ProposalTypes::FourOptions; - proposal.epoch = 1; // non-zero means current epoch - setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) - voteWithValidVoter(qpi, *pv, qpi.computor(0), 1, proposal.type, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 1, proposal.type, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(1), 1, proposal.type, qpi.tick(), 4); - voteWithValidVoter(qpi, *pv, qpi.computor(1), 1, proposal.type, qpi.tick(), 4); - - // fail: vote with non-computor - voteWithInvalidVoter(qpi, *pv, qpi.originator(), 1, proposal.type, qpi.tick(), 0); - voteWithInvalidVoter(qpi, *pv, secondNonComputorId, 1, proposal.type, qpi.tick(), 0); - voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 1, proposal.type, qpi.tick(), 0); - - // okay: correct votes in proposalIndex 1 (first use) - expectNoVotes(qpi, pv, 1); - for (int i = 0; i < pv->maxVotes; ++i) - { - voteWithValidVoter(qpi, *pv, qpi.computor(i), 1, proposal.type, qpi.tick(), (i < 100) ? i % 4 : 3); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), 1, proposal.type, qpi.tick(), (i < 100) ? i % 4 : 3); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), 25); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), 25); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(2), 25); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(3), pv->maxVotes - 75); - for (int i = 4; i < votingSummaryReturned.optionVoteCount.capacity(); ++i) - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), 0); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 3); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 3); - - // fail: proposal of transfer with wrong address - proposal.type = QPI::ProposalTypes::TransferYesNo; - proposal.data.transfer.destination = QPI::NULL_ID; - proposal.data.transfer.amounts.setAll(0); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - // check that overwrite did not work - EXPECT_TRUE(qpi(*pv).getProposal(qpi(*pv).proposalIndex(secondProposer), proposalReturned)); - EXPECT_FALSE(isReturnedProposalAsExpected(qpi, proposalReturned, proposal)); - - // fail: proposal of transfer with too many or too few options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 0); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 6); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - - // fail: proposal of revenue distribution with invalid amount - proposal.type = QPI::ProposalTypes::TransferYesNo; - proposal.data.transfer.destination = qpi.originator(); - proposal.data.transfer.amounts.set(0, -123456); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - - // okay: revenue distribution, overwrite existing proposal of comp 2 (proposal index 1, reused) - proposal.data.transfer.destination = qpi.originator(); - proposal.data.transfer.amounts.set(0, 1005); - setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) - QPI::uint16 secondProposalIdx = qpi(*pv).proposalIndex(secondProposer); - voteWithValidVoter(qpi, *pv, qpi.computor(0), secondProposalIdx, proposal.type, qpi.tick(), -1); - voteWithValidVoter(qpi, *pv, qpi.computor(1), secondProposalIdx, proposal.type, qpi.tick(), 2); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), secondProposalIdx, proposal.type, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(1), secondProposalIdx, proposal.type, qpi.tick(), 2); - - // okay: correct votes in proposalIndex 1 (reused) - expectNoVotes(qpi, pv, 1); // checks that setProposal clears previous votes - for (int i = 0; i < pv->maxVotes; ++i) - { - voteWithValidVoter(qpi, *pv, qpi.computor(i), secondProposalIdx, proposal.type, qpi.tick(), i % 2); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), secondProposalIdx, proposal.type, qpi.tick(), i % 2); - } - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(3), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(5), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - voteWithValidVoter(qpi, *pv, qpi.computor(3), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - voteWithValidVoter(qpi, *pv, qpi.computor(5), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes - 2); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), pv->maxVotes / 2); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), pv->maxVotes / 2 - 2); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 0); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 0); - - if (!supportScalarVotes) - { - // fail: scalar proposal not supported - proposal.type = QPI::ProposalTypes::VariableScalarMean; - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - } - else - { - // fail: scalar proposal with wrong min/max - proposal.type = QPI::ProposalTypes::VariableScalarMean; - proposal.data.variableScalar.proposedValue = 10; - proposal.data.variableScalar.minValue = 11; - proposal.data.variableScalar.maxValue = 20; - proposal.data.variableScalar.variable = 123; // not checked, full range usable - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - proposal.data.variableScalar.minValue = 0; - proposal.data.variableScalar.maxValue = 9; - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // fail: scalar proposal with full range is invalid, because NO_VOTE_VALUE is reserved for no vote - proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue - 1; - proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // okay: scalar proposal with nearly full range - proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue; - proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; - setProposalWithSuccessCheck(qpi, pv, qpi.computor(1), proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(1)), 2); - EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), (int)secondProposalIdx); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); - EXPECT_EQ(qpi(*pv).nextProposalIndex(2), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // okay: votes in proposalIndex of computor 1 for testing overflow-avoiding summary algorithm for average - expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(qpi.computor(1))); - for (int i = 0; i < 99; ++i) - { - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.maxSupportedValue - 2 + i % 3); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.maxSupportedValue - 2 + i % 3); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(qpi.computor(1)), votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, (int)qpi(*pv).proposalIndex(qpi.computor(1))); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 99); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), -1); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.maxSupportedValue - 1); - for (int i = 0; i < 555; ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.minSupportedValue + 10 - i % 5); - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.minSupportedValue + 10 - i % 5); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(qpi.computor(1)), votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 555); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.minSupportedValue + 8); - - // okay: scalar proposal with limited range - proposal.data.variableScalar.minValue = -1000; - proposal.data.variableScalar.maxValue = 1000; - setProposalWithSuccessCheck(qpi, pv, qpi.computor(10), proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(10)), 3); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); - EXPECT_EQ(qpi(*pv).nextProposalIndex(2), 3); - EXPECT_EQ(qpi(*pv).nextProposalIndex(3), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote with invalid values - voteWithValidVoter(qpi, *pv, qpi.computor(0), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), -1001); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), -1001); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(1), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 1001); - voteWithValidVoter(qpi, *pv, qpi.computor(1), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 1001); - - // okay: correct votes in proposalIndex of computor 10 - expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(qpi.computor(10))); - for (int i = 0; i < 603; ++i) - { - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), (i % 201) - 100); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), (i % 201) - 100); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 603); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, 0); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), -1); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); - - // another case for scalar voting summary - for (int i = 0; i < 603; ++i) - { - voteWithValidVoterMultiVote (qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - } - for (int i = 0; i < 200; ++i) - { - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), i + 1); - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), i + 1); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 200); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, (200 * 201 / 2) / 200); - } - - // fail: test multi-option transfer proposal with invalid amounts - proposal.type = QPI::ProposalTypes::TransferThreeAmounts; - proposal.data.transfer.destination = qpi.originator(); - for (int i = 0; i < 4; ++i) - { - proposal.data.transfer.amounts.setAll(0); - proposal.data.transfer.amounts.set(i, -100 * i - 1); - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - } - proposal.data.transfer.amounts.set(0, 0); - proposal.data.transfer.amounts.set(1, 10); - proposal.data.transfer.amounts.set(2, 20); - proposal.data.transfer.amounts.set(3, 100); // for ProposalTypes::TransferThreeAmounts, fourth must be 0 - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // fail: duplicate options - proposal.data.transfer.amounts.setAll(0); - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // fail: options not sorted - for (int i = 0; i < 3; ++i) - proposal.data.transfer.amounts.set(i, 100 - i); - setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); - - // okay: fill proposal storage - proposal.data.transfer.amounts.setAll(0); - constexpr QPI::uint16 computorProposalToFillAll = (proposalByComputorsOnly) ? pv->maxProposals : pv->maxProposals - 1; - for (int i = 0; i < computorProposalToFillAll; ++i) - { - proposal.data.transfer.amounts.set(0, i); - proposal.data.transfer.amounts.set(1, i * 2 + 1); - proposal.data.transfer.amounts.set(2, i * 3 + 2); - setProposalWithSuccessCheck(qpi, pv, qpi.computor(i), proposal); - } - EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: no space left - setProposalExpectFailure(qpi, pv, qpi.computor(pv->maxProposals), proposal); - - // cast some votes before epoch change to test querying voting summary afterwards - for (int i = 0; i < 20; ++i) - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 0); - for (int i = 20; i < 60; ++i) - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 1); - for (int i = 60; i < 160; ++i) - voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 2); - for (int i = 160; i < 360; ++i) - voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 3); - - // simulate epoch change - ++system.epoch; - EXPECT_EQ(countActiveProposals(qpi, pv), 0); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals); - - // okay: same setProposal after epoch change, because the oldest proposal will be deleted - proposal.epoch = qpi.epoch(); - setProposalWithSuccessCheck(qpi, pv, qpi.computor(pv->maxProposals), proposal); - EXPECT_EQ(countActiveProposals(qpi, pv), 1); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 1); - - // fail: vote in wrong epoch - voteWithValidVoter(qpi, *pv, qpi.computor(123), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 0); - - // okay: query voting summary of other epoch - EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(qpi.computor(10)), votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 20+40+100+200); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), 20); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), 40); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(2), 100); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(3), 200); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(4), 0); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 3); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); - - // manually clear some proposals - EXPECT_FALSE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.originator()))); - EXPECT_TRUE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.computor(5)))); - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(5)), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(countActiveProposals(qpi, pv), 1); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); - proposal.epoch = 0; - setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); - EXPECT_NE((int)qpi(*pv).setProposal(qpi.computor(7), proposal), (int)QPI::INVALID_PROPOSAL_INDEX); // success - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(7)), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(countActiveProposals(qpi, pv), 1); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 3); - - // simulate epoch change with changes in computors - ++system.epoch; - initComputors(100); - EXPECT_EQ(countActiveProposals(qpi, pv), 0); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); - - // set new proposals, adding new computor IDs (simulated next epoch) - proposal.epoch = qpi.epoch(); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 6); - for (int i = 0; i < pv->maxProposals; ++i) - setProposalWithSuccessCheck(qpi, pv, qpi.computor(i), proposal); - for (int i = 0; i < pv->maxProposals; ++i) - expectNoVotes(qpi, pv, i); - EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); - EXPECT_EQ(countFinishedProposals(qpi, pv), 0); - - delete pv; -} - -TEST(TestCoreQPI, ProposalVotingV1proposalOnlyByComputorWithScalarVoteSupport) -{ - testProposalVotingComputorsV1(); -} - -TEST(TestCoreQPI, ProposalVotingV1proposalOnlyByComputorWithoutScalarVoteSupport) -{ - testProposalVotingComputorsV1(); -} - -TEST(TestCoreQPI, ProposalVotingV1proposalByAnyoneWithScalarVoteSupport) -{ - testProposalVotingComputorsV1(); -} - -TEST(TestCoreQPI, ProposalVotingV1proposalByAnyoneWithoutScalarVoteSupport) -{ - testProposalVotingComputorsV1(); -} - -// TODO: ProposalVoting YesNo - -template -void testProposalVotingShareholdersV1() -{ - ContractTesting test; - test.initEmptyUniverse(); - - system.tick = 123456789; - system.epoch = 12345; - initComputors(0); - - typedef QPI::ProposalAndVotingByShareholders<6, MSVAULT_ASSET_NAME> ProposerAndVoterHandling; - - QpiContextUserProcedureCall qpi(0, QPI::id(1, 2, 3, 4), 123); - auto* pv = new QPI::ProposalVoting< - ProposerAndVoterHandling, - QPI::ProposalDataV1>; - - // Memory must be zeroed to work, which is done in contract states on init - QPI::setMemory(*pv, 0); - - std::vector> shareholderShares{ - {id(100, 20, 3, 4), 200}, - {id(0, 0, 0, 3), 100}, - {id(0, 0, 0, 2), 250}, - {id(0, 0, 0, 1), 50}, - {id(10, 20, 3, 4), 10}, - {id(10, 20, 2, 1), 54}, - {id(10, 20, 3, 1), 12}, - }; - issueContractShares(MSVAULT_CONTRACT_INDEX, shareholderShares); - sortContractShareVector(shareholderShares); - - // fail: get before proposals have been set - QPI::ProposalDataV1 proposalReturned; - QPI::ProposalMultiVoteDataV1 voteDataReturned; - QPI::ProposalSummarizedVotingDataV1 votingSummaryReturned; - for (int i = 0; i < pv->maxProposals; ++i) - { - proposalReturned.type = 42; // test that additional error indicator is set 0 - EXPECT_FALSE(qpi(*pv).getProposal(i, proposalReturned)); - EXPECT_EQ((int)proposalReturned.type, 0); - - voteDataReturned.proposalType = 42; // test that additional error indicator is set 0 - EXPECT_FALSE(qpi(*pv).getVotes(i, shareholderShares[0].first, voteDataReturned)); - EXPECT_EQ((int)voteDataReturned.proposalType, 0); - - votingSummaryReturned.totalVotesAuthorized = 42; // test that additional error indicator is set 0 - EXPECT_FALSE(qpi(*pv).getVotingSummary(i, votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, 0); - } - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), -1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(123456), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(0), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(123456), -1); - - // fail: get with invalid proposal index - EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals, proposalReturned)); - EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals + 1, proposalReturned)); - EXPECT_FALSE(qpi(*pv).getVotes(pv->maxProposals, shareholderShares[0].first, voteDataReturned)); - EXPECT_FALSE(qpi(*pv).getVotes(pv->maxProposals + 1, shareholderShares[0].first, voteDataReturned)); - EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals, votingSummaryReturned)); - EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals + 1, votingSummaryReturned)); - - // fail: no proposals for given IDs and invalid input - EXPECT_EQ((int)qpi(*pv).proposalIndex(QPI::NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); // always equal - EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[0].first), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(qpi(*pv).proposerId(QPI::INVALID_PROPOSAL_INDEX), QPI::NULL_ID); // always equal - EXPECT_EQ(qpi(*pv).proposerId(pv->maxProposals), QPI::NULL_ID); // always equal - EXPECT_EQ(qpi(*pv).proposerId(0), QPI::NULL_ID); - EXPECT_EQ(qpi(*pv).proposerId(1), QPI::NULL_ID); - - // fail: IDs / indices of non-voters - EXPECT_EQ(qpi(*pv).voteIndex(qpi.originator()), QPI::INVALID_VOTE_INDEX); - EXPECT_EQ(qpi(*pv).voteIndex(QPI::NULL_ID), QPI::INVALID_VOTE_INDEX); - EXPECT_EQ(qpi(*pv).voteCount(QPI::INVALID_VOTE_INDEX), 0); - EXPECT_EQ(qpi(*pv).voteCount(1000), 0); - EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes), QPI::NULL_ID); - EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes + 1), QPI::NULL_ID); - - // okay: set proposal for shareholder 0 - QPI::ProposalDataV1 proposal; - proposal.url.set(0, 0); - proposal.epoch = qpi.epoch(); - proposal.type = QPI::ProposalTypes::YesNo; - setProposalWithSuccessCheck(qpi, pv, shareholderShares[0].first, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[0].first), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // check that voters match shareholders - for (size_t i = 0; i < shareholderShares.size(); ++i) - { - uint32 voterIdx = qpi(*pv).voteIndex(shareholderShares[i].first); - EXPECT_NE(voterIdx, NO_VOTE_VALUE); - EXPECT_EQ(qpi(*pv).voteCount(voterIdx), shareholderShares[i].second); - EXPECT_EQ(qpi(*pv).voterId(voterIdx), shareholderShares[i].first); - } - - // fail: vote although no proposal is available at proposal index - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - - // fail: vote with wrong type - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); - - // fail: vote with non-computor - voteWithInvalidVoter(qpi, *pv, qpi.originator(), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); - - // fail: vote with invalid value - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); - - // fail: vote with wrong tick - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() - 1, 0); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() - 1, 0); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() + 1, 0); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() + 1, 0); - - // okay: correct votes in proposalIndex 0 - expectNoVotes(qpi, pv, 0); - int optionProposalVoteCounts[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - for (size_t i = 0; i < shareholderShares.size(); ++i) - { - if (i % 3 != 0) - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); - else - voteWithValidVoter(qpi, *pv, shareholderShares[i].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); - optionProposalVoteCounts[i % 2] += shareholderShares[i].second; - } - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - EXPECT_TRUE(qpi(*pv).getVotingSummary(0, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 0); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes - shareholderShares[0].second); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), optionProposalVoteCounts[0] - shareholderShares[0].second); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), optionProposalVoteCounts[1]); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 1); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 1); - - // fail: originator id(1,2,3,4) is no shareholder (see custom qpi.computor() above) - setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); - - // fail: invalid type (more options than supported) - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 9); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // fail: invalid type (less options than supported) - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 0); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // okay: set proposal for computor 2 (proposal index 1, first use) - QPI::id secondProposer = shareholderShares[2].first; - proposal.type = QPI::ProposalTypes::FourOptions; - proposal.epoch = 1; // non-zero means current epoch - setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 1, proposal.type, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 1, proposal.type, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[1].first, 1, proposal.type, qpi.tick(), 4); - voteWithValidVoter(qpi, *pv, shareholderShares[1].first, 1, proposal.type, qpi.tick(), 4); - - // fail: vote with non-shareholder - voteWithInvalidVoter(qpi, *pv, qpi.originator(), 1, proposal.type, qpi.tick(), 0); - voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 1, proposal.type, qpi.tick(), 0); - - // okay: cast votes in proposalIndex 1 (first use) - expectNoVotes(qpi, pv, 1); - for (int i = 0; i < 8; ++i) - optionProposalVoteCounts[i] = 0; - for (size_t i = 0; i < shareholderShares.size(); ++i) - { - int voteValue = i % 4; - optionProposalVoteCounts[voteValue] += shareholderShares[i].second; - if (i & 1) - voteWithValidVoter(qpi, *pv, shareholderShares[i].first, 1, proposal.type, qpi.tick(), voteValue); - else - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, 1, proposal.type, qpi.tick(), voteValue); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); - for (int i = 0; i < 4; ++i) - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), optionProposalVoteCounts[i]); - for (int i = 4; i < votingSummaryReturned.optionVoteCount.capacity(); ++i) - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), 0); - - // fail: proposal of transfer with wrong address - proposal.type = QPI::ProposalTypes::TransferYesNo; - proposal.data.transfer.destination = QPI::NULL_ID; - proposal.data.transfer.amounts.setAll(0); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - // check that overwrite did not work - EXPECT_TRUE(qpi(*pv).getProposal(qpi(*pv).proposalIndex(secondProposer), proposalReturned)); - EXPECT_FALSE(isReturnedProposalAsExpected(qpi, proposalReturned, proposal)); - - // fail: proposal of transfer with too many or too few options - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 0); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 1); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 6); - EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - - // fail: proposal of revenue distribution with invalid amount - proposal.type = QPI::ProposalTypes::TransferYesNo; - proposal.data.transfer.destination = qpi.originator(); - proposal.data.transfer.amounts.set(0, -123456); - setProposalExpectFailure(qpi, pv, secondProposer, proposal); - - // okay: revenue distribution, overwrite existing proposal of comp 2 (proposal index 1, reused) - proposal.data.transfer.destination = qpi.originator(); - proposal.data.transfer.amounts.set(0, 1005); - setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) - QPI::uint16 secondProposalIdx = qpi(*pv).proposalIndex(secondProposer); - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, secondProposalIdx, proposal.type, qpi.tick(), -1); - voteWithValidVoter(qpi, *pv, shareholderShares[1].first, secondProposalIdx, proposal.type, qpi.tick(), 2); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, secondProposalIdx, proposal.type, qpi.tick(), -1); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[1].first, secondProposalIdx, proposal.type, qpi.tick(), 2); - - // okay: cast votes in proposalIndex 1 (reused) - expectNoVotes(qpi, pv, 1); // checks that setProposal clears previous votes - optionProposalVoteCounts[0] = 0; - optionProposalVoteCounts[1] = 0; - for (size_t i = 0; i < shareholderShares.size(); ++i) - { - int voteValue = i % 2; - optionProposalVoteCounts[voteValue] += shareholderShares[i].second; - if (i & 1) - voteWithValidVoter(qpi, *pv, shareholderShares[i].first, secondProposalIdx, proposal.type, qpi.tick(), voteValue); - else - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, secondProposalIdx, proposal.type, qpi.tick(), voteValue); - } - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[3].first, secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - optionProposalVoteCounts[1] -= shareholderShares[3].second; - voteWithValidVoter(qpi, *pv, shareholderShares[5].first, secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - optionProposalVoteCounts[1] -= shareholderShares[5].second; - EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, optionProposalVoteCounts[0] + optionProposalVoteCounts[1]); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), optionProposalVoteCounts[0]); - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), optionProposalVoteCounts[1]); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 0); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 0); - - if (!supportScalarVotes) - { - // fail: scalar proposal not supported - proposal.type = QPI::ProposalTypes::VariableScalarMean; - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - } - else - { - // fail: scalar proposal with wrong min/max - proposal.type = QPI::ProposalTypes::VariableScalarMean; - proposal.data.variableScalar.proposedValue = 10; - proposal.data.variableScalar.minValue = 11; - proposal.data.variableScalar.maxValue = 20; - proposal.data.variableScalar.variable = 123; // not checked, full range usable - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - proposal.data.variableScalar.minValue = 0; - proposal.data.variableScalar.maxValue = 9; - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // fail: scalar proposal with full range is invalid, because NO_VOTE_VALUE is reserved for no vote - proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue - 1; - proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // okay: scalar proposal with nearly full range - proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue; - proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; - setProposalWithSuccessCheck(qpi, pv, shareholderShares[1].first, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[1].first), 2); - EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), (int)secondProposalIdx); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); - EXPECT_EQ(qpi(*pv).nextProposalIndex(2), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // okay: votes in proposalIndex for testing overflow-avoiding summary algorithm for average - expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(shareholderShares[1].first)); - for (int i = 0; i < 3; ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[1].first), - proposal.type, qpi.tick(), - proposal.data.variableScalar.maxSupportedValue - 2 + i % 3, 5, - proposal.data.variableScalar.maxSupportedValue - 2 + (i + 1) % 3, 3, - proposal.data.variableScalar.maxSupportedValue - 2 + (i + 2) % 3, 2); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(shareholderShares[1].first), votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, (int)qpi(*pv).proposalIndex(shareholderShares[1].first)); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 30); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.maxSupportedValue - 1); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), -1); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); - - for (int i = 0; i < 5; ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[1].first), - proposal.type, qpi.tick(), - proposal.data.variableScalar.minSupportedValue + 4 - i % 5, 4, - proposal.data.variableScalar.minSupportedValue + 12 - i % 5, 2); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(shareholderShares[1].first), votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 30); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.minSupportedValue + 2 + 3); - - // okay: scalar proposal with limited range - proposal.data.variableScalar.minValue = -1000; - proposal.data.variableScalar.maxValue = 1000; - setProposalWithSuccessCheck(qpi, pv, shareholderShares[5].first, proposal); - EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[5].first), 3); - EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); - EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); - EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); - EXPECT_EQ(qpi(*pv).nextProposalIndex(2), 3); - EXPECT_EQ(qpi(*pv).nextProposalIndex(3), -1); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: vote with invalid values - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), -1001); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), -1001); - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[1].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), 1001); - voteWithValidVoter(qpi, *pv, shareholderShares[1].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), 1001); - - // okay: cast votes in proposalIndex of shareholder 5 - expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(shareholderShares[5].first)); - for (int i = 0; i < (int)shareholderShares.size(); ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), - proposal.type, qpi.tick(), - (i + 1) * 5, 6, - (i + 1) * -10, 3, - 0, shareholderShares[i].second - 9); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 676); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, 0); - - // another case for scalar voting summary - for (size_t i = 0; i < shareholderShares.size(); ++i) - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote - expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(shareholderShares[5].first)); - for (size_t i = 0; i < shareholderShares.size(); ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), - proposal.type, qpi.tick(), - i * 3, 2, - i * 3 + 1, 3, - i * 3 + 2, 2); - } - EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); - EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, shareholderShares.size() * 7); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); - EXPECT_EQ(votingSummaryReturned.scalarVotingResult, (shareholderShares.size() / 2) * 3 + 1); - } - - // fail: test multi-option transfer proposal with invalid amounts - proposal.type = QPI::ProposalTypes::TransferThreeAmounts; - proposal.data.transfer.destination = qpi.originator(); - for (int i = 0; i < 4; ++i) - { - proposal.data.transfer.amounts.setAll(0); - proposal.data.transfer.amounts.set(i, -100 * i - 1); - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - } - proposal.data.transfer.amounts.set(0, 0); - proposal.data.transfer.amounts.set(1, 10); - proposal.data.transfer.amounts.set(2, 20); - proposal.data.transfer.amounts.set(3, 100); // for ProposalTypes::TransferThreeAmounts, fourth must be 0 - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // fail: duplicate options - proposal.data.transfer.amounts.setAll(0); - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // fail: options not sorted - for (int i = 0; i < 3; ++i) - proposal.data.transfer.amounts.set(i, 100 - i); - setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); - - // okay: fill proposal storage - proposal.data.transfer.amounts.setAll(0); - ASSERT_EQ((int)pv->maxProposals, (int)shareholderShares.size() - 1); - for (int i = 0; i < pv->maxProposals; ++i) - { - proposal.data.transfer.amounts.set(0, i); - proposal.data.transfer.amounts.set(1, i * 2 + 1); - proposal.data.transfer.amounts.set(2, i * 3 + 2); - setProposalWithSuccessCheck(qpi, pv, shareholderShares[i].first, proposal); - } - EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); - EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); - - // fail: no space left - setProposalExpectFailure(qpi, pv, shareholderShares[pv->maxProposals].first, proposal); - - // cast some votes before epoch change to test querying voting summary afterwards - for (int i = 0; i < 8; ++i) - optionProposalVoteCounts[i] = 0; - for (size_t i = 0; i < shareholderShares.size(); ++i) - { - voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), - i & 1, shareholderShares[i].second / 2, - 2, shareholderShares[i].second / 8, - 3, shareholderShares[i].second / 4); - optionProposalVoteCounts[i & 1] += shareholderShares[i].second / 2; - optionProposalVoteCounts[2] += shareholderShares[i].second / 8; - optionProposalVoteCounts[3] += shareholderShares[i].second / 4; - } - - // simulate epoch change - ++system.epoch; - EXPECT_EQ(countActiveProposals(qpi, pv), 0); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals); - - // okay: same setProposal after epoch change, because the oldest proposal will be deleted - proposal.epoch = qpi.epoch(); - setProposalWithSuccessCheck(qpi, pv, shareholderShares[pv->maxProposals].first, proposal); - EXPECT_EQ(countActiveProposals(qpi, pv), 1); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 1); - - // fail: vote in wrong epoch - voteWithValidVoter(qpi, *pv, shareholderShares[0].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), 0); - - // okay: query voting summary of other epoch - EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(shareholderShares[5].first), votingSummaryReturned)); - EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); - uint32 voteCountSum = 0; - for (int i = 0; i < 4; ++i) - voteCountSum += optionProposalVoteCounts[i]; - EXPECT_EQ(votingSummaryReturned.totalVotesCasted, voteCountSum); - EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); - for (int i = 0; i < 8; ++i) - EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), (i < 4) ? optionProposalVoteCounts[i] : 0); - EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 0); - EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); - - // manually clear some proposals - EXPECT_FALSE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.originator()))); - EXPECT_TRUE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(shareholderShares[5].first))); - EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[5].first), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(countActiveProposals(qpi, pv), 1); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); - proposal.epoch = 0; - setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); - EXPECT_NE((int)qpi(*pv).setProposal(shareholderShares[3].first, proposal), (int)QPI::INVALID_PROPOSAL_INDEX); // success (clear by epoch 0) - EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[3].first), (int)QPI::INVALID_PROPOSAL_INDEX); - EXPECT_EQ(countActiveProposals(qpi, pv), 1); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 3); - - // simulate epoch change - ++system.epoch; - EXPECT_EQ(countActiveProposals(qpi, pv), 0); - EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); - - // change of shareholders - AssetPossessionIterator iter({ NULL_ID, MSVAULT_ASSET_NAME }); - int newPossessorCount = 0; - while (!iter.reachedEnd()) - { - if (iter.numberOfPossessedShares()) - { - int destinationOwnershipIndex, destinationPossessionIndex; - EXPECT_TRUE(transferShareOwnershipAndPossession(iter.ownershipIndex(), iter.possessionIndex(), qpi.computor(newPossessorCount), - iter.numberOfPossessedShares(), &destinationOwnershipIndex, &destinationPossessionIndex, true)); - ++newPossessorCount; - } - iter.next(); - } - EXPECT_GE(newPossessorCount, (int)pv->maxProposals); - - // set new proposals by new shareholders - proposal.epoch = qpi.epoch(); - proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 6); - for (int i = 0; i < pv->maxProposals; ++i) - setProposalWithSuccessCheck(qpi, pv, qpi.computor(i), proposal); - for (int i = 0; i < pv->maxProposals; ++i) - expectNoVotes(qpi, pv, i); - EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); - EXPECT_EQ(countFinishedProposals(qpi, pv), 0); - - delete pv; -} - -TEST(TestCoreQPI, ProposalVotingV1shareholderWithScalarVoteSupport) -{ - testProposalVotingShareholdersV1(); -} - -TEST(TestCoreQPI, ProposalVotingV1shareholderWithoutScalarVoteSupport) -{ - testProposalVotingShareholdersV1(); -} diff --git a/test/qpi_collection.cpp b/test/qpi_collection.cpp deleted file mode 100644 index 5988bfd6c..000000000 --- a/test/qpi_collection.cpp +++ /dev/null @@ -1,1716 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#include "../src/contract_core/pre_qpi_def.h" -#include "../src/contracts/qpi.h" -#include "../src/common_buffers.h" -#include "../src/contract_core/qpi_collection_impl.h" -#include "../src/contract_core/qpi_trivial_impl.h" - - -#include -#include -#include -#include - -template -void checkPriorityQueue(const QPI::Collection& coll, const QPI::id& pov, bool print = false) -{ - if (print) - { - std::cout << "Priority queue ID(" << pov.u64._0 << ", " << pov.u64._1 << ", " - << pov.u64._2 << ", " << pov.u64._3 << ")" << std::endl; - } - bool first = true; - QPI::sint64 elementIndex = coll.headIndex(pov); - QPI::sint64 prevPriority; - QPI::sint64 prevElementIdx = QPI::NULL_INDEX; - int elementCount = 0; - while (elementIndex != QPI::NULL_INDEX) - { - if (print) - { - std::cout << "\tindex " << elementIndex << ", value " << coll.element(elementIndex) - << ", priority " << coll.priority(elementIndex) - << ", prev " << coll.prevElementIndex(elementIndex) - << ", next " << coll.nextElementIndex(elementIndex) << std::endl; - } - - if (!first) - { - EXPECT_LE(coll.priority(elementIndex), prevPriority); - } - EXPECT_EQ(coll.prevElementIndex(elementIndex), prevElementIdx); - EXPECT_EQ(coll.pov(elementIndex), pov); - - prevElementIdx = elementIndex; - prevPriority = coll.priority(elementIndex); - - first = false; - elementIndex = coll.nextElementIndex(elementIndex); - ++elementCount; - } - EXPECT_EQ(elementCount, coll.population(pov)); - EXPECT_EQ(prevElementIdx, coll.tailIndex(pov)); -} - -void printPovElementCounts(const std::map& povElementCounts) -{ - std::cout << "PoV element counts:\n"; - for (const auto& id_count_pair : povElementCounts) - { - QPI::id id = id_count_pair.first; - unsigned long long count = id_count_pair.second; - std::cout << "\t(" << id.u64._0 << ", " << id.u64._1 << ", " << id.u64._2 << ", " << id.u64._3 << "): " << count << std::endl; - } -} - -// return sorted set of PoVs -template -std::map getPovElementCounts(const QPI::Collection& coll) -{ - // use that in current implementation elements are always in range 0 to N-1 - std::map povs; - for (unsigned long long i = 0; i < coll.population(); ++i) - { - QPI::id id = coll.pov(i); - EXPECT_NE(coll.headIndex(id), QPI::NULL_INDEX); - EXPECT_NE(coll.tailIndex(id), QPI::NULL_INDEX); - ++povs[id]; - } - - for (const auto& id_count_pair : povs) - { - EXPECT_EQ(coll.population(id_count_pair.first), id_count_pair.second); - } - - return povs; -} - -template -void checkCollectionValidState(const QPI::Collection& collection, QPI::sint64 expectedNumOfPoV = -1, bool verbose = false) -{ - auto povCounts = getPovElementCounts(collection); - if (expectedNumOfPoV != -1) - { - EXPECT_EQ(expectedNumOfPoV, povCounts.size()); - } - for (const auto& idCountPair : povCounts) - { - QPI::id pov = idCountPair.first; - checkPriorityQueue(collection, pov, verbose); - } -} - -template -struct CollectionReferenceImpl : std::map>> -{ - void add(const QPI::id& pov, const ValueT& element, QPI::sint64 priority) - { - (*this)[pov].insert(std::pair{ priority, element }); - } - - void remove(const QPI::id& pov, const ValueT& element, QPI::sint64 priority) - { - auto queueIt = this->find(pov); - EXPECT_NE(queueIt, this->end()); - if (queueIt == this->end()) - return; - auto& queue = queueIt->second; - auto range = queue.equal_range(priority); - for (auto elementIt = range.first; elementIt != range.second; ++elementIt) - { - if (elementIt->second == element) - { - queue.erase(elementIt); - if (queue.size() == 0) - this->erase(pov); - return; - } - } - bool elementMissing = true; - EXPECT_FALSE(elementMissing); - } - - template - void checkEqualContent(const QPI::Collection& coll) const - { - auto povQueueSizes = getPovElementCounts(coll); - EXPECT_EQ(povQueueSizes.size(), this->size()); - for (const auto& povPairs : povQueueSizes) - { - auto queueIt = this->find(povPairs.first); - EXPECT_NE(queueIt, this->end()); - if (queueIt == this->end()) - continue; - EXPECT_EQ(queueIt->second.size(), povPairs.second); - const auto& queue = queueIt->second; - auto elementIdx = coll.headIndex(povPairs.first); - for (auto refElementIt = queue.begin(); refElementIt != queue.end(); ++refElementIt) - { - EXPECT_NE(elementIdx, QPI::NULL_INDEX); - EXPECT_EQ(refElementIt->first, coll.priority(elementIdx)); - EXPECT_EQ(refElementIt->second, coll.element(elementIdx)); - elementIdx = coll.nextElementIndex(elementIdx); - } - EXPECT_EQ(elementIdx, QPI::NULL_INDEX); - } - } -}; - -template -bool isCompletelySame(const QPI::Collection& coll1, const QPI::Collection& coll2) -{ - return memcmp(&coll1, &coll2, sizeof(coll1)) == 0; -} - -template -bool haveSameContent(const QPI::Collection& coll1, const QPI::Collection& coll2, bool verbose = true) -{ - // check that both contain the same PoVs, each with the same number of elements - auto coll1PovCounts = getPovElementCounts(coll1); - auto coll2PovCounts = getPovElementCounts(coll2); - if (coll1PovCounts != coll2PovCounts) - { - if (verbose) - { - std::cout << "Differences in PoV sets of collections!" << std::endl; - if (coll1PovCounts.size() != coll2PovCounts.size()) - std::cout << "\tPoV count: " << coll1PovCounts.size() << " vs " << coll2PovCounts.size() << std::endl; - printPovElementCounts(coll1PovCounts); - printPovElementCounts(coll2PovCounts); - } - return false; - } - - // check that values and priorities of the elements are the same - for (const auto& id_count_pair : coll1PovCounts) - { - QPI::id pov = id_count_pair.first; - QPI::sint64 elementIndex1 = coll1.headIndex(pov); - QPI::sint64 elementIndex2 = coll2.headIndex(pov); - while (elementIndex1 != QPI::NULL_INDEX && elementIndex2 != QPI::NULL_INDEX) - { - if (coll1.priority(elementIndex1) != coll2.priority(elementIndex2)) - return false; - if (coll1.element(elementIndex1) != coll2.element(elementIndex2)) - return false; - - EXPECT_EQ(coll1.pov(elementIndex1), pov); - EXPECT_EQ(coll2.pov(elementIndex2), pov); - - elementIndex1 = coll1.nextElementIndex(elementIndex1); - elementIndex2 = coll2.nextElementIndex(elementIndex2); - } - EXPECT_EQ(elementIndex1, QPI::NULL_INDEX); - EXPECT_EQ(elementIndex2, QPI::NULL_INDEX); - EXPECT_EQ(coll1.nextElementIndex(coll1.tailIndex(pov)), QPI::NULL_INDEX); - EXPECT_EQ(coll2.nextElementIndex(coll2.tailIndex(pov)), QPI::NULL_INDEX); - } - - return true; -} - -template -void cleanupCollectionReferenceImplementation(const QPI::Collection& coll, QPI::Collection& newColl) -{ - newColl.reset(); - - // for each pov, add all elements of priority queue in order - auto povs = getPovElementCounts(coll); - for (const auto& id_count_pair : povs) - { - QPI::id pov = id_count_pair.first; - QPI::sint64 elementIndex = coll.headIndex(pov); - while (elementIndex != QPI::NULL_INDEX) - { - newColl.add(pov, coll.element(elementIndex), coll.priority(elementIndex)); - elementIndex = coll.nextElementIndex(elementIndex); - } - } -} - -template -void cleanupCollection(QPI::Collection& coll) -{ - // check that collection in itself is in valid state - checkCollectionValidState(coll); - - // save original data for checking - QPI::Collection origColl; - copyMem(&origColl, &coll, sizeof(coll)); - - // run reference cleanup and test that cleanup did not change any relevant content - cleanupCollectionReferenceImplementation(origColl, coll); - EXPECT_TRUE(haveSameContent(origColl, coll)); - - // run faster cleanup and check result - origColl.cleanup(); - EXPECT_TRUE(haveSameContent(origColl, coll)); -} - - -TEST(TestCoreQPI, CollectionMultiPovMultiElements) -{ - QPI::id id1(1, 2, 3, 4); - QPI::id id2(3, 100, 579, 5431); - QPI::id id3(1, 100, 579, 5431); - - constexpr unsigned long long capacity = 8; - - // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) - QPI::Collection coll; - coll.reset(); - - // test behavior of empty collection - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 0); - EXPECT_EQ(coll.headIndex(id1), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id1), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id1), 0); - EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id2), 0); - EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id3), 0); - // all properties of non-occupied elements are initialized to 0 (by reset function), but in practice only occupied - // elements should be accessed - EXPECT_EQ(coll.pov(0), QPI::id(0, 0, 0, 0)); - EXPECT_EQ(coll.element(0), 0); - EXPECT_EQ(coll.nextElementIndex(0), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(0), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(0), 0); - - // add an element with id1 - constexpr int firstElementValue = 42; - constexpr QPI::sint64 firstElementPriority = 1234; - QPI::sint64 firstElementIdx = coll.add(id1, firstElementValue, firstElementPriority); - EXPECT_TRUE(firstElementIdx != QPI::NULL_INDEX); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 1); - EXPECT_EQ(coll.headIndex(id1), firstElementIdx); - EXPECT_EQ(coll.tailIndex(id1), firstElementIdx); - EXPECT_EQ(coll.population(id1), 1); - EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id2), 0); - EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id3), 0); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - - // add another element with with id1, but higher priority value - // id1 priority queue order: secondElement, firstElement - constexpr int secondElementValue = 987; - constexpr QPI::sint64 secondElementPriority = 12345; - QPI::sint64 secondElementIdx = coll.add(id1, secondElementValue, secondElementPriority); - EXPECT_TRUE(secondElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(secondElementIdx != firstElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 2); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), firstElementIdx); - EXPECT_EQ(coll.population(id1), 2); - EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id2), 0); - EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id3), 0); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - - // add another element with id1, but lower priority value - // id1 priority queue order: secondElement, firstElement, thirdElement - constexpr int thirdElementValue = 98; - constexpr QPI::sint64 thirdElementPriority = 12; - QPI::sint64 thirdElementIdx = coll.add(id1, thirdElementValue, thirdElementPriority); - EXPECT_TRUE(thirdElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(thirdElementIdx != firstElementIdx); - EXPECT_TRUE(thirdElementIdx != secondElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 3); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), thirdElementIdx); - EXPECT_EQ(coll.population(id1), 3); - EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id2), 0); - EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id3), 0); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); - EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); - EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); - - // add element with id2 - // id2 priority queue order: fourthElement - constexpr int fourthElementValue = 4; - constexpr QPI::sint64 fourthElementPriority = -10; - QPI::sint64 fourthElementIdx = coll.add(id2, fourthElementValue, fourthElementPriority); - EXPECT_TRUE(fourthElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(fourthElementIdx != firstElementIdx); - EXPECT_TRUE(fourthElementIdx != secondElementIdx); - EXPECT_TRUE(fourthElementIdx != thirdElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 4); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), thirdElementIdx); - EXPECT_EQ(coll.population(id1), 3); - EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.population(id2), 1); - EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(id3), 0); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - EXPECT_EQ(coll.pov(thirdElementIdx), id1); - EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); - EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); - EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); - EXPECT_EQ(coll.pov(fourthElementIdx), id2); - EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); - EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); - - // add element with id3 - // id3 priority queue order: fifthElement - constexpr int fifthElementValue = 50; - constexpr QPI::sint64 fifthElementPriority = -10; - QPI::sint64 fifthElementIdx = coll.add(id3, fifthElementValue, fifthElementPriority); - EXPECT_TRUE(fifthElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(fifthElementIdx != firstElementIdx); - EXPECT_TRUE(fifthElementIdx != secondElementIdx); - EXPECT_TRUE(fifthElementIdx != thirdElementIdx); - EXPECT_TRUE(fifthElementIdx != fourthElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 5); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), thirdElementIdx); - EXPECT_EQ(coll.population(id1), 3); - EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.population(id2), 1); - EXPECT_EQ(coll.headIndex(id3), fifthElementIdx); - EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); - EXPECT_EQ(coll.population(id3), 1); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - EXPECT_EQ(coll.pov(thirdElementIdx), id1); - EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); - EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); - EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); - EXPECT_EQ(coll.pov(fourthElementIdx), id2); - EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); - EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); - EXPECT_EQ(coll.pov(fifthElementIdx), id3); - EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); - EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); - - // add another element with id1, with lowest priority value - // id1 priority queue order: secondElement, firstElement, thirdElement, sixthElement - constexpr int sixthElementValue = 600; - constexpr QPI::sint64 sixthElementPriority = -60; - QPI::sint64 sixthElementIdx = coll.add(id1, sixthElementValue, sixthElementPriority); - EXPECT_TRUE(sixthElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(sixthElementIdx != firstElementIdx); - EXPECT_TRUE(sixthElementIdx != secondElementIdx); - EXPECT_TRUE(sixthElementIdx != thirdElementIdx); - EXPECT_TRUE(sixthElementIdx != fourthElementIdx); - EXPECT_TRUE(sixthElementIdx != fifthElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 6); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), sixthElementIdx); - EXPECT_EQ(coll.population(id1), 4); - EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.population(id2), 1); - EXPECT_EQ(coll.headIndex(id3), fifthElementIdx); - EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); - EXPECT_EQ(coll.population(id3), 1); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - EXPECT_EQ(coll.pov(thirdElementIdx), id1); - EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); - EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), sixthElementIdx); - EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); - EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); - EXPECT_EQ(coll.pov(fourthElementIdx), id2); - EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); - EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); - EXPECT_EQ(coll.pov(fifthElementIdx), id3); - EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); - EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); - EXPECT_EQ(coll.pov(sixthElementIdx), id1); - EXPECT_EQ(coll.element(sixthElementIdx), sixthElementValue); - EXPECT_EQ(coll.nextElementIndex(sixthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(sixthElementIdx), thirdElementIdx); - EXPECT_EQ(coll.priority(sixthElementIdx), sixthElementPriority); - - // add another element with id3, with highest priority value - // id3 priority queue order: seventhElement, fifthElement - constexpr int seventhElementValue = 700; - constexpr QPI::sint64 seventhElementPriority = 70000; - QPI::sint64 seventhElementIdx = coll.add(id3, seventhElementValue, seventhElementPriority); - EXPECT_TRUE(seventhElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(seventhElementIdx != firstElementIdx); - EXPECT_TRUE(seventhElementIdx != secondElementIdx); - EXPECT_TRUE(seventhElementIdx != thirdElementIdx); - EXPECT_TRUE(seventhElementIdx != fourthElementIdx); - EXPECT_TRUE(seventhElementIdx != fifthElementIdx); - EXPECT_TRUE(seventhElementIdx != sixthElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 7); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), sixthElementIdx); - EXPECT_EQ(coll.population(id1), 4); - EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.population(id2), 1); - EXPECT_EQ(coll.headIndex(id3), seventhElementIdx); - EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); - EXPECT_EQ(coll.population(id3), 2); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - EXPECT_EQ(coll.pov(thirdElementIdx), id1); - EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); - EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), sixthElementIdx); - EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); - EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); - EXPECT_EQ(coll.pov(fourthElementIdx), id2); - EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); - EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); - EXPECT_EQ(coll.pov(fifthElementIdx), id3); - EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); - EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), seventhElementIdx); - EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); - EXPECT_EQ(coll.pov(sixthElementIdx), id1); - EXPECT_EQ(coll.element(sixthElementIdx), sixthElementValue); - EXPECT_EQ(coll.nextElementIndex(sixthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(sixthElementIdx), thirdElementIdx); - EXPECT_EQ(coll.priority(sixthElementIdx), sixthElementPriority); - EXPECT_EQ(coll.pov(seventhElementIdx), id3); - EXPECT_EQ(coll.element(seventhElementIdx), seventhElementValue); - EXPECT_EQ(coll.nextElementIndex(seventhElementIdx), fifthElementIdx); - EXPECT_EQ(coll.prevElementIndex(seventhElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(seventhElementIdx), seventhElementPriority); - - // add another element with id1, with medium priority value - // id1 priority queue order: secondElement, firstElement, eighthElement, thirdElement, sixthElement - // priorities: 12345, 1234, 123, 12, -60 - constexpr int eighthElementValue = 800; - constexpr QPI::sint64 eighthElementPriority = 123; - QPI::sint64 eighthElementIdx = coll.add(id1, eighthElementValue, eighthElementPriority); - EXPECT_TRUE(eighthElementIdx != QPI::NULL_INDEX); - EXPECT_TRUE(eighthElementIdx != firstElementIdx); - EXPECT_TRUE(eighthElementIdx != secondElementIdx); - EXPECT_TRUE(eighthElementIdx != thirdElementIdx); - EXPECT_TRUE(eighthElementIdx != fourthElementIdx); - EXPECT_TRUE(eighthElementIdx != fifthElementIdx); - EXPECT_TRUE(eighthElementIdx != sixthElementIdx); - EXPECT_TRUE(eighthElementIdx != seventhElementIdx); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 8); - EXPECT_EQ(coll.headIndex(id1), secondElementIdx); - EXPECT_EQ(coll.tailIndex(id1), sixthElementIdx); - EXPECT_EQ(coll.population(id1), 5); - EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); - EXPECT_EQ(coll.population(id2), 1); - EXPECT_EQ(coll.headIndex(id3), seventhElementIdx); - EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); - EXPECT_EQ(coll.population(id3), 2); - EXPECT_EQ(coll.pov(firstElementIdx), id1); - EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); - EXPECT_EQ(coll.nextElementIndex(firstElementIdx), eighthElementIdx); - EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); - EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); - EXPECT_EQ(coll.pov(secondElementIdx), id1); - EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); - EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); - EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); - EXPECT_EQ(coll.pov(thirdElementIdx), id1); - EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); - EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), sixthElementIdx); - EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), eighthElementIdx); - EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); - EXPECT_EQ(coll.pov(fourthElementIdx), id2); - EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); - EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); - EXPECT_EQ(coll.pov(fifthElementIdx), id3); - EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); - EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), seventhElementIdx); - EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); - EXPECT_EQ(coll.pov(sixthElementIdx), id1); - EXPECT_EQ(coll.element(sixthElementIdx), sixthElementValue); - EXPECT_EQ(coll.nextElementIndex(sixthElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(sixthElementIdx), thirdElementIdx); - EXPECT_EQ(coll.priority(sixthElementIdx), sixthElementPriority); - EXPECT_EQ(coll.pov(seventhElementIdx), id3); - EXPECT_EQ(coll.element(seventhElementIdx), seventhElementValue); - EXPECT_EQ(coll.nextElementIndex(seventhElementIdx), fifthElementIdx); - EXPECT_EQ(coll.prevElementIndex(seventhElementIdx), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(seventhElementIdx), seventhElementPriority); - EXPECT_EQ(coll.pov(eighthElementIdx), id1); - EXPECT_EQ(coll.element(eighthElementIdx), eighthElementValue); - EXPECT_EQ(coll.nextElementIndex(eighthElementIdx), thirdElementIdx); - EXPECT_EQ(coll.prevElementIndex(eighthElementIdx), firstElementIdx); - EXPECT_EQ(coll.priority(eighthElementIdx), eighthElementPriority); - - checkPriorityQueue(coll, id1); - checkPriorityQueue(coll, id2); - checkPriorityQueue(coll, id3); - - // test that nothing is added to full - EXPECT_EQ(capacity, 8); - QPI::sint64 ninthElementIdx = coll.add(id1, 1234, 6544); - EXPECT_TRUE(ninthElementIdx == QPI::NULL_INDEX); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 8); - - // test comparison function of full collection - QPI::Collection empty_coll; - empty_coll.reset(); - EXPECT_TRUE(isCompletelySame(coll, coll)); - EXPECT_TRUE(haveSameContent(coll, coll)); - EXPECT_FALSE(isCompletelySame(coll, empty_coll)); - EXPECT_FALSE(haveSameContent(coll, empty_coll, false)); - - // test behavior of collection after resetting non-empty collection - coll.reset(); - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), 0); - EXPECT_EQ(coll.headIndex(id1), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id1), QPI::NULL_INDEX); - EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); - EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); - // all properties of non-occupied elements are initialized to 0 (by reset function), but in practice only occupied - // elements should be accessed - EXPECT_EQ(coll.pov(0), QPI::id(0, 0, 0, 0)); - EXPECT_EQ(coll.element(0), 0); - EXPECT_EQ(coll.nextElementIndex(0), QPI::NULL_INDEX); - EXPECT_EQ(coll.prevElementIndex(0), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(0), 0); -} - -template -void testCollectionOnePovMultiElements(int prioAmpFactor, int prioFreqDiv) -{ - // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) - QPI::Collection coll; - coll.reset(); - - // scratchpad may be needed if Collection::_rebuild() is called - EXPECT_TRUE(commonBuffers.init(1, sizeof(coll))); - - // check that behavior of collection and reference implementation matches - CollectionReferenceImpl collReference; - - // these tests support changing the implementation of the element array filling to non-sequential - // by saving element indices in order - std::vector elementIndices; - - // fill completely with alternating priorities - QPI::id pov(1, 2, 3, 4); - for (int i = 0; i < capacity; ++i) - { - QPI::sint64 prio = QPI::sint64(i * prioAmpFactor * sin(i / prioFreqDiv)); - int value = i * 4; - - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), i); - EXPECT_EQ(coll.population(pov), i); - - QPI::sint64 elementIndex = coll.add(pov, value, prio); - elementIndices.push_back(elementIndex); - checkPriorityQueue(coll, pov); - - EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(elementIndex), prio); - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.population(pov), i + 1); - EXPECT_EQ(coll.population(), i + 1); - - collReference.add(pov, value, prio); - collReference.checkEqualContent(coll); - } - - // check that nothing can be added - QPI::sint64 elementIndex = coll.add(pov, 1234, 12345); - EXPECT_TRUE(elementIndex == QPI::NULL_INDEX); - EXPECT_EQ(coll.capacity(), coll.population()); - EXPECT_EQ(coll.population(pov), coll.capacity()); - - // check validity of data - checkPriorityQueue(coll, pov); - for (int i = 0; i < capacity; ++i) - { - QPI::sint64 prio = QPI::sint64(i * prioAmpFactor * sin(i / prioFreqDiv)); - int value = i * 4; - - QPI::sint64 elementIndex = elementIndices[i]; - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - } - - // remove first element - { - QPI::sint64 headIndex = coll.headIndex(pov); - QPI::sint64 afterHeadIndex = coll.nextElementIndex(headIndex); - QPI::sint64 afterHeadPrio = coll.priority(afterHeadIndex); - int afterHeadValue = coll.element(afterHeadIndex); - EXPECT_EQ(coll.population(), coll.capacity()); - EXPECT_EQ(coll.population(pov), coll.capacity()); - checkPriorityQueue(coll, pov); - QPI::sint64 followingRemovedIndex = coll.remove(headIndex); - EXPECT_EQ(coll.priority(followingRemovedIndex), afterHeadPrio); - EXPECT_EQ(coll.element(followingRemovedIndex), afterHeadValue); - EXPECT_EQ(coll.population(), coll.capacity() - 1); - EXPECT_EQ(coll.population(pov), coll.capacity() - 1); - - checkPriorityQueue(coll, pov); - - headIndex = coll.headIndex(pov); - EXPECT_EQ(coll.prevElementIndex(headIndex), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(headIndex), afterHeadPrio); - EXPECT_EQ(coll.element(headIndex), afterHeadValue); - } - - // remove last element - { - QPI::sint64 tailIndex = coll.tailIndex(pov); - QPI::sint64 beforeTailIndex = coll.prevElementIndex(tailIndex); - QPI::sint64 beforeTailPrio = coll.priority(beforeTailIndex); - int beforeTailValue = coll.element(beforeTailIndex); - EXPECT_EQ(coll.population(), coll.capacity() - 1); - EXPECT_EQ(coll.population(pov), coll.capacity() - 1); - QPI::sint64 followingRemovedIndex = coll.remove(tailIndex); - EXPECT_EQ(followingRemovedIndex, QPI::NULL_INDEX); - EXPECT_EQ(coll.population(), coll.capacity() - 2); - EXPECT_EQ(coll.population(pov), coll.capacity() - 2); - - checkPriorityQueue(coll, pov); - - tailIndex = coll.tailIndex(pov); - EXPECT_EQ(coll.nextElementIndex(tailIndex), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(tailIndex), beforeTailPrio); - EXPECT_EQ(coll.element(tailIndex), beforeTailValue); - } - - // remove element in front of tail - { - QPI::sint64 tailIndex = coll.tailIndex(pov); - QPI::sint64 beforeTailIndex = coll.prevElementIndex(tailIndex); - QPI::sint64 twoBeforeTailIndex = coll.prevElementIndex(beforeTailIndex); - QPI::sint64 tailPrio = coll.priority(tailIndex); - QPI::sint64 twoBeforeTailPrio = coll.priority(twoBeforeTailIndex); - int tailValue = coll.element(tailIndex); - int twoBeforeTailValue = coll.element(twoBeforeTailIndex); - EXPECT_EQ(coll.population(), coll.capacity() - 2); - EXPECT_EQ(coll.population(pov), coll.capacity() - 2); - QPI::sint64 followingRemovedIndex = coll.remove(beforeTailIndex); - EXPECT_EQ(coll.priority(followingRemovedIndex), tailPrio); - EXPECT_EQ(coll.element(followingRemovedIndex), tailValue); - EXPECT_EQ(coll.population(), coll.capacity() - 3); - EXPECT_EQ(coll.population(pov), coll.capacity() - 3); - - checkPriorityQueue(coll, pov); - - tailIndex = coll.tailIndex(pov); - beforeTailIndex = coll.prevElementIndex(tailIndex); - EXPECT_EQ(coll.nextElementIndex(tailIndex), QPI::NULL_INDEX); - EXPECT_EQ(coll.priority(tailIndex), tailPrio); - EXPECT_EQ(coll.priority(beforeTailIndex), twoBeforeTailPrio); - EXPECT_EQ(coll.element(tailIndex), tailValue); - EXPECT_EQ(coll.element(beforeTailIndex), twoBeforeTailValue); - } - - // add new highest and lowest priority element and remove others to trigger uncovered case of moving - // last element to other index - { - int newValue1 = 4278956; - QPI::sint64 newPrio1 = 10000000000ll; - QPI::sint64 newIdx1 = coll.add(pov, newValue1, newPrio1); - EXPECT_EQ(newIdx1, coll.population() - 1); - int newValue2 = 2568956; - QPI::sint64 newPrio2 = -10000000000ll; - QPI::sint64 newIdx2 = coll.add(pov, newValue2, newPrio2); - EXPECT_EQ(newIdx2, coll.population() - 1); - - EXPECT_EQ(coll.population(), coll.capacity() - 1); - EXPECT_EQ(coll.population(pov), coll.capacity() - 1); - - checkPriorityQueue(coll, pov); - - // remove one - int followingRemovedValue = coll.element(coll.nextElementIndex(0)); - QPI::sint64 followingRemovedPrio = coll.priority(coll.nextElementIndex(0)); - QPI::sint64 followingRemovedIndex = coll.remove(0); - EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); - EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); - checkPriorityQueue(coll, pov); - - // remove another (not head or tail!) - QPI::sint64 removeIdx = followingRemovedIndex; - followingRemovedValue = coll.element(coll.nextElementIndex(removeIdx)); - followingRemovedPrio = coll.priority(coll.nextElementIndex(removeIdx)); - followingRemovedIndex = coll.remove(removeIdx); - EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); - EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); - checkPriorityQueue(coll, pov); - - EXPECT_EQ(coll.population(), coll.capacity() - 3); - EXPECT_EQ(coll.population(pov), coll.capacity() - 3); - - // check tail and head - newIdx1 = coll.tailIndex(pov); - newIdx2 = coll.headIndex(pov); - EXPECT_EQ(coll.priority(newIdx1), newPrio2); - EXPECT_EQ(coll.priority(newIdx2), newPrio1); - EXPECT_EQ(coll.element(newIdx1), newValue2); - EXPECT_EQ(coll.element(newIdx2), newValue1); - } - - // remove remaining elements except last - while (coll.population() > 1) - { - int followingRemovedValue = coll.element(coll.nextElementIndex(0)); - QPI::sint64 followingRemovedPrio = coll.priority(coll.nextElementIndex(0)); - QPI::sint64 followingRemovedIndex = coll.remove(0); - EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); - EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); - checkPriorityQueue(coll, pov); - EXPECT_EQ(coll.population(), coll.population(pov)); - } - - // remove last element - { - EXPECT_EQ(coll.headIndex(pov), 0); - EXPECT_EQ(coll.tailIndex(pov), 0); - EXPECT_EQ(coll.remove(0), QPI::NULL_INDEX); - checkPriorityQueue(coll, pov); - EXPECT_EQ(coll.headIndex(pov), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(pov), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(), 0); - EXPECT_EQ(coll.population(pov), 0); - } - - // test that removing element from empty collection has no effect - { - EXPECT_EQ(coll.remove(0), QPI::NULL_INDEX); - checkPriorityQueue(coll, pov); - EXPECT_EQ(coll.headIndex(pov), QPI::NULL_INDEX); - EXPECT_EQ(coll.tailIndex(pov), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(), 0); - EXPECT_EQ(coll.population(pov), 0); - } - - // check that cleanup after removing all elements leads to same as reset() in terms of memory - QPI::Collection resetColl; - resetColl.reset(); - EXPECT_FALSE(isCompletelySame(resetColl, coll)); - coll.cleanup(); - EXPECT_TRUE(isCompletelySame(resetColl, coll)); - - // cleanup - commonBuffers.deinit(); -} - -TEST(TestCoreQPI, CollectionOnePovMultiElements) -{ - testCollectionOnePovMultiElements<16>(10, 3); - testCollectionOnePovMultiElements<128>(10, 3); - testCollectionOnePovMultiElements<128>(10, 10); - testCollectionOnePovMultiElements<128>(1, 10); - testCollectionOnePovMultiElements<128>(1, 1); -} - -TEST(TestCoreQPI, CollectionOnePovMultiElementsSamePrioOrder) -{ - constexpr unsigned long long capacity = 16; - - // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) - QPI::Collection coll; - coll.reset(); - - // these tests support changing the implementation of the element array filling to non-sequential - // by saving element indices in order - std::vector elementIndices; - - // check that behavior of collection and reference implementation matches - CollectionReferenceImpl collReference; - - // fill completely with same priority - QPI::id pov(1, 2, 3, 4); - constexpr QPI::sint64 prio = 100; - for (int i = 0; i < capacity; ++i) - { - int value = i * 3; - - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(), i); - EXPECT_EQ(coll.population(pov), i); - - QPI::sint64 elementIndex = coll.add(pov, value, prio); - elementIndices.push_back(elementIndex); - checkPriorityQueue(coll, pov); - collReference.add(pov, value, prio); - - EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - EXPECT_EQ(coll.population(pov), i + 1); - EXPECT_EQ(coll.population(), i + 1); - } - - collReference.checkEqualContent(coll); - - // check that priority queue order of same priorty items matches the order of insertion - QPI::sint64 elementIndex = coll.headIndex(pov); - for (int i = 0; i < capacity; ++i) - { - int value = i * 3; - EXPECT_NE(elementIndex, QPI::NULL_INDEX); - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - elementIndex = coll.nextElementIndex(elementIndex); - } - EXPECT_EQ(elementIndex, QPI::NULL_INDEX); -} - -template -void testCollectionMultiPovOneElement(bool cleanupAfterEachRemove) -{ - // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) - QPI::Collection coll; - coll.reset(); - - for (int i = 0; i < capacity; ++i) - { - // select pov to ensure hash collisions - QPI::id pov(i / 2, i % 2, i * 2, i * 3); - int value = i * 4; - QPI::sint64 prio = i * 5; - - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(pov), 0); - EXPECT_EQ(coll.population(), i); - - QPI::sint64 elementIndex = coll.add(pov, value, prio); - - EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); - EXPECT_EQ(coll.population(pov), 1); - EXPECT_EQ(coll.population(), i + 1); - - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - EXPECT_EQ(coll.pov(elementIndex), pov); - checkPriorityQueue(coll, pov, false); - } - - // check that nothing can be added - QPI::sint64 elementIndex = coll.add(QPI::id(1, 2, 3, 4), 12345, 123456); - EXPECT_TRUE(elementIndex == QPI::NULL_INDEX); - EXPECT_EQ(coll.capacity(), coll.population()); - - // check and remove - for (int j = 0; j < capacity; ++j) - { - // check integrity of povs not removed yet - for (int i = j; i < capacity; ++i) - { - QPI::id pov(i / 2, i % 2, i * 2, i * 3); - int value = i * 4; - QPI::sint64 prio = i * 5; - - QPI::sint64 elementIndex = coll.headIndex(pov); - EXPECT_NE(elementIndex, -1); - EXPECT_EQ(elementIndex, coll.tailIndex(pov)); - - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - EXPECT_EQ(coll.pov(elementIndex), pov); - checkPriorityQueue(coll, pov, false); - } - - // remove - QPI::id removePov(j / 2, j % 2, j * 2, j * 3); - EXPECT_EQ(coll.population(removePov), 1); - EXPECT_EQ(coll.remove(coll.headIndex(removePov)), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(removePov), 0); - EXPECT_EQ(coll.population(), capacity - j - 1); - - if (cleanupAfterEachRemove) - coll.cleanup(); - } - - // check that cleanup after removing all elements leads to same as reset() in terms of memory - QPI::Collection resetColl; - resetColl.reset(); - if (!cleanupAfterEachRemove) - EXPECT_FALSE(isCompletelySame(resetColl, coll)); - EXPECT_TRUE(haveSameContent(resetColl, coll, true)); - coll.cleanup(); - EXPECT_TRUE(isCompletelySame(resetColl, coll)); -} - -TEST(TestCoreQPI, CollectionMultiPovOneElement) -{ - bool cleanupAfterEachRemove = false; - testCollectionMultiPovOneElement<16>(cleanupAfterEachRemove); - testCollectionMultiPovOneElement<32>(cleanupAfterEachRemove); - testCollectionMultiPovOneElement<64>(cleanupAfterEachRemove); - testCollectionMultiPovOneElement<128>(cleanupAfterEachRemove); -} - -template -void testCollectionMultiPovOneElementReuseFreedSlotsBeforeCleanup() -{ - // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) - QPI::Collection coll; - coll.reset(); - - // for checking that content of collection matches with reference implementation - CollectionReferenceImpl collReference; - - // add and remove same item multiple times for testing simple case of reusing slot - for (int i = 0; i < capacity / 2; ++i) - { - for (int j = 0; j <= i; ++j) - { - QPI::id pov(j, 0, 0, 0); - QPI::sint64 elementIndex = coll.add(pov, i, 2 * j); - - EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); - EXPECT_EQ(coll.pov(elementIndex), pov); - EXPECT_EQ(coll.population(pov), 1); - EXPECT_EQ(coll.population(), 1); - checkCollectionValidState(coll, 1); - - EXPECT_EQ(coll.remove(elementIndex), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(pov), 0); - EXPECT_EQ(coll.population(), 0); - checkCollectionValidState(coll, 0); - } - } - - // fill collection up to capacity - for (int i = 0; i < capacity; ++i) - { - // select pov to ensure hash collisions - QPI::id pov(i / 3, i % 2, i * 2, i * 3); - int value = i * 4; - QPI::sint64 prio = i * 5; - - EXPECT_EQ(coll.capacity(), capacity); - EXPECT_EQ(coll.population(pov), 0); - EXPECT_EQ(coll.population(), i); - - QPI::sint64 elementIndex = coll.add(pov, value, prio); - collReference.add(pov, value, prio); - - EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); - EXPECT_EQ(coll.population(pov), 1); - EXPECT_EQ(coll.population(), i + 1); - checkCollectionValidState(coll, i + 1); - - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - EXPECT_EQ(coll.pov(elementIndex), pov); - - collReference.checkEqualContent(coll); - } - - // check that nothing can be added - QPI::sint64 elementIndex = coll.add(QPI::id(1, 2, 3, 4), 12345, 123456); - EXPECT_TRUE(elementIndex == QPI::NULL_INDEX); - EXPECT_EQ(coll.capacity(), coll.population()); - - // check and remove all one by one - for (int j = 0; j < capacity; ++j) - { - // check integrity of povs not removed yet - checkCollectionValidState(coll, capacity - j); - - // remove - QPI::id removePov(j / 3, j % 2, j * 2, j * 3); - int value = j * 4; - QPI::sint64 prio = j * 5; - EXPECT_EQ(coll.population(removePov), 1); - EXPECT_EQ(coll.remove(coll.headIndex(removePov)), QPI::NULL_INDEX); - EXPECT_EQ(coll.population(removePov), 0); - EXPECT_EQ(coll.population(), capacity - j - 1); - - collReference.remove(removePov, value, prio); - collReference.checkEqualContent(coll); - } - - // reuse pov slots without cleanup - for (int i = 0; i < capacity; ++i) - { - // select pov to ensure hash collisions - QPI::id pov(i / 2, i % 4, i * 5, i + 1); - int value = i * 6; - QPI::sint64 prio = i * 9; - - EXPECT_EQ(coll.population(pov), 0); - EXPECT_EQ(coll.population(), i); - checkCollectionValidState(coll, i); - - QPI::sint64 elementIndex = coll.add(pov, value, prio); - collReference.add(pov, value, prio); - - EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); - EXPECT_EQ(coll.population(pov), 1); - EXPECT_EQ(coll.population(), i + 1); - checkCollectionValidState(coll, i + 1); - - EXPECT_EQ(coll.element(elementIndex), value); - EXPECT_EQ(coll.priority(elementIndex), prio); - EXPECT_EQ(coll.pov(elementIndex), pov); - - collReference.checkEqualContent(coll); - } -} - -TEST(TestCoreQPI, CollectionMultiPovOneElementReuseSlotsBeforeCleanup) -{ - testCollectionMultiPovOneElementReuseFreedSlotsBeforeCleanup<4>(); - testCollectionMultiPovOneElementReuseFreedSlotsBeforeCleanup<16>(); -} - -TEST(TestCoreQPI, CollectionOneRemoveLastHeadTail) -{ - // Minimal test cases for bug fixed in - // https://github.com/qubic/core/commit/b379a36666f747b25992d025dd68949b771b1cd0#diff-2435a5cdb31de2a231e71d143e1cba9e4f9207181a6223d736293d40da41d002 - - QPI::id pov(1, 2, 3, 4); - constexpr unsigned long long capacity = 4; - - // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) - QPI::Collection coll; - coll.reset(); - - bool print = false; - coll.add(pov, 1234, 1000); - coll.add(pov, 1234, 10000); - checkPriorityQueue(coll, pov, print); - EXPECT_EQ(coll.remove(1), 0); - checkPriorityQueue(coll, pov, print); - - coll.reset(); - coll.add(pov, 1234, 10000); - coll.add(pov, 1234, 1000); - checkPriorityQueue(coll, pov, print); - EXPECT_EQ(coll.remove(1), QPI::NULL_INDEX); - checkPriorityQueue(coll, pov, print); -} - -TEST(TestCoreQPI, CollectionSubCollections) -{ - QPI::id pov(1, 2, 3, 4); - - QPI::Collection coll; - coll.reset(); - - // test empty - auto headIdx = coll.headIndex(pov, 0); - EXPECT_EQ(headIdx, QPI::NULL_INDEX); - auto tailIdx = coll.tailIndex(pov, 0); - EXPECT_EQ(tailIdx, QPI::NULL_INDEX); - - std::vector priorities = { - 44, 22, 88, 111, 55, 56, 11, 55, 55, 54, 66, 77, 99 - }; - - for (size_t i = 0; i < priorities.size(); i++) - { - coll.add(pov, i, priorities[i]); - } - checkPriorityQueue(coll, pov, false); - - // sorted priorities: 111, 99, 88, 77, 66, .... 44, 22, 11 - - // test head/tail - headIdx = coll.headIndex(pov); - EXPECT_EQ(coll.priority(headIdx), 111); - tailIdx = coll.tailIndex(pov); - EXPECT_EQ(coll.priority(tailIdx), 11); - - // test prev/next - auto idx = coll.prevElementIndex(tailIdx); - idx = coll.prevElementIndex(idx); - EXPECT_EQ(coll.priority(idx), 44); - idx = coll.nextElementIndex(headIdx); - idx = coll.nextElementIndex(idx); - EXPECT_EQ(coll.priority(idx), 88); - - // test sub-collection's head priority <= maxPriority - headIdx = coll.headIndex(pov, 112); - EXPECT_EQ(coll.priority(headIdx), 111); - - // test sub-collection's tail priority > maxPriority - headIdx = coll.headIndex(pov, 10); - EXPECT_EQ(headIdx, QPI::NULL_INDEX); - - // test sub-collection's head priority < minPriority - tailIdx = coll.tailIndex(pov, 112); - EXPECT_EQ(tailIdx, QPI::NULL_INDEX); - - // test sub-collection's tail priority >= minPriority - tailIdx = coll.tailIndex(pov, 10); - EXPECT_EQ(coll.priority(tailIdx), 11); - - // test sub-collection's head - headIdx = coll.headIndex(pov, 100); - EXPECT_EQ(coll.priority(headIdx), 99); - headIdx = coll.headIndex(pov, 99); - EXPECT_EQ(coll.priority(headIdx), 99); - - // test sub-collection's head: duplicated priorites - headIdx = coll.headIndex(pov, 55); - EXPECT_EQ(coll.priority(headIdx), 55); - idx = coll.prevElementIndex(headIdx); - EXPECT_EQ(coll.priority(idx), 56); - - // test sub-collection's tail - tailIdx = coll.tailIndex(pov, 33); - EXPECT_EQ(coll.priority(tailIdx), 44); - tailIdx = coll.tailIndex(pov, 44); - EXPECT_EQ(coll.priority(tailIdx), 44); - - // test sub-collection's tail: duplicated priorites - tailIdx = coll.tailIndex(pov, 55); - EXPECT_EQ(coll.priority(tailIdx), 55); - idx = coll.nextElementIndex(tailIdx); - EXPECT_EQ(coll.priority(idx), 54); -} - -TEST(TestCoreQPI, CollectionSubCollectionsRandom) -{ - QPI::id pov(1, 2, 3, 4); - - QPI::Collection coll; - coll.reset(); - - // scratchpad may be needed if Collection::_rebuild() is called - EXPECT_TRUE(commonBuffers.init(1, sizeof(coll))); - - const int seed = 246357; - std::mt19937_64 gen64(seed); - - const int numTests = 10; - for (int test = 1; test <= numTests; test++) - { - coll.reset(); - std::vector< QPI::sint64> priorities(777); - for (size_t i = 0; i < priorities.size(); i++) { - auto v = std::abs((QPI::sint64)gen64()) % 0xFFFF; - priorities[i] = v; - coll.add(pov, (i + 1) * test, priorities[i]); - } - checkPriorityQueue(coll, pov, false); - - std::sort(priorities.begin(), priorities.end(), std::greater<>()); - const auto size = priorities.size(); - - // test sub-collection's head priority <= maxPriority - auto headIdx = coll.headIndex(pov, priorities[0] + 1); - EXPECT_EQ(coll.priority(headIdx), priorities[0]); - headIdx = coll.headIndex(pov, priorities[0]); - EXPECT_EQ(coll.priority(headIdx), priorities[0]); - - // test sub-collection's tail priority > maxPriority - headIdx = coll.headIndex(pov, priorities[size - 1] - 1); - EXPECT_EQ(headIdx, QPI::NULL_INDEX); - - // test sub-collection's head priority < minPriority - auto tailIdx = coll.tailIndex(pov, priorities[0] + 1); - EXPECT_EQ(tailIdx, QPI::NULL_INDEX); - - // test sub-collection's tail priority >= minPriority - tailIdx = coll.tailIndex(pov, priorities[size - 1] - 1); - EXPECT_EQ(coll.priority(tailIdx), priorities[size - 1]); - tailIdx = coll.tailIndex(pov, priorities[size - 1]); - EXPECT_EQ(coll.priority(tailIdx), priorities[size - 1]); - - std::vector indices(std::min(size, std::max(1llu, size / 5))); - for (size_t i = 0; i < indices.size(); i++) { - indices[i] = std::abs((QPI::sint64)gen64()) % indices.size(); - } - - for (size_t i : indices) - { - const auto priority = priorities[i]; - - // test sub-collection: head - auto idx = coll.headIndex(pov, priority); - EXPECT_EQ(coll.priority(idx), priority); - - // test sub-collection: higher priority - if (idx != coll.headIndex(pov)) - { - auto higher_priority = priority; - for (size_t j = i - 1; j >= 0; j--) - { - if (priorities[j] > priority) - { - higher_priority = priorities[j]; - break; - } - } - auto prev_idx = coll.prevElementIndex(idx); - EXPECT_EQ(coll.priority(prev_idx), higher_priority); - } - - // test sub-collection: tail - idx = coll.tailIndex(pov, priority); - EXPECT_EQ(coll.priority(idx), priority); - - // test sub-collection: lower priority - if (idx != coll.tailIndex(pov)) - { - auto lower_priority = priority; - for (size_t j = i + 1; j < size; j++) - { - if (priorities[j] < priority) - { - lower_priority = priorities[j]; - break; - } - } - auto next_idx = coll.nextElementIndex(idx); - EXPECT_EQ(coll.priority(next_idx), lower_priority); - } - } - } - - commonBuffers.deinit(); -} - -TEST(TestCoreQPI, CollectionReplaceElements) -{ - QPI::id pov(1, 2, 3, 4); - - QPI::Collection coll; - coll.reset(); - - const int seed = 246357; - std::mt19937_64 gen64(seed); - - // init a collection for test - const size_t numElements = 1000; - std::vector priorities(numElements); - for (size_t i = 0; i < numElements; i++) - { - priorities[i] = std::abs((QPI::sint64)gen64()) % 0xFFFF; - coll.add(pov, i, priorities[i]); - } - checkPriorityQueue(coll, pov, false); - - // test for special cases out of bound element index - size_t replaceElement = 999; - - // out of collection's capacity - coll.replace(coll.capacity(), replaceElement); - for (size_t i = 0; i < numElements; i++) - { - EXPECT_EQ(coll.element(i), i); - } - - // out of collection's size - coll.replace(numElements, replaceElement); - for (size_t i = 0; i < numElements; i++) - { - EXPECT_EQ(coll.element(i), i); - } - - // generate random replace indices - const size_t numTests = 500; - std::vector replaceIndices(numTests); - for (size_t i = 0; i < numTests; i++) - { - replaceIndices[i] = std::abs((QPI::sint64)gen64()) % numElements; - // remove some of indices in the list - if (i % 4) - { - coll.remove(replaceIndices[i]); - } - } - - // get the new priorities list - size_t numRemainedElements = coll.population(); - priorities.resize(numRemainedElements); - for (size_t i = 0; i < numRemainedElements; i++) - { - priorities[i] = coll.priority(i); - } - - // run the test on the list - for (size_t i = 0; i < numTests; i++) - { - QPI::sint64 replaceElement = i; - QPI::sint64 replaceIndex = replaceIndices[i]; - - QPI::sint64 nextElementIndex = coll.nextElementIndex(replaceIndex); - QPI::sint64 prevElementIndex = coll.prevElementIndex(replaceIndex); - - coll.replace(replaceIndex, replaceElement); - - if (size_t(replaceIndex) < numRemainedElements) - { - EXPECT_EQ(coll.element(replaceIndex), replaceElement); - EXPECT_EQ(coll.priority(replaceIndex), priorities[replaceIndex]); - EXPECT_EQ(coll.nextElementIndex(replaceIndex), nextElementIndex); - EXPECT_EQ(coll.prevElementIndex(replaceIndex), prevElementIndex); - } - } -} - -template -void testCollectionPseudoRandom(int povs, int seed, bool povCollisions, int cleanups, int percentAdd = 70, int percentAddSecondHalf = -1) -{ - // add and remove entries with pseudo-random sequence - std::mt19937_64 gen64(seed); - - QPI::Collection coll; - coll.reset(); - CollectionReferenceImpl collReference; - - // test cleanup of empty collection - cleanupCollection(coll); - - int cleanupCounter = 0; - while (cleanupCounter < cleanups) - { - int p = gen64() % 100; - - if (p == 0) - { - // cleanup (after about 100 add/remove) - cleanupCollection(coll); - ++cleanupCounter; - - if (cleanupCounter == cleanups / 2 && percentAddSecondHalf >= 0) - percentAdd = percentAddSecondHalf; - } - - if (p < percentAdd) - { - // add to collection (more probable than remove) - QPI::id pov = (povCollisions) ? QPI::id(0, 0, 0, gen64() % povs) : QPI::id(gen64() % povs, 0, 0, 0); - unsigned long long value = gen64(); - QPI::sint64 priority = gen64(); - if (coll.population() != coll.capacity()) - { - EXPECT_NE(coll.add(pov, value, priority), QPI::NULL_INDEX); - collReference.add(pov, value, priority); - } - else - { - EXPECT_EQ(coll.add(pov, value, priority), QPI::NULL_INDEX); - } - } - else if (coll.population() > 0) - { - // remove from collection (also testing next index returned by remove) - QPI::sint64 removeIdx = gen64() % coll.population(); - QPI::id pov = coll.pov(removeIdx); - QPI::sint64 priority = coll.priority(removeIdx); - unsigned long long value = coll.element(removeIdx); - QPI::sint64 followingRemovedIndex = coll.nextElementIndex(removeIdx); - if (followingRemovedIndex != QPI::NULL_INDEX) - { - unsigned long long followingRemovedValue = coll.element(followingRemovedIndex); - QPI::sint64 followingRemovedPrio = coll.priority(followingRemovedIndex); - followingRemovedIndex = coll.remove(removeIdx); - EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); - EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); - } - else - { - EXPECT_EQ(coll.remove(removeIdx), QPI::NULL_INDEX); - } - collReference.remove(pov, value, priority); - } - - collReference.checkEqualContent(coll); - - // std::cout << "population: " << coll.population() << " = " << coll.population() * 100 / coll.capacity() << " %" << std::endl; - } -} - -TEST(TestCoreQPI, CollectionInsertRemoveCleanupRandom) -{ - commonBuffers.init(1, 10 * 1024 * 1024); - constexpr unsigned int numCleanups = 30; - for (int i = 0; i < 10; ++i) - { - bool povCollisions = false; - testCollectionPseudoRandom<512>(300, 12345 + i, povCollisions, numCleanups, 70, 40); - testCollectionPseudoRandom<256>(256, 1234 + i, povCollisions, numCleanups, 60, 40); - testCollectionPseudoRandom<256>(10, 123 + i, povCollisions, numCleanups, 60, 40); - testCollectionPseudoRandom<16>(10, 12 + i, povCollisions, numCleanups, 55, 45); - testCollectionPseudoRandom<4>(4, 42 + i, povCollisions, numCleanups, 52, 48); - - povCollisions = true; - testCollectionPseudoRandom<512>(300, 12345 + i, povCollisions, numCleanups, 70, 40); - testCollectionPseudoRandom<256>(256, 1234 + i, povCollisions, numCleanups, 60, 40); - testCollectionPseudoRandom<256>(10, 123 + i, povCollisions, numCleanups, 60, 40); - testCollectionPseudoRandom<16>(10, 12 + i, povCollisions, numCleanups, 55, 45); - testCollectionPseudoRandom<4>(4, 42 + i, povCollisions, numCleanups, 52, 48); - } - commonBuffers.deinit(); -} - -TEST(TestCoreQPI, CollectionCleanupWithPovCollisions) -{ - // Shows bugs in cleanup() that occur in case of massive pov hash map collisions and in case of capacity < 32 - commonBuffers.init(1, 10 * 1024 * 1024); - bool cleanupAfterEachRemove = true; - testCollectionMultiPovOneElement<16>(cleanupAfterEachRemove); - testCollectionMultiPovOneElement<32>(cleanupAfterEachRemove); - testCollectionMultiPovOneElement<64>(cleanupAfterEachRemove); - testCollectionMultiPovOneElement<128>(cleanupAfterEachRemove); - commonBuffers.deinit(); -} - - -template -T genNumber( - const T* genBuffer, - const QPI::uint64 genSize, - QPI::uint64& idx) -{ - T val = genBuffer[idx]; - idx = (idx + 1) % genSize; - return val; -} - -// TODO: move all performance tests into a separate project!? -template -QPI::uint64 testCollectionPerformance( - QPI::Collection& coll, - const QPI::uint64 povs, - const QPI::sint64* genBuffer, - const QPI::uint64 genSize, - const QPI::uint64 genSeed, - const QPI::uint64 maxCleanupCounter) -{ - // add and remove entries with pseudo-random sequence - QPI::uint64 idx = genSeed % genSize; - - // test cleanup of empty collection - coll.cleanup(); - -#define GEN64 genNumber(genBuffer, genSize, idx) - - QPI::uint64 cleanupCounter = 0; - while (cleanupCounter < maxCleanupCounter) - { - int p = GEN64 % 100; - - if (p == 0) - { - // cleanup (after about 100 add/remove) - coll.cleanup(); - ++cleanupCounter; - } - - if (p < 70) - { - // add to collection (more probable than remove) - QPI::id pov(GEN64 % povs, 0, 0, 0); - if (coll.add(pov, GEN64, GEN64) == QPI::NULL_INDEX) - { - for (int i = 0; i < 10; i++) - { - p = GEN64 % 100; - if (p < 70) - { - if (coll.population() > 0) - { - coll.remove(GEN64 % coll.population()); - if (!coll.population()) - { - break; - } - } - else - { - coll.cleanup(); - ++cleanupCounter; - break; - } - } - } - } - } - else if (coll.population() > 0) - { - // remove from collection - coll.remove(GEN64 % coll.population()); - } - } - - return coll.population(); -} - -template -QPI::uint64 testCollectionPerformance( - const QPI::uint64 maxPovsCount, const QPI::uint64 maxCleanupCounter) -{ - std::mt19937_64 gen64(113377); - const QPI::uint64 genSize = 113377; - QPI::sint64 gen_buffers[genSize]; - for (QPI::uint64 i = 0; i < genSize; i++) - { - gen_buffers[i] = gen64(); - } - - QPI::Collection* coll = new QPI::Collection(); - coll->reset(); - - auto t0 = std::chrono::high_resolution_clock::now(); - - for (int i = 0; i < 333; ++i) - { - testCollectionPerformance(*coll, - maxPovsCount, gen_buffers, genSize, i + 11, maxCleanupCounter); - } - - auto t1 = std::chrono::high_resolution_clock::now(); - auto duration = t1 - t0; - auto ms = std::chrono::duration_cast(t1 - t0); - - delete coll; - - return ms.count(); -} - -TEST(TestCoreQPI, CollectionPerformance) -{ - - commonBuffers.init(1, 16 * 1024 * 1024); - - std::vector durations; - std::vector descriptions; - - durations.push_back(testCollectionPerformance<1024>(128, 333)); - descriptions.push_back("[CollectionPerformance] Collection<1024>(128, 333)"); - - durations.push_back(testCollectionPerformance<1024>(64, 333)); - descriptions.push_back("[CollectionPerformance] Collection<1024>(64, 333)"); - - durations.push_back(testCollectionPerformance<1024>(32, 333)); - descriptions.push_back("[CollectionPerformance] Collection<1024>(32, 333)"); - - durations.push_back(testCollectionPerformance<1024>(16, 333)); - descriptions.push_back("[CollectionPerformance] Collection<1024>(16, 333)"); - - durations.push_back(testCollectionPerformance<512>(128, 333)); - descriptions.push_back("[CollectionPerformance] Collection<512>(128, 333)"); - - durations.push_back(testCollectionPerformance<512>(64, 333)); - descriptions.push_back("[CollectionPerformance] Collection<512>(64, 333)"); - - durations.push_back(testCollectionPerformance<512>(32, 333)); - descriptions.push_back("[CollectionPerformance] Collection<512>(32, 333)"); - - durations.push_back(testCollectionPerformance<512>(16, 333)); - descriptions.push_back("[CollectionPerformance] Collection<512>(16, 333)"); - - commonBuffers.deinit(); - - bool verbose = true; - if (verbose) - { - QPI::uint64 total = 0; - for (size_t i = 0; i < durations.size(); i++) - { - total += durations[i]; - std::cout << "- " << descriptions[i] << ":\t" << durations[i] << " ms\n"; - } - std::cout << "* [CollectionPerformance] Total:\t\t" << total << " ms\n"; - } -} diff --git a/test/qpi_date_time.cpp b/test/qpi_date_time.cpp deleted file mode 100644 index 654d02bd4..000000000 --- a/test/qpi_date_time.cpp +++ /dev/null @@ -1,589 +0,0 @@ -#define NO_UEFI -#include "gtest/gtest.h" - -#include - -// workaround for name clash with stdlib -#define system qubicSystemStruct - -#include "contract_core/contract_def.h" -#include "contract_core/contract_exec.h" -#include "contract_core/qpi_spectrum_impl.h" - -#include "../src/contract_core/qpi_ticking_impl.h" - -#include - -::std::ostream& operator<<(::std::ostream& os, const DateAndTime& dt) -{ - std::ios_base::fmtflags f(os.flags()); - os << std::setfill('0') << dt.getYear() << "-" - << std::setw(2) << (int)dt.getMonth() << "-" - << std::setw(2) << (int)dt.getDay() << " " - << std::setw(2) << (int)dt.getHour() << ":" - << std::setw(2) << (int)dt.getMinute() << ":" - << std::setw(2) << (int)dt.getSecond() << "." - << std::setw(3) << dt.getMillisec() << "'" - << std::setw(3) << dt.getMicrosecDuringMillisec(); - os.flags(f); - return os; -} - -TEST(DateAndTimeTest, IsLeapYear) -{ - EXPECT_TRUE(DateAndTime::isLeapYear(1600)); - EXPECT_FALSE(DateAndTime::isLeapYear(1700)); - EXPECT_FALSE(DateAndTime::isLeapYear(1800)); - EXPECT_FALSE(DateAndTime::isLeapYear(1900)); - EXPECT_TRUE(DateAndTime::isLeapYear(2000)); - EXPECT_FALSE(DateAndTime::isLeapYear(2019)); - EXPECT_TRUE(DateAndTime::isLeapYear(2020)); - EXPECT_FALSE(DateAndTime::isLeapYear(2021)); - EXPECT_FALSE(DateAndTime::isLeapYear(2022)); - EXPECT_FALSE(DateAndTime::isLeapYear(2023)); - EXPECT_TRUE(DateAndTime::isLeapYear(2024)); - EXPECT_FALSE(DateAndTime::isLeapYear(2025)); - EXPECT_FALSE(DateAndTime::isLeapYear(2026)); - EXPECT_FALSE(DateAndTime::isLeapYear(2027)); - EXPECT_TRUE(DateAndTime::isLeapYear(2028)); - EXPECT_FALSE(DateAndTime::isLeapYear(2029)); - EXPECT_FALSE(DateAndTime::isLeapYear(2030)); - EXPECT_FALSE(DateAndTime::isLeapYear(2031)); - EXPECT_TRUE(DateAndTime::isLeapYear(2032)); - EXPECT_FALSE(DateAndTime::isLeapYear(2100)); - EXPECT_FALSE(DateAndTime::isLeapYear(2200)); - EXPECT_TRUE(DateAndTime::isLeapYear(2400)); - EXPECT_FALSE(DateAndTime::isLeapYear(2500)); - EXPECT_TRUE(DateAndTime::isLeapYear(2800)); - EXPECT_TRUE(DateAndTime::isLeapYear(3200)); -} - -TEST(DateAndTimeTest, IsValid) -{ - // Checking year - EXPECT_TRUE(DateAndTime::isValid(0, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2100, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(65535, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(65536, 1, 1, 0, 0, 0, 0, 0)); - - // Checking month - EXPECT_FALSE(DateAndTime::isValid(2025, 0, 1, 0, 0, 0, 0, 0)); - for (int i = 1; i <= 12; ++i) - EXPECT_TRUE(DateAndTime::isValid(2025, i, 1, 0, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 13, 1, 0, 0, 0, 0, 0)); - - // Checking day range - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 0, 0, 0, 0, 0, 0)); - for (int i = 1; i <= 31; ++i) - EXPECT_TRUE(DateAndTime::isValid(2025, 1, i, 0, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 32, 0, 0, 0, 0, 0)); - - // Checking last day of month (except Feb) - int daysPerMonth[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - for (int i = 0; i < 12; ++i) - { - if (daysPerMonth[i]) - { - EXPECT_TRUE(DateAndTime::isValid(2025, i + 1, daysPerMonth[i], 0, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, i + 1, daysPerMonth[i] + 1, 0, 0, 0, 0, 0)); - } - } - - // Checking last day of February - for (int year = 1582; year < 3000; ++year) - { - int daysInFeb = (DateAndTime::isLeapYear(year)) ? 29 : 28; - EXPECT_TRUE(DateAndTime::isValid(year, 2, daysInFeb, 0, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(year, 2, daysInFeb + 1, 0, 0, 0, 0, 0)); - } - - // Checking hour - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 3, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 14, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 23, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 24, 0, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 25, 0, 0, 0, 0)); - - // Checking minute - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 49, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 59, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 60, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 61, 0, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 101, 0, 0, 0)); - - // Checking second - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 49, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 59, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 60, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 61, 0, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 101, 0, 0)); - - // Checking millisec - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 999, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 1000, 0)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 1002, 0)); - - // Checking microsec - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 999)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 1000)); - EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 1002)); -} - -TEST(DateAndTimeTest, SetAndGet) -{ - // Default constructor (invalid value) - DateAndTime d1; - EXPECT_FALSE(d1.isValid()); - EXPECT_EQ((int)d1.getYear(), 0); - EXPECT_EQ(d1.getMonth(), 0); - EXPECT_EQ(d1.getDay(), 0); - EXPECT_EQ(d1.getHour(), 0); - EXPECT_EQ(d1.getMinute(), 0); - EXPECT_EQ(d1.getSecond(), 0); - EXPECT_EQ((int)d1.getMillisec(), 0); - EXPECT_EQ((int)d1.getMicrosecDuringMillisec(), 0); - - // Set if valid - EXPECT_FALSE(d1.setIfValid(2025, 0, 0, 0, 0, 0)); - EXPECT_TRUE(d1.setIfValid(2025, 1, 2, 3, 4, 5, 6, 7)); - EXPECT_EQ((int)d1.getYear(), 2025); - EXPECT_EQ(d1.getMonth(), 1); - EXPECT_EQ(d1.getDay(), 2); - EXPECT_EQ(d1.getHour(), 3); - EXPECT_EQ(d1.getMinute(), 4); - EXPECT_EQ(d1.getSecond(), 5); - EXPECT_EQ((int)d1.getMillisec(), 6); - EXPECT_EQ((int)d1.getMicrosecDuringMillisec(), 7); - - // Copy constructor - DateAndTime d2(d1); - EXPECT_EQ((int)d2.getYear(), 2025); - EXPECT_EQ(d2.getMonth(), 1); - EXPECT_EQ(d2.getDay(), 2); - EXPECT_EQ(d2.getHour(), 3); - EXPECT_EQ(d2.getMinute(), 4); - EXPECT_EQ(d2.getSecond(), 5); - EXPECT_EQ((int)d2.getMillisec(), 6); - EXPECT_EQ((int)d2.getMicrosecDuringMillisec(), 7); - - // Set edge case values (invalid as date but good for checking if - // bit-level processing works) - d2.set(65535, 15, 31, 31, 63, 63, 1023, 1023); - EXPECT_FALSE(d2.isValid()); - EXPECT_EQ((int)d2.getYear(), 65535); - EXPECT_EQ(d2.getMonth(), 15); - EXPECT_EQ(d2.getDay(), 31); - EXPECT_EQ(d2.getHour(), 31); - EXPECT_EQ(d2.getMinute(), 63); - EXPECT_EQ(d2.getSecond(), 63); - EXPECT_EQ((int)d2.getMillisec(), 1023); - EXPECT_EQ((int)d2.getMicrosecDuringMillisec(), 1023); - - // Operator = - EXPECT_NE(d1, d2); - d2 = d1; - EXPECT_EQ(d1, d2); - - // Test setTime() and setDate(), which runs without checking validity - EXPECT_EQ(d1, DateAndTime(2025, 1, 2, 3, 4, 5, 6, 7)); - d1.setDate(65535, 15, 31); - EXPECT_EQ(d1, DateAndTime(65535, 15, 31, 3, 4, 5, 6, 7)); - d1.setTime(31, 63, 63, 1023, 1023); - EXPECT_EQ(d1, DateAndTime(65535, 15, 31, 31, 63, 63, 1023, 1023)); - d1.setDate(2030, 7, 10); - EXPECT_EQ(d1, DateAndTime(2030, 7, 10, 31, 63, 63, 1023, 1023)); - d1.setTime(20, 15, 16, 457, 738); - EXPECT_EQ(d1, DateAndTime(2030, 7, 10, 20, 15, 16, 457, 738)); -} - -TEST(DateAndTimeTest, Equality) -{ - DateAndTime d1{ 2024, 3, 3, 8, 15, 30, 500 }; // 2024-03-03 8:15:30.500 - DateAndTime d2{ 2024, 3, 3, 8, 15, 30, 500 }; - DateAndTime d3{ 2024, 3, 3, 8, 15, 30, 501 }; // Different millisecond - DateAndTime d4{ 2025, 3, 3, 8, 15, 30, 500 }; // Different year - - EXPECT_EQ(d1, d2); - EXPECT_EQ(d1, d1); - EXPECT_NE(d1, d3); - EXPECT_NE(d1, d4); -} - -TEST(DateAndTimeTest, Comparison) -{ - DateAndTime sorted[] = { - { 2024, 6, 1, 0, 0, 0 }, // June 1, 2024, 00:00:00.0 - { 2025, 5, 1, 0, 0, 0 }, // May 1, 2025, 00:00:00.0 - { 2025, 5, 29, 12, 0, 0 }, - { 2025, 6, 1, 10, 0, 0 }, - { 2025, 6, 1, 10, 10, 0 }, - { 2025, 6, 1, 10, 10, 10 }, - { 2025, 6, 1, 12, 0, 0 }, // June 1, 2025, 12:00:00.0 - { 2025, 6, 1, 12, 0, 0, 0, 999 }, // June 1, 2025, 12:00:00.000999 - { 2025, 6, 1, 12, 0, 0, 1, 0 }, // June 1, 2025, 12:00:00.001000 - { 2025, 6, 1, 12, 0, 1 }, // June 1, 2025, 12:00:01.0 - { 2025, 6, 1, 12, 1, 0 }, // June 1, 2025, 12:01:00.0 - { 2025, 6, 1, 13, 0, 0 }, // June 1, 2025, 13:01:00.0 - { 2025, 6, 2, 10, 0, 0 }, // June 2, 2025, 10:00:00.0 - { 2025, 7, 1, 10, 0, 0 }, // July 1, 2025, 10:00:00.0 - { 2026, 6, 1, 10, 0, 0 }, // June 1, 2026, 10:00:00.0 - }; - const int count = sizeof(sorted) / sizeof(sorted[0]); - - for (int i = 0; i < count; ++i) - { - for (int j = i; j < count; ++j) - { - if (i == j) - { - EXPECT_EQ(sorted[i], sorted[j]); - } - else - { - EXPECT_LT(sorted[i], sorted[j]); - EXPECT_GT(sorted[j], sorted[i]); - } - } - } -} - -TEST(DateAndTimeTest, Addition) -{ - // changing date only (using day count) - DateAndTime d1{ 2025, 1, 1 }; - EXPECT_TRUE(d1.add(0, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1)); - EXPECT_TRUE(d1.add(0, 0, 10)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 11)); - EXPECT_TRUE(d1.add(0, 0, -6)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 5)); - EXPECT_TRUE(d1.add(0, 0, 26)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 31)); - EXPECT_TRUE(d1.add(0, 0, 15)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 15)); - EXPECT_TRUE(d1.add(0, 0, 15)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 2)); - EXPECT_TRUE(d1.add(0, 0, -2)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, -13)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 15)); - EXPECT_TRUE(d1.add(0, 0, -20)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 26)); - EXPECT_TRUE(d1.add(0, 0, -27)); - EXPECT_EQ(d1, DateAndTime(2024, 12, 30)); - EXPECT_TRUE(d1.add(0, 0, 31)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 30)); - EXPECT_TRUE(d1.add(0, 0, 31)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 2)); - EXPECT_TRUE(d1.add(0, 0, -31)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 30)); - EXPECT_TRUE(d1.add(0, 0, 52)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 23)); - EXPECT_TRUE(d1.add(0, 0, 70)); - EXPECT_EQ(d1, DateAndTime(2025, 6, 1)); - EXPECT_TRUE(d1.add(0, 0, -122)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 30)); - EXPECT_TRUE(d1.add(0, 0, 132)); - EXPECT_EQ(d1, DateAndTime(2025, 6, 11)); - EXPECT_TRUE(d1.add(0, 0, -162)); - EXPECT_EQ(d1, DateAndTime(2024, 12, 31)); - EXPECT_TRUE(d1.add(0, 0, -35)); - EXPECT_EQ(d1, DateAndTime(2024, 11, 26)); - EXPECT_TRUE(d1.add(0, 0, -25)); - EXPECT_EQ(d1, DateAndTime(2024, 11, 1)); - EXPECT_TRUE(d1.add(0, 0, 60)); - EXPECT_EQ(d1, DateAndTime(2024, 12, 31)); - EXPECT_TRUE(d1.add(0, 0, -140)); - EXPECT_EQ(d1, DateAndTime(2024, 8, 13)); - EXPECT_TRUE(d1.add(0, 0, -49)); - EXPECT_EQ(d1, DateAndTime(2024, 6, 25)); - EXPECT_TRUE(d1.add(0, 0, 190)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1)); - EXPECT_TRUE(d1.add(0, 0, -200)); - EXPECT_EQ(d1, DateAndTime(2024, 6, 15)); - EXPECT_TRUE(d1.add(0, 0, 220)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 21)); - EXPECT_TRUE(d1.add(0, 0, -220)); - EXPECT_EQ(d1, DateAndTime(2024, 6, 15)); - EXPECT_TRUE(d1.add(0, 0, -21)); - EXPECT_EQ(d1, DateAndTime(2024, 5, 25)); - EXPECT_TRUE(d1.add(0, 0, -50)); - EXPECT_EQ(d1, DateAndTime(2024, 4, 5)); - EXPECT_TRUE(d1.add(0, 0, 70)); - EXPECT_EQ(d1, DateAndTime(2024, 6, 14)); - EXPECT_TRUE(d1.add(0, 0, -80)); - EXPECT_EQ(d1, DateAndTime(2024, 3, 26)); - EXPECT_TRUE(d1.add(0, 0, -26)); - EXPECT_EQ(d1, DateAndTime(2024, 2, 29)); - EXPECT_TRUE(d1.add(0, 0, 1)); - EXPECT_EQ(d1, DateAndTime(2024, 3, 1)); - EXPECT_TRUE(d1.add(0, 0, -2)); - EXPECT_EQ(d1, DateAndTime(2024, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, 366)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, -366)); - EXPECT_EQ(d1, DateAndTime(2024, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, 366 + 365)); - EXPECT_EQ(d1, DateAndTime(2026, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, 1)); - EXPECT_EQ(d1, DateAndTime(2026, 3, 1)); - EXPECT_TRUE(d1.add(0, 0, -1)); - EXPECT_EQ(d1, DateAndTime(2026, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, -365 - 366 - 365)); - EXPECT_EQ(d1, DateAndTime(2023, 2, 28)); - d1.setDate(2025, 1, 31); - EXPECT_TRUE(d1.add(0, 0, 31)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 3)); - d1.setDate(2025, 12, 31); - EXPECT_TRUE(d1.add(0, 0, 31)); - EXPECT_EQ(d1, DateAndTime(2026, 1, 31)); - d1.setDate(2025, 3, 31); - EXPECT_TRUE(d1.add(0, 0, -31)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); - d1.setDate(2024, 2, 28); - EXPECT_TRUE(d1.add(0, 0, 366)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, -366)); - EXPECT_EQ(d1, DateAndTime(2024, 2, 28)); - d1.setDate(2024, 2, 29); - EXPECT_TRUE(d1.add(0, 0, 365)); - EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); - EXPECT_TRUE(d1.add(0, 0, -365)); - EXPECT_EQ(d1, DateAndTime(2024, 2, 29)); - d1.setDate(2024, 2, 29); - EXPECT_TRUE(d1.add(0, 0, 366)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); - EXPECT_TRUE(d1.add(0, 0, -366)); - EXPECT_EQ(d1, DateAndTime(2024, 2, 29)); - d1.setDate(2024, 3, 1); - EXPECT_TRUE(d1.add(0, 0, 365)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); - EXPECT_TRUE(d1.add(0, 0, -365)); - EXPECT_EQ(d1, DateAndTime(2024, 3, 1)); - d1.setDate(2024, 3, 1); - EXPECT_TRUE(d1.add(0, 0, 366)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 2)); - EXPECT_TRUE(d1.add(0, 0, -366)); - EXPECT_EQ(d1, DateAndTime(2024, 3, 1)); - - // changing date only using months count - d1.setDate(2025, 10, 31); - EXPECT_TRUE(d1.add(0, 1, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 12, 1)); - EXPECT_TRUE(d1.add(0, -1, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 11, 1)); - EXPECT_TRUE(d1.add(0, 5, 0)); - EXPECT_EQ(d1, DateAndTime(2026, 4, 1)); - EXPECT_TRUE(d1.add(0, -6, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 10, 1)); - EXPECT_TRUE(d1.add(0, 9, 0)); - EXPECT_EQ(d1, DateAndTime(2026, 7, 1)); - EXPECT_TRUE(d1.add(0, -12, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 7, 1)); - EXPECT_TRUE(d1.add(0, 12, 0)); - EXPECT_EQ(d1, DateAndTime(2026, 7, 1)); - EXPECT_TRUE(d1.add(0, -18, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1)); - EXPECT_TRUE(d1.add(0, 18, 0)); - EXPECT_EQ(d1, DateAndTime(2026, 7, 1)); - EXPECT_TRUE(d1.add(0, -24, 0)); - EXPECT_EQ(d1, DateAndTime(2024, 7, 1)); - EXPECT_TRUE(d1.add(0, 36, 0)); - EXPECT_EQ(d1, DateAndTime(2027, 7, 1)); - d1.setDate(2024, 2, 29); - EXPECT_TRUE(d1.add(0, -12, 0)); - EXPECT_EQ(d1, DateAndTime(2023, 3, 1)); - d1.setDate(2024, 2, 29); - EXPECT_TRUE(d1.add(0, 12, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); - - // changing date only using year count - d1.setDate(2025, 10, 31); - EXPECT_TRUE(d1.add(100, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2125, 10, 31)); - EXPECT_TRUE(d1.add(-200, 0, 0)); - EXPECT_EQ(d1, DateAndTime(1925, 10, 31)); - d1.setDate(2024, 2, 29); - EXPECT_TRUE(d1.add(1, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); - d1.setDate(2024, 2, 29); - EXPECT_TRUE(d1.add(-3, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2021, 3, 1)); - - // change time and date - d1 = DateAndTime{2025, 1, 1, 12, 0, 0, 0, 0}; - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 1)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 0, 1)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 3500)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 3, 501)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 3, 500)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -2500)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 1, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 0, 999)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 2)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 1, 1)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -10)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 0, 991)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1000)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 11, 59, 59, 999, 991)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 1009)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 1, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1001000)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 11, 59, 59, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 61 * 1000000)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 60 * 60 * 1000000ll)); - EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 13, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -14 * 60 * 60 * 1000000ll)); - EXPECT_EQ(d1, DateAndTime(2024, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 365 * 24 * 60 * 60 * 1000000ll)); - EXPECT_EQ(d1, DateAndTime(2025, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 365 * 24 * 60 * 60 * 1000ll)); - EXPECT_EQ(d1, DateAndTime(2026, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 365 * 24 * 60 * 60)); - EXPECT_EQ(d1, DateAndTime(2027, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 366 * 24 * 60, 0)); - EXPECT_EQ(d1, DateAndTime(2028, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 365 * 24, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2029, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 365, 0, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 12, 0, 0, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2031, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(1, 0, 0, 0, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2032, 12, 31, 23, 1, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, -10, -3, 0, -1, 0)); - EXPECT_EQ(d1, DateAndTime(2032, 2, 28, 23, 0, 0, 0, 0)); - EXPECT_TRUE(d1.add(0, 0, 1, 0, 59, 59, 999, 999)); - EXPECT_EQ(d1, DateAndTime(2032, 2, 29, 23, 59, 59, 999, 999)); - EXPECT_TRUE(d1.add(-1, 0, 0, 0, 0, 0, 0, 0)); - EXPECT_EQ(d1, DateAndTime(2031, 3, 1, 23, 59, 59, 999, 999)); - EXPECT_TRUE(d1.add(0, -1, -29, 0, 2, -120, 1, -1999)); - EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 23, 59, 59, 999, 0)); - EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 1000)); - EXPECT_EQ(d1, DateAndTime(2031, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(d1.add(1, -12, 2, -48, 2, -120, 10, -10000)); - EXPECT_EQ(d1, DateAndTime(2031, 1, 1, 0, 0, 0, 0, 0)); - EXPECT_TRUE(d1.add(-3, 36, -1, 24, -3, 180, -5, 4999)); - EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 23, 59, 59, 999, 999)); - - // test addDays() and addMicrosec() helpers - EXPECT_TRUE(d1.addDays(15)); - EXPECT_EQ(d1, DateAndTime(2031, 1, 15, 23, 59, 59, 999, 999)); - EXPECT_TRUE(d1.addDays(-16)); - EXPECT_EQ(d1, DateAndTime(2030, 12, 30, 23, 59, 59, 999, 999)); - EXPECT_TRUE(d1.addMicrosec(2)); - EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 0, 0, 0, 0, 1)); - EXPECT_TRUE(d1.addMicrosec(-2002)); - EXPECT_EQ(d1, DateAndTime(2030, 12, 30, 23, 59, 59, 997, 999)); - - // test speedup of adding large number of days - d1.set(2000, 1, 1, 0, 0, 0); - EXPECT_TRUE(d1.addDays(366)); - EXPECT_EQ(d1, DateAndTime(2001, 1, 1, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(-366 - 365)); - EXPECT_EQ(d1, DateAndTime(1999, 1, 1, 0, 0, 0)); - d1.set(2000, 3, 1, 0, 0, 0); - EXPECT_TRUE(d1.addDays(365)); - EXPECT_EQ(d1, DateAndTime(2001, 3, 1, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(-365)); - EXPECT_EQ(d1, DateAndTime(2000, 3, 1, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(-366)); - EXPECT_EQ(d1, DateAndTime(1999, 3, 1, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(3 * 366 + 7 * 365)); // leap years: 2000, 2004, 2008 - EXPECT_EQ(d1, DateAndTime(2009, 3, 1, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(7 * 366 + 23 * 365)); // leap years: 2012, 2016, 2020, 2024, 2028, 2032, 2036 - EXPECT_EQ(d1, DateAndTime(2039, 3, 1, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(-(3 * 366 + 12 * 365))); // leap years: 2028, 2032, 2036 (2024 not included due to date after Feb) - EXPECT_EQ(d1, DateAndTime(2024, 3, 1, 0, 0, 0)); - d1.set(2039, 2, 28, 0, 0, 0); - EXPECT_TRUE(d1.addDays(-(4 * 366 + 11 * 365))); // leap years: 2024, 2028, 2032, 2036 - EXPECT_EQ(d1, DateAndTime(2024, 2, 28, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(97 * 366 + 303 * 365)); // 400 years always have the same number of leap years - EXPECT_EQ(d1, DateAndTime(2424, 2, 28, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(2 * 366 + 3 * 365)); // leap years: 2424, 2028 - EXPECT_EQ(d1, DateAndTime(2429, 2, 28, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(97 * 366 + 304 * 365)); // 400 years always have the same number of leap years + 1 year - EXPECT_EQ(d1, DateAndTime(2830, 2, 28, 0, 0, 0)); - EXPECT_TRUE(d1.addDays(-3 * (97 * 366 + 303 * 365))); // 400 years always have the same number of leap years - EXPECT_EQ(d1, DateAndTime(1630, 2, 28, 0, 0, 0)); - EXPECT_TRUE(d1.addDays((97 * 366 + 303 * 365) + 365 + 1)); // + 400 years + 1 year (2031) + 1 day - EXPECT_EQ(d1, DateAndTime(2031, 3, 1, 0, 0, 0)); - - // test some error cases - EXPECT_FALSE(d1.addDays(366 * 66000)); - EXPECT_FALSE(d1.add(INT64_MAX - 1000, 0, 0)); - EXPECT_FALSE(d1.add(0, INT64_MAX, 0)); - d1.setTime(0, 0, 0, 0, 999); - EXPECT_FALSE(d1.add(0, 0, 0, 0, 0, 0, 0, INT64_MAX - 998)); - EXPECT_FALSE(d1.add(0, 0, 0, 0, 0, 0, INT64_MIN, INT64_MIN)); -} - -uint64 microSeconds(uint64 days, uint64 hours, uint64 minutes, uint64 seconds, uint64 milli, uint64 micro) -{ - return (((((((days * 24) + hours) * 60llu) + minutes) * 60 + seconds) * 1000) + milli) * 1000 + micro; -} - -TEST(DateAndTimeTest, Subtraction) -{ - DateAndTime d0; - DateAndTime d1(2030, 12, 31, 5, 4, 3, 2, 1); - EXPECT_EQ(d0.durationMicrosec(d0), UINT64_MAX); - EXPECT_EQ(d0.durationMicrosec(d1), UINT64_MAX); - EXPECT_EQ(d1.durationMicrosec(d0), UINT64_MAX); - - DateAndTime d2(2030, 12, 31, 0, 0, 0, 0, 0); - EXPECT_EQ(d1.durationMicrosec(d1), 0); - EXPECT_EQ(d1.durationMicrosec(d2), d2.durationMicrosec(d1)); - EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(0, 5, 4, 3, 2, 1)); - EXPECT_EQ(d1.durationDays(d2), 0); - - d1.set(2030, 12, 31, 23, 59, 59, 999, 999); - d2.set(2031, 1, 1, 0, 0, 0, 0, 0); - EXPECT_EQ(d1.durationMicrosec(d2), 1); - EXPECT_EQ(d1.durationDays(d2), 0); - - d1.set(2025, 1, 1, 0, 0, 0, 0, 0); - d2.set(2027, 1, 1, 0, 0, 0, 0, 0); - EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(2 * 365, 0, 0, 0, 0, 0)); - EXPECT_EQ(d1.durationDays(d2), 2 * 365); - - d1.set(2027, 1, 1, 12, 15, 43, 123, 456); - d2.set(2025, 1, 1, 11, 10, 34, 101, 412); - EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(2 * 365, 1, 5, 9, 22, 44)); - EXPECT_EQ(d1.durationDays(d2), 2 * 365); - - d1.set(2027, 1, 1, 12, 15, 43, 123, 456); - d2.set(2024, 1, 1, 11, 10, 34, 101, 412); - EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(366 + 2 * 365, 1, 5, 9, 22, 44)); - EXPECT_EQ(d2.durationMicrosec(d1), microSeconds(366 + 2 * 365, 1, 5, 9, 22, 44)); - EXPECT_EQ(d1.durationDays(d2), 366 + 2 * 365); - EXPECT_EQ(d2.durationDays(d1), 366 + 2 * 365); - - d2.set(2024, 1, 1, 0, 0, 0, 0, 0); - d1.set(2024, 4, 1, 0, 0, 0, 0, 42); - EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(31 + 29 + 31, 0, 0, 0, 0, 42)); - EXPECT_EQ(d1.durationDays(d2), 31 + 29 + 31); - - std::mt19937_64 gen64(42); - for (int i = 0; i < 1000; ++i) - { - d1.set((gen64() % 3000) + 1500, (gen64() % 12) + 1, (gen64() % 28) + 1, gen64() % 24, - gen64() % 60, gen64() % 60, gen64() % 999, gen64() % 999); - EXPECT_TRUE(d1.isValid()); - - sint64 microsec = (sint64)gen64() % (1000llu * 365 * 24 * 60 * 60 * 1000 * 1000); - d2 = d1; - EXPECT_TRUE(d2.addMicrosec(microsec)); - EXPECT_TRUE(d2.isValid()); - - EXPECT_EQ(d2.durationMicrosec(d1), microsec); - } -} diff --git a/test/qpi_hash_map.cpp b/test/qpi_hash_map.cpp deleted file mode 100644 index b28dc2e7d..000000000 --- a/test/qpi_hash_map.cpp +++ /dev/null @@ -1,1009 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#include "../src/contract_core/pre_qpi_def.h" -#include "../src/contracts/qpi.h" -#include "../src/common_buffers.h" -#include "../src/contract_core/qpi_hash_map_impl.h" -#include -#include -#include -#include -#include -#include - - -// New KeyT, ValueT combinations for testing need to implement the following functions: -template -struct HashMapTestData -{ - static std::array, 4> CreateKeyValueTestPairs(); - static inline KeyT GetKeyNotInTestPairs(); - static inline ValueT GetValueNotInTestPairs(); -}; - -// Test data for KeyT = QPI::id, ValueT = int. -template<> -std::array, 4> HashMapTestData::CreateKeyValueTestPairs() -{ - std::array, 4> res = { { - {{ 87, 456, 29, 823}, 17 }, - {{ 897, 23, 64, 90 }, 5467}, - {{5478, 908, 123, 45}, 8752}, - {{ 143, 746, 87, 6 }, 5348}, - } }; - return res; -} - -template<> -inline QPI::id HashMapTestData::GetKeyNotInTestPairs() -{ - return { 1,2,3,4 }; -} - -template<> -inline int HashMapTestData::GetValueNotInTestPairs() -{ - return 42; -} - -// Test data for KeyT = QPI::sint64, ValueT = char. -template<> -std::array, 4> HashMapTestData::CreateKeyValueTestPairs() -{ - std::array, 4> res = { { - { -564, 'a'}, - { 67, 'z'}, - { -783, 'f'}, - { 8924, 'h'}, - } }; - return res; -} - -template<> -inline QPI::sint64 HashMapTestData::GetKeyNotInTestPairs() -{ - return 1234; -} - -template<> -inline char HashMapTestData::GetValueNotInTestPairs() -{ - return '*'; -} - - -// testdata for KeyT = QPI::BitArray<1024>, ValueT = uint64 -template<> -std::array, 4> HashMapTestData::CreateKeyValueTestPairs() -{ - // Create a properly sized array to work with BitArray's setMem - alignas(32) unsigned char buffer[128] = {0}; // 1024 bits = 128 bytes - - // Helper lambda to set a string pattern in bit_1024 - auto setStringKey = [](QPI::bit_1024& bits, const std::string& str) { - bits.setAll(0); // Clear all bits first - // Each character becomes 8 bits - for(size_t i = 0; i < str.length() && i < 128; i++) // 128 is max bytes (1024/8) - { - unsigned char c = str[i]; - // Set 8 bits for this character - for(int bit = 0; bit < 8; bit++) - { - bits.set(i * 8 + bit, (c & (1 << bit)) != 0); - } - } - }; - - QPI::bit_1024 key1, key2, key3, key4; - setStringKey(key1, "TestString1"); - setStringKey(key2, "AnotherTest2"); - setStringKey(key3, "ThirdString3"); - setStringKey(key4, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_-+={}[]|;:\"\'<>,.?/~`abcdefghijklmnopqrstuvwxyzABCDEFGH"); - - std::array, 4> res; - res = { { - { key1, 12}, - { key2, 99}, - { key3, 723}, - { key4, 0}, - } }; - return res; -} - -template<> -inline QPI::bit_1024 HashMapTestData::GetKeyNotInTestPairs() -{ - alignas(32) unsigned char buffer[128] = {0}; - QPI::bit_1024 key; - std::string str = "NotInTestPairs"; - for(size_t i = 0; i < str.length() && i < 128; i++) - { - unsigned char c = str[i]; - for(int bit = 0; bit < 8; bit++) - { - key.set(i * 8 + bit, (c & (1 << bit)) != 0); - } - } - return key; -} - -template<> -inline QPI::uint64 HashMapTestData::GetValueNotInTestPairs() -{ - return 42; -} - -// Define the test fixture class with a single template parameter T because the type list -// for test instantiation will contain pair-types std::pair to use for T. -template -class QPIHashMapTest : public testing::Test {}; - -using testing::Types; - -TYPED_TEST_CASE_P(QPIHashMapTest); - - -TEST(NonTypedQPIHashMapTest, TestHashFunctionQPIID) -{ - auto randomId = QPI::id::randomValue(); - - // Check that the hash function returns the first 8 bytes as hash for QPI::id. - QPI::uint64 hashRes = QPI::HashFunction::hash(randomId); - EXPECT_EQ(hashRes, randomId.u64._0); -} - -TEST(NonTypedQPIHashMapTest, TestHashFunction) -{ - std::unordered_set hashesSoFar; - - for (int i = 0; i < 1000; ++i) - { - // We expect the hash function to produce different hashes for 0...N. - QPI::uint64 hashRes = QPI::HashFunction::hash(i); - EXPECT_FALSE(hashesSoFar.contains(hashRes)); - hashesSoFar.insert(hashRes); - } -} - -TYPED_TEST_P(QPIHashMapTest, TestCreation) -{ - constexpr QPI::uint64 capacity = 2; - QPI::HashMap hashMap; - - EXPECT_EQ(hashMap.capacity(), capacity); - EXPECT_EQ(hashMap.population(), 0); -} - -TYPED_TEST_P(QPIHashMapTest, TestGetters) -{ - constexpr QPI::uint64 capacity = 4; - QPI::HashMap hashMap; - - typedef HashMapTestData TestData; - - std::array keyValuePairs = TestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - QPI::sint64 returnedIndex; - - EXPECT_EQ(hashMap.getElementIndex(ids[3]), QPI::NULL_INDEX); - EXPECT_FALSE(hashMap.contains(ids[3])); - EXPECT_TRUE(hashMap.isEmptySlot(0)); - - for (int i = 0; i < 4; ++i) - { - returnedIndex = hashMap.set(ids[i], values[i]); - } - - EXPECT_EQ(hashMap.getElementIndex(ids[3]), returnedIndex); - EXPECT_TRUE(hashMap.contains(ids[3])); - EXPECT_EQ(hashMap.key(returnedIndex), ids[3]); - EXPECT_EQ(hashMap.value(returnedIndex), values[3]); - EXPECT_FALSE(hashMap.isEmptySlot(returnedIndex)); - - typename TypeParam::second_type valueAfter = {}; - typename TypeParam::second_type valueBefore = valueAfter; - - EXPECT_FALSE(hashMap.get(TestData::GetKeyNotInTestPairs(), valueAfter)); - EXPECT_EQ(valueAfter, valueBefore); - - EXPECT_TRUE(hashMap.get(ids[0], valueAfter)); - EXPECT_EQ(valueAfter, values[0]); - - // Test getElementIndex when all slots are marked for removal so it has to iterate through the whole hash map. - for (int i = 0; i < 4; ++i) - { - hashMap.removeByKey(ids[i]); - } - EXPECT_EQ(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); - - EXPECT_TRUE(hashMap.isEmptySlot(0)); - - hashMap.cleanupIfNeeded(); -} - -TYPED_TEST_P(QPIHashMapTest, TestSet) -{ - constexpr QPI::uint64 capacity = 2; - QPI::HashMap hashMap; - - std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - - QPI::sint64 returnedIndex = hashMap.set(ids[0], values[0]); - EXPECT_EQ(hashMap.population(), 1); - EXPECT_GE(returnedIndex, 0); - EXPECT_LT(QPI::uint64(returnedIndex), capacity); - - // Set with existing key should overwrite the value. - EXPECT_EQ(hashMap.set(ids[0], 42), returnedIndex); - EXPECT_EQ(hashMap.value(returnedIndex), 42); - - returnedIndex = hashMap.set(ids[1], values[1]); - EXPECT_EQ(hashMap.population(), hashMap.capacity()); - - // Set when full should return NULL_INDEX for new key. - EXPECT_EQ(hashMap.set(ids[2], values[2]), QPI::NULL_INDEX); - - // Set when full should still work for existing key and replace the value. - EXPECT_EQ(hashMap.set(ids[1], values[2]), returnedIndex); - EXPECT_EQ(hashMap.value(returnedIndex), values[2]); -} - -TYPED_TEST_P(QPIHashMapTest, TestRemove) -{ - constexpr QPI::uint64 capacity = 8; - QPI::HashMap hashMap; - - typedef HashMapTestData TestData; - - std::array keyValuePairs = TestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - QPI::sint64 returnedIndex; - - for (int i = 0; i < 3; ++i) - { - returnedIndex = hashMap.set(ids[i], values[i]); - } - EXPECT_EQ(hashMap.population(), 3); - - // Remove by element index. - hashMap.removeByIndex(returnedIndex); - - EXPECT_EQ(hashMap.population(), 2); - EXPECT_NE(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); - EXPECT_NE(hashMap.getElementIndex(ids[1]), QPI::NULL_INDEX); - EXPECT_EQ(hashMap.getElementIndex(ids[2]), QPI::NULL_INDEX); - EXPECT_TRUE(hashMap.contains(ids[0])); - EXPECT_TRUE(hashMap.contains(ids[1])); - EXPECT_FALSE(hashMap.contains(ids[2])); - - // Try to remove key not contained in the hash map. - returnedIndex = hashMap.removeByKey(TestData::GetKeyNotInTestPairs()); - EXPECT_EQ(returnedIndex, QPI::NULL_INDEX); - EXPECT_EQ(hashMap.population(), 2); - - // Remove by key that is contained in the hash map. - returnedIndex = hashMap.removeByKey(ids[0]); - EXPECT_NE(returnedIndex, QPI::NULL_INDEX); - EXPECT_EQ(hashMap.population(), 1); - EXPECT_EQ(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); - EXPECT_NE(hashMap.getElementIndex(ids[1]), QPI::NULL_INDEX); - EXPECT_EQ(hashMap.getElementIndex(ids[2]), QPI::NULL_INDEX); - EXPECT_FALSE(hashMap.contains(ids[0])); - EXPECT_TRUE(hashMap.contains(ids[1])); - EXPECT_FALSE(hashMap.contains(ids[2])); -} - -TYPED_TEST_P(QPIHashMapTest, TestRemoveReuse) -{ - constexpr QPI::uint64 capacity = 4; - QPI::HashMap hashMap; - hashMap.reset(); - - typedef HashMapTestData TestData; - - const auto keyValuePairs = TestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - - // add and remove same item multiple times for testing simple case of reusing slot - for (int i = 0; i < capacity; ++i) - { - for (int j = 0; j <= i; ++j) - { - QPI::sint64 elementIndex = hashMap.set(ids[j], values[j]); - EXPECT_NE(elementIndex, QPI::NULL_INDEX); - EXPECT_FALSE(hashMap.isEmptySlot(elementIndex)); - EXPECT_EQ(hashMap.key(elementIndex), ids[j]); - EXPECT_EQ(hashMap.value(elementIndex), values[j]); - EXPECT_EQ(hashMap.population(), 1); - - hashMap.removeByIndex(elementIndex); - EXPECT_TRUE(hashMap.isEmptySlot(elementIndex)); - EXPECT_EQ(hashMap.population(), 0); - } - } - - // fill to full capacity - for (int i = capacity - 1; i >= 0; --i) - { - QPI::sint64 elementIndex = hashMap.set(ids[i], values[i]); - EXPECT_NE(elementIndex, QPI::NULL_INDEX); - EXPECT_FALSE(hashMap.isEmptySlot(elementIndex)); - EXPECT_EQ(hashMap.key(elementIndex), ids[i]); - EXPECT_EQ(hashMap.value(elementIndex), values[i]); - EXPECT_EQ(hashMap.population(), capacity - i); - } - - // adding another item fails - EXPECT_EQ(hashMap.set(TestData::GetKeyNotInTestPairs(), TestData::GetValueNotInTestPairs()), QPI::NULL_INDEX); - - // mark all items for removal - for (int i = 0; i < capacity; ++i) - { - EXPECT_NE(hashMap.removeByKey(ids[i]), QPI::NULL_INDEX); - } - EXPECT_EQ(hashMap.population(), 0); - - // lookup of non-existing key will now need to iterate through the whole hash map until next cleanup - EXPECT_FALSE(hashMap.contains(ids[0])); - - // for adding an item, set() also needs to go through the whole hash map until next cleanup - EXPECT_NE(hashMap.set(ids[0], values[0]), QPI::NULL_INDEX); - EXPECT_EQ(hashMap.population(), 1); -} - -TYPED_TEST_P(QPIHashMapTest, TestCleanup) -{ - constexpr QPI::uint64 capacity = 4; - QPI::HashMap hashMap; - - commonBuffers.init(1, 2 * sizeof(hashMap)); - - std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - QPI::sint64 returnedIndex; - - for (int i = 0; i < 4; ++i) - { - hashMap.set(ids[i], values[i]); - } - - // This will mark for removal - hashMap.removeByKey(ids[3]); - EXPECT_EQ(hashMap.population(), 3); - - // Slots marked for removal will be reused, but performance may suffer with increasing number of removes without - // calling cleanup - EXPECT_NE(hashMap.set(ids[3], values[3]), QPI::NULL_INDEX); - EXPECT_EQ(hashMap.population(), 4); - - // Remove again - hashMap.removeByKey(ids[3]); - EXPECT_EQ(hashMap.population(), 3); - - // Cleanup will properly remove the element marked for removal. - hashMap.cleanup(); - EXPECT_EQ(hashMap.population(), 3); - - EXPECT_NE(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); - EXPECT_NE(hashMap.getElementIndex(ids[1]), QPI::NULL_INDEX); - EXPECT_NE(hashMap.getElementIndex(ids[2]), QPI::NULL_INDEX); - EXPECT_EQ(hashMap.getElementIndex(ids[3]), QPI::NULL_INDEX); - - // Regular set() without reusing slot of element marked for removal (after cleanup, faster on average) - returnedIndex = hashMap.set(ids[3], values[3]); - EXPECT_NE(returnedIndex, QPI::NULL_INDEX); - EXPECT_EQ(hashMap.population(), 4); - - commonBuffers.deinit(); -} - -TYPED_TEST_P(QPIHashMapTest, TestCleanupPerformanceShortcuts) -{ - constexpr QPI::uint64 capacity = 4; - QPI::HashMap hashMap; - - std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - - for (int i = 0; i < 4; ++i) - { - hashMap.set(ids[i], values[i]); - } - - // Test cleanup when no elements have been marked for removal. - hashMap.cleanup(); - - for (int i = 0; i < 4; ++i) - { - hashMap.removeByKey(ids[i]); - } - EXPECT_EQ(hashMap.population(), 0); - - // Test cleanup when all elements have been marked for removal. - hashMap.cleanup(); -} - -// This test is not type-parameterized because QPI::id is the only type where we can easily create different keys with the same hashes. -TEST(NonTypedQPIHashMapTest, TestCleanupLargeMapSameHashes) -{ - constexpr QPI::uint64 capacity = 64; - QPI::HashMap hashMap; - - commonBuffers.init(1, 2 * sizeof(hashMap)); - - for (QPI::uint64 i = 0; i < 64; ++i) - { - // Add 64 elements with different keys but same hash. - // The hash for QPI::id is the first 8 bytes. - hashMap.set({ 3478, i, i + 1, i + 2 }, int(i * 2 + 7)); - } - hashMap.removeByKey({ 3478, 63, 64, 65 }); - - // Cleanup will have to iterate through the whole map to find an empty slot for the last element. - hashMap.cleanup(); - - commonBuffers.deinit(); -} - -TYPED_TEST_P(QPIHashMapTest, TestReplace) -{ - constexpr QPI::uint64 capacity = 8; - QPI::HashMap hashMap; - - typedef HashMapTestData TestData; - - std::array keyValuePairs = TestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - QPI::sint64 returnedIndex; - - for (int i = 0; i < 3; ++i) - { - returnedIndex = hashMap.set(ids[i], values[i]); - } - EXPECT_EQ(hashMap.population(), 3); - - typename TypeParam::first_type newKey = TestData::GetKeyNotInTestPairs(); - typename TypeParam::second_type newValue = TestData::GetValueNotInTestPairs(); - - EXPECT_FALSE(hashMap.replace(newKey, newValue)); - - EXPECT_EQ(hashMap.population(), 3); - EXPECT_EQ(hashMap.getElementIndex(newKey), QPI::NULL_INDEX); - - EXPECT_TRUE(hashMap.replace(ids[1], newValue)); - - EXPECT_EQ(hashMap.population(), 3); - typename TypeParam::second_type valueRead = {}; - EXPECT_TRUE(hashMap.get(ids[1], valueRead)); - EXPECT_EQ(valueRead, newValue); - EXPECT_TRUE(hashMap.get(ids[0], valueRead)); - EXPECT_EQ(valueRead, values[0]); - EXPECT_TRUE(hashMap.get(ids[2], valueRead)); - EXPECT_EQ(valueRead, values[2]); -} - -TYPED_TEST_P(QPIHashMapTest, TestReset) -{ - constexpr QPI::uint64 capacity = 4; - QPI::HashMap hashMap; - - std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); - auto ids = std::views::keys(keyValuePairs); - auto values = std::views::values(keyValuePairs); - - for (int i = 0; i < 4; ++i) - { - hashMap.set(ids[i], values[i]); - } - - EXPECT_EQ(hashMap.population(), 4); - hashMap.reset(); - EXPECT_EQ(hashMap.population(), 0); -} - -REGISTER_TYPED_TEST_CASE_P(QPIHashMapTest, - TestCreation, - TestGetters, - TestSet, - TestRemove, - TestRemoveReuse, - TestCleanup, - TestCleanupPerformanceShortcuts, - TestReplace, - TestReset -); - -typedef Types, std::pair, std::pair> KeyValueTypesToTest; -INSTANTIATE_TYPED_TEST_CASE_P(TypedQPIHashMapTests, QPIHashMapTest, KeyValueTypesToTest); - -template -void hasSameContent(QPI::HashMap& map, const std::map& referenceMap) -{ - EXPECT_EQ(map.population(), referenceMap.size()); - for (const auto& item : referenceMap) - { - EXPECT_TRUE(map.contains(item.first)); - ValueT value; - EXPECT_TRUE(map.get(item.first, value)); - EXPECT_EQ(value, item.second); - } - for (unsigned int i = 0; i < capacity; ++i) - { - KeyT key = map.key(i); // returns key value at slot i (like 0), even if the value is not contained - if (!map.isEmptySlot(i)) - { - // key is valid, part of map, and exactly at this position - EXPECT_NE(referenceMap.find(key), referenceMap.end()); - EXPECT_EQ(map.getElementIndex(key), i); - } - else - { - // key is invalid and not at this slot - QPI::uint64 elementIndex = map.getElementIndex(key); - EXPECT_NE(elementIndex, i); - if (elementIndex == QPI::NULL_INDEX) - { - // not in map - EXPECT_EQ(referenceMap.find(key), referenceMap.end()); - } - else - { - // in map, but at different position - EXPECT_NE(referenceMap.find(key), referenceMap.end()); - } - } - } - - // test iterator - QPI::sint64 i = map.nextElementIndex(QPI::NULL_INDEX); - unsigned int cnt = 0; - while (i != QPI::NULL_INDEX) - { - cnt++; - EXPECT_FALSE(map.isEmptySlot(i)); - auto it = referenceMap.find(map.key(i)); - EXPECT_NE(it, referenceMap.end()); - EXPECT_EQ(it->second, map.value(i)); - i = map.nextElementIndex(i); - } - EXPECT_EQ(cnt, referenceMap.size()); -} - -template -void cleanupHashMap(QPI::HashMap& map, const std::map& referenceMap) -{ - hasSameContent(map, referenceMap); - map.cleanup(); - //map.cleanupIfNeeded(); - hasSameContent(map, referenceMap); -} - -void getValue(std::mt19937_64& gen64, QPI::id& value) -{ - value.u64._0 = gen64(); - value.u64._1 = gen64(); - value.u64._2 = gen64(); - value.u64._3 = gen64(); -} - -void getValue(std::mt19937_64& gen64, QPI::uint8& value) -{ - value = gen64() & 0xff; -} - -template -void testHashMapPseudoRandom(int seed, int cleanups, int percentAdd, int percentAddSecondHalf = -1) -{ - // add and remove entries with pseudo-random sequence - std::mt19937_64 gen64(seed); - - std::map referenceMap; - QPI::HashMap map; - - commonBuffers.init(1, 2 * sizeof(map)); - - map.reset(); - - // test cleanup of empty collection - cleanupHashMap(map, referenceMap); - - if (seed & 1) - { - // add default value of empty slot to set - referenceMap[map.key(0)] = map.value(0); - EXPECT_NE(map.set(map.key(0), map.value(0)), QPI::NULL_INDEX); - hasSameContent(map, referenceMap); - } - - // Randomly add, remove, and cleanup until 50 cleanups are reached - int cleanupCounter = 0; - while (cleanupCounter < cleanups) - { - int p = gen64() % 100; - - if (p == 0) - { - // cleanup (with 1% probability) - cleanupHashMap(map, referenceMap); - ++cleanupCounter; - - if (cleanupCounter == cleanups / 2 && percentAddSecondHalf >= 0) - percentAdd = percentAddSecondHalf; - } - - if (p < percentAdd) - { - // add to map (more probable than remove) - KeyT key; - ValueT value; - getValue(gen64, key); - getValue(gen64, value); - if (map.set(key, value) != QPI::NULL_INDEX) - referenceMap[key] = value; - hasSameContent(map, referenceMap); - } - else - { - // remove from map - QPI::sint64 removeIdx = gen64() % map.capacity(); - KeyT key = map.key(removeIdx); - if (!map.isEmptySlot(removeIdx)) - { - referenceMap.erase(key); - EXPECT_EQ(map.removeByKey(key), removeIdx); - } - else if (referenceMap.find(key) == referenceMap.end()) - { - EXPECT_EQ(map.removeByKey(key), QPI::NULL_INDEX); - } - hasSameContent(map, referenceMap); - } - - // std::cout << "capacity: " << set.capacity() << ", pupulation:" << set.population() << std::endl; - } - - commonBuffers.deinit(); -} - -TEST(QPIHashMapTest, HashMapPseudoRandom) -{ - constexpr unsigned int numCleanups = 5; - testHashMapPseudoRandom(42, numCleanups, 20); - testHashMapPseudoRandom(1337, numCleanups, 80); - testHashMapPseudoRandom(42, 4 * numCleanups, 30); - testHashMapPseudoRandom(1337, 4 * numCleanups, 50); - testHashMapPseudoRandom(123456789, 4 * numCleanups, 70); - testHashMapPseudoRandom(42, 10 * numCleanups, 30, 70); - testHashMapPseudoRandom(1337, 10 * numCleanups, 50, 10); - testHashMapPseudoRandom(123456789, 10 * numCleanups, 70, 10); - testHashMapPseudoRandom(42 + 1, 6 * numCleanups, 30); - testHashMapPseudoRandom(1337 + 1, 6 * numCleanups, 50); - testHashMapPseudoRandom(123456789 + 1, 6 * numCleanups, 70); - testHashMapPseudoRandom(42 + 2, 10 * numCleanups, 30); - testHashMapPseudoRandom(1337 + 2, 10 * numCleanups, 50); - testHashMapPseudoRandom(123456789 + 2, 10 * numCleanups, 70); - testHashMapPseudoRandom(42, numCleanups, 20); - testHashMapPseudoRandom(42 + 1, 10 * numCleanups, 30, 70); - testHashMapPseudoRandom(1337, 10 * numCleanups, 50, 10); - testHashMapPseudoRandom(123456789, 10 * numCleanups, 70, 10); - testHashMapPseudoRandom(1337 + 1, 6 * numCleanups, 50); - testHashMapPseudoRandom(123456789 + 2, 10 * numCleanups, 70); -} - -TEST(QPIHashMapTest, HashSet) -{ - constexpr QPI::uint64 capacity = 128; - QPI::HashSet hashSet; - commonBuffers.init(1, 2 * sizeof(hashSet)); - EXPECT_EQ(hashSet.capacity(), capacity); - - // Test add() and contains() - for (int i = 0; i < capacity; ++i) - { - const QPI::id newId(i / 3, i + 5, i * 3, i % 10); - EXPECT_EQ(hashSet.population(), i); - auto idx = hashSet.add(newId); - EXPECT_NE(idx, QPI::NULL_INDEX); - EXPECT_EQ(hashSet.key(idx), newId); - EXPECT_FALSE(hashSet.isEmptySlot(idx)); - EXPECT_EQ(idx, hashSet.add(newId)); // adding a second time just returns same index - EXPECT_TRUE(hashSet.contains(newId)); - } - EXPECT_EQ(hashSet.population(), capacity); - EXPECT_FALSE(hashSet.contains(QPI::NULL_ID)); - EXPECT_EQ(hashSet.add(QPI::NULL_ID), QPI::NULL_INDEX); // set is full - EXPECT_FALSE(hashSet.contains(QPI::NULL_ID)); - - // Test remove() - EXPECT_EQ(hashSet.remove(QPI::NULL_ID), QPI::NULL_INDEX); - for (int i = 0; i < capacity; i += 4) - { - const QPI::id theId(i / 3, i + 5, i * 3, i % 10); - EXPECT_EQ(hashSet.population(), capacity - i / 4); - EXPECT_TRUE(hashSet.contains(theId)); - EXPECT_NE(hashSet.remove(theId), QPI::NULL_INDEX); - EXPECT_FALSE(hashSet.contains(theId)); - } - - // Check consistency - for (int i = 0; i < capacity; i++) - { - const QPI::id theId(i / 3, i + 5, i * 3, i % 10); - if (i % 4 == 0) - EXPECT_FALSE(hashSet.contains(theId)); - else - EXPECT_TRUE(hashSet.contains(theId)); - } - - // Check that it works to reuse slots of removed entries - for (int i = 0; i < capacity / 4; ++i) - { - const QPI::id newId(capacity / 4 - 1 - i, 0, 0, 0); - EXPECT_EQ(hashSet.population(), capacity * 3 / 4 + i); - auto idx = hashSet.add(newId); - EXPECT_NE(idx, QPI::NULL_INDEX); - EXPECT_EQ(hashSet.key(idx), newId); - EXPECT_FALSE(hashSet.isEmptySlot(idx)); - EXPECT_EQ(idx, hashSet.add(newId)); // adding a second time just returns same index - EXPECT_TRUE(hashSet.contains(newId)); - } - EXPECT_TRUE(hashSet.contains(QPI::id(0, 0, 0, 0))); - - // Check consistency - for (int i = 0; i < capacity; i++) - { - const QPI::id theId(i / 3, i + 5, i * 3, i % 10); - if (i % 4 == 0) - EXPECT_FALSE(hashSet.contains(theId)); - else - EXPECT_TRUE(hashSet.contains(theId)); - if (i < capacity / 4) - { - const QPI::id theId(capacity / 4 - 1 - i, 0, 0, 0); - EXPECT_TRUE(hashSet.contains(theId)); - } - } - - // Remove entries added first - for (int i = 0; i < capacity; i++) - { - const QPI::id theId(i / 3, i + 5, i * 3, i % 10); - if (i % 4 == 0) - EXPECT_EQ(hashSet.remove(theId), QPI::NULL_INDEX); // already removed before - else - EXPECT_NE(hashSet.remove(theId), QPI::NULL_INDEX); - EXPECT_FALSE(hashSet.contains(theId)); - } - - // Reorganize hash map, speeding up access - hashSet.cleanup(); - - // Check consistency - EXPECT_EQ(hashSet.population(), capacity / 4); - for (int i = 0; i < capacity / 4; ++i) - { - EXPECT_TRUE(hashSet.contains(QPI::id(i, 0, 0, 0))); - } - for (int i = 0; i < capacity; i++) - { - EXPECT_FALSE(hashSet.contains(QPI::id(i / 3, i + 5, i * 3, i % 10))); - } - - hashSet.reset(); - EXPECT_EQ(hashSet.population(), 0); - - commonBuffers.deinit(); -} - -template -void hasSameContent(QPI::HashSet& set, const std::set& referenceSet) -{ - EXPECT_EQ(set.population(), referenceSet.size()); - for (const T& item: referenceSet) - { - EXPECT_TRUE(set.contains(item)); - } - for (unsigned int i = 0; i < capacity; ++i) - { - T key = set.key(i); // returns key value at slot i (like 0), even if the value is not contained - if (!set.isEmptySlot(i)) - { - // key is valid, part of set, and exactly at this position - EXPECT_NE(referenceSet.find(key), referenceSet.end()); - EXPECT_EQ(set.getElementIndex(key), i); - } - else - { - // key is invalid and not at this slot - QPI::uint64 elementIndex = set.getElementIndex(key); - EXPECT_NE(elementIndex, i); - if (elementIndex == QPI::NULL_INDEX) - { - // not in set - EXPECT_EQ(referenceSet.find(key), referenceSet.end()); - } - else - { - // in set, but at different position - EXPECT_NE(referenceSet.find(key), referenceSet.end()); - } - } - } - - // test iterator - QPI::sint64 i = set.nextElementIndex(QPI::NULL_INDEX); - unsigned int cnt = 0; - while (i != QPI::NULL_INDEX) - { - cnt++; - EXPECT_FALSE(set.isEmptySlot(i)); - EXPECT_NE(referenceSet.find(set.key(i)), referenceSet.end()); - i = set.nextElementIndex(i); - } - EXPECT_EQ(cnt, referenceSet.size()); -} - -template -void cleanupHashSet(QPI::HashSet& set, const std::set& referenceSet) -{ - hasSameContent(set, referenceSet); - set.cleanup(); - //set.cleanupIfNeeded(); - hasSameContent(set, referenceSet); -} - -template -void testHashSetPseudoRandom(int seed, int cleanups, int percentAdd, int percentAddSecondHalf = -1) -{ - // add and remove entries with pseudo-random sequence - std::mt19937_64 gen64(seed); - - std::set referenceSet; - QPI::HashSet set; - - commonBuffers.init(1, 2 * sizeof(set)); - - set.reset(); - - // test cleanup of empty collection - cleanupHashSet(set, referenceSet); - - if (seed & 1) - { - // add default value of empty slot to set - referenceSet.insert(set.key(0)); - EXPECT_NE(set.add(set.key(0)), QPI::NULL_INDEX); - hasSameContent(set, referenceSet); - } - - // Randomly add, remove, and cleanup until 50 cleanups are reached - int cleanupCounter = 0; - while (cleanupCounter < cleanups) - { - int p = gen64() % 100; - - if (p == 0) - { - // cleanup (with 1% probability) - cleanupHashSet(set, referenceSet); - ++cleanupCounter; - - if (cleanupCounter == cleanups / 2 && percentAddSecondHalf >= 0) - percentAdd = percentAddSecondHalf; - } - - if (p < percentAdd) - { - // add to set (more probable than remove) - T value; - getValue(gen64, value); - if (set.add(value) != QPI::NULL_INDEX) - referenceSet.insert(value); - hasSameContent(set, referenceSet); - } - else - { - // remove from set - QPI::sint64 removeIdx = gen64() % set.capacity(); - T key = set.key(removeIdx); - if (!set.isEmptySlot(removeIdx)) - { - referenceSet.erase(key); - EXPECT_EQ(set.remove(key), removeIdx); - } - else if (referenceSet.find(key) == referenceSet.end()) - { - EXPECT_EQ(set.remove(key), QPI::NULL_INDEX); - } - hasSameContent(set, referenceSet); - } - - // std::cout << "capacity: " << set.capacity() << ", pupulation:" << set.population() << std::endl; - } - - commonBuffers.deinit(); -} - -TEST(QPIHashMapTest, HashSetPseudoRandom) -{ - constexpr unsigned int numCleanups = 5; - testHashSetPseudoRandom(42, numCleanups, 20); - testHashSetPseudoRandom(1337, numCleanups, 80); - testHashSetPseudoRandom(42, 4 * numCleanups, 30); - testHashSetPseudoRandom(1337, 4 * numCleanups, 50); - testHashSetPseudoRandom(123456789, 4 * numCleanups, 70); - testHashSetPseudoRandom(42, 10 * numCleanups, 30, 70); - testHashSetPseudoRandom(1337, 10 * numCleanups, 50, 10); - testHashSetPseudoRandom(123456789, 10 * numCleanups, 70, 10); - testHashSetPseudoRandom(42 + 1, 6 * numCleanups, 30); - testHashSetPseudoRandom(1337 + 1, 6 * numCleanups, 50); - testHashSetPseudoRandom(123456789 + 1, 6 * numCleanups, 70); - testHashSetPseudoRandom(42 + 2, 10 * numCleanups, 30); - testHashSetPseudoRandom(1337 + 2, 10 * numCleanups, 50); - testHashSetPseudoRandom(123456789 + 2, 10 * numCleanups, 70); - testHashSetPseudoRandom(42, numCleanups, 20); - testHashSetPseudoRandom(42 + 1, 10 * numCleanups, 30, 70); - testHashSetPseudoRandom(1337, 10 * numCleanups, 50, 10); - testHashSetPseudoRandom(123456789, 10 * numCleanups, 70, 10); - testHashSetPseudoRandom(1337 + 1, 6 * numCleanups, 50); - testHashSetPseudoRandom(123456789 + 2, 10 * numCleanups, 70); -} - - - -template -static void perfTestCleanup(int seed) -{ - std::mt19937_64 gen64(seed); - - auto* set = new QPI::HashSet(); - commonBuffers.init(1, sizeof(*set)); - - for (QPI::uint64 i = 1; i <= 100; ++i) - { - QPI::uint64 population = capacity * i / 100; - set->reset(); - - // add random items - auto startTime1 = std::chrono::high_resolution_clock::now(); - for (QPI::uint64 j = 0; j < population; ++j) - { - EXPECT_NE(set->add(QPI::id(gen64(), 0, 0, 0)), QPI::NULL_INDEX); - } - auto addMilliseconds = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime1).count(); - - // measure run-time of cleanup - auto startTime2 = std::chrono::high_resolution_clock::now(); - set->cleanup(); - auto cleanupMilliseconds = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime2).count(); - - std::cout << "HastSet::cleanup() with " << population * 100 / capacity << "% population takes " << cleanupMilliseconds << " ms (adding took " << addMilliseconds * 1000000 / population << " ms/million)" << std::endl; - } - - delete set; - commonBuffers.deinit(); -} - -TEST(QPIHashMapTest, HashSetPerfTest) -{ - // How often should cleanup() be run? - - // for a set of capacities: vary population - // keep sequence of add/remove - - // measure run-time of cleanups -> O(N^2) with N = population - //perfTestCleanup<2 * 1024 * 1024>(42); - //perfTestCleanup<2 * 1024 * 1024>(13); - - // measure lookups/seconds -> O(1) if population is sparse -> O(N) if population is high with N = max population since last cleanup -} diff --git a/test/quorum_value.cpp b/test/quorum_value.cpp deleted file mode 100644 index 3698e5d76..000000000 --- a/test/quorum_value.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#define NO_UEFI - -#include -#include -#include - -#include "gtest/gtest.h" -#include "../src/platform/quorum_value.h" - -TEST(FixedTypeQuorumTest, CalculateAscendingQuorumSimple) -{ - long long values[10] = {10, 5, 8, 3, 7, 1, 9, 2, 6, 4}; - - long long result = calculateAscendingQuorumValue(values, 10); - - // (10 * 2) / 3 = 6, index 6 after sorting = 7 - EXPECT_EQ(result, 7); -} - -TEST(FixedTypeQuorumTest, Calculate676Quorum) -{ - long long values[676]; - for (int i = 0; i < 676; i++) - { - values[i] = i + 1; - } - - long long quorum = calculateAscendingQuorumValue(values, 676); - - // (676 * 2) / 3 = 450, value at index 450 = 451 - EXPECT_EQ(quorum, 451); -} - -TEST(FixedTypeQuorumTest, EmptyArray) -{ - long long values[1] = {0}; - long long result = calculateAscendingQuorumValue(values, 0); - EXPECT_EQ(result, 0); -} - -TEST(FixedTypeQuorumTest, SingleElement) -{ - long long values[1] = {42}; - long long result = calculateAscendingQuorumValue(values, 1); - EXPECT_EQ(result, 42); -} - -TEST(FixedTypeQuorumTest, PercentileDescending) -{ - int values[10] = {10, 5, 8, 3, 7, 1, 9, 2, 6, 4}; - - int result = calculatePercentileValue(values, 10); - - // (10 * 1) / 3 = 3, descending sort, index 3 = 7 - EXPECT_EQ(result, 7); -} - -TEST(FixedTypeQuorumTest, AllZeros) -{ - long long values[676] = {0}; - - long long quorum = calculateAscendingQuorumValue(values, 676); - - // All values are 0, quorum should be 0 - EXPECT_EQ(quorum, 0); -} - -TEST(FixedTypeQuorumTest, MostlyZeros) -{ - long long values[676] = {0}; - - // Set first 225 elements to non-zero values - for (int i = 0; i < 225; i++) - { - values[i] = i + 1; - } - - long long quorum = calculateAscendingQuorumValue(values, 676); - - // After sorting: 0,0,0,...,0 (451 zeros), 1,2,3,...,225 - // Index 450 will be 0 (since 676-225 = 451 zeros, and index 450 < 451) - EXPECT_EQ(quorum, 0); -} - -template -std::vector prepareData(unsigned int seed, unsigned int numElements) -{ - std::mt19937 gen32(seed); - - unsigned int numberOfBlocks = (sizeof(T) + 3) / 4; - - std::vector vec(numElements, 0); - for (unsigned int i = 0; i < numElements; ++i) - { - for (unsigned int b = 0; b < numberOfBlocks; ++b) - { - vec[i] |= (static_cast(gen32()) << (b * 32)); - } - } - - return vec; -} - -template -void testCalculatePercentile(unsigned int seed) -{ - std::vector vec = prepareData(seed, 676); - - std::vector referenceVec = vec; - std::sort(referenceVec.begin(), referenceVec.end()); - - T result = calculatePercentileValue(vec.data(), static_cast(vec.size())); - - // Calculate expected index: (676 * 2) / 3 = 450 - unsigned int expectedIndex = (676 * 2) / 3; - EXPECT_EQ(result, referenceVec[expectedIndex]); -} - -template -class QuorumValueTest : public testing::Test {}; - -using testing::Types; - -TYPED_TEST_CASE_P(QuorumValueTest); - -TYPED_TEST_P(QuorumValueTest, CalculatePercentile) -{ - unsigned int metaSeed = 98765; - std::mt19937 gen32(metaSeed); - - for (unsigned int t = 0; t < 10; ++t) - testCalculatePercentile(gen32()); -} - -REGISTER_TYPED_TEST_CASE_P(QuorumValueTest, - CalculatePercentile -); - -typedef Types TestTypes; -INSTANTIATE_TYPED_TEST_CASE_P(TypeParamQuorumValueTests, QuorumValueTest, TestTypes); diff --git a/test/revenue.cpp b/test/revenue.cpp deleted file mode 100644 index 48793d6fa..000000000 --- a/test/revenue.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#pragma once - -#define NO_UEFI - -#include "gtest/gtest.h" - -#include "../src/revenue.h" - -#include - -std::string TEST_DIR = "data/"; -std::vector REVENUE_FILES = { -"custom_revenue.eoe" -}; - -unsigned int random(const unsigned int range) -{ - unsigned int value; - _rdrand32_step(&value); - return value % range; -} - -TEST(TestCoreRevenue, GetQuorumScore) -{ - unsigned long long data[NUMBER_OF_COMPUTORS]; - - // Zeros data - setMem(data, sizeof(data), 0); - unsigned long long quorumScore = getQuorumScore(data); - EXPECT_EQ(quorumScore, 1); - - // Constant data - const unsigned long long CONSTANT_VALUE = random(0xFFFFFFFF); - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - data[i] = CONSTANT_VALUE; - } - quorumScore = getQuorumScore(data); - EXPECT_EQ(quorumScore, CONSTANT_VALUE); - - // Generate sort - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - data[i] = random(0xFFFFFFFF); - } - quorumScore = getQuorumScore(data); - std::sort(data, data + NUMBER_OF_COMPUTORS, std::greater()); - EXPECT_EQ(quorumScore, data[QUORUM - 1]); -} - -TEST(TestCoreRevenue, ComputeRevFactor) -{ - const unsigned long long scaleFactor = 1024; - unsigned long long data[NUMBER_OF_COMPUTORS]; - unsigned long long dataFactor[NUMBER_OF_COMPUTORS]; - - // All zeros. No reveue for alls - setMem(data, sizeof(data), 0); - computeRevFactor(data, scaleFactor, dataFactor); - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_EQ(dataFactor[i], 0); - } - - // Constant values. Max revenue for all - const unsigned long long CONSTANT_VALUE = random(0xFFFFFFFF); - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - data[i] = CONSTANT_VALUE; - } - computeRevFactor(data, scaleFactor, dataFactor); - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_EQ(dataFactor[i], scaleFactor); - } - - // General case - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - data[i] = random(0xFFFFFFFF); - } - computeRevFactor(data, scaleFactor, dataFactor); - // Data in range [0, scaleFactor] and quorum get max scale factor - unsigned long long quorumValue = getQuorumScore(data); - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_LE(dataFactor[i], scaleFactor); - EXPECT_GE(dataFactor[i], 0); - if (data[i] >= quorumValue) - { - EXPECT_EQ(dataFactor[i], scaleFactor); - } - else - { - EXPECT_LT(dataFactor[i], scaleFactor); - } - } - - // One zeros case - unsigned int zeroPositions = random(NUMBER_OF_COMPUTORS); - data[zeroPositions] = 0; - computeRevFactor(data, scaleFactor, dataFactor); - EXPECT_EQ(dataFactor[zeroPositions], 0); - - // Very small data - unsigned int smallPosition = random(NUMBER_OF_COMPUTORS); - data[smallPosition] = 1; - computeRevFactor(data, scaleFactor, dataFactor); - EXPECT_EQ(dataFactor[smallPosition], 0); -} - -TEST(TestCoreRevenue, GeneralTest) -{ - unsigned long long tx[NUMBER_OF_COMPUTORS]; - unsigned long long votes[NUMBER_OF_COMPUTORS]; - unsigned long long customMiningShares[NUMBER_OF_COMPUTORS]; - long long revenuePerComputors[NUMBER_OF_COMPUTORS]; - - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - tx[i] = random(0xFFFFFFFF); - } - computeRevFactor(tx, gTxScoreScalingThreshold, gTxScoreFactor); - - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - votes[i] = random(0xFFFFFFFF); - } - computeRevFactor(votes, gVoteScoreScalingThreshold, gVoteScoreFactor); - - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - customMiningShares[i] = random(0xFFFFFFFF); - } - computeRevFactor(customMiningShares, gCustomMiningScoreScalingThreshold, gCustomMiningScoreFactor); - - long long arbitratorRevenue = ISSUANCE_RATE; - constexpr long long issuancePerComputor = ISSUANCE_RATE / NUMBER_OF_COMPUTORS; - for (unsigned int computorIndex = 0; computorIndex < NUMBER_OF_COMPUTORS; computorIndex++) - { - // Compute initial computor revenue, reducing arbitrator revenue - unsigned long long combinedScoreFactor = gTxScoreFactor[computorIndex] * gVoteScoreFactor[computorIndex] * gCustomMiningScoreFactor[computorIndex]; - long long revenue = (long long)(combinedScoreFactor * issuancePerComputor / gTxScoreScalingThreshold / gVoteScoreScalingThreshold / gCustomMiningScoreScalingThreshold); - revenuePerComputors[computorIndex] = revenue; - } - - unsigned long long txQuorumScore = getQuorumScore(tx); - unsigned long long voteQuorumScore = getQuorumScore(votes); - unsigned long long customMiningQuorumScore = getQuorumScore(customMiningShares); - - // Checking score factor - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_LE(revenuePerComputors[i], arbitratorRevenue); - EXPECT_GE(revenuePerComputors[i], 0); - } -} - -// Simulate the revenue fomula from real data -TEST(TestCoreRevenue, ReadFile) -{ - unsigned long long tx[NUMBER_OF_COMPUTORS]; - unsigned long long votes[NUMBER_OF_COMPUTORS]; - unsigned long long customMining[NUMBER_OF_COMPUTORS]; - long long revenue[NUMBER_OF_COMPUTORS]; - constexpr long long issuancePerComputor = ISSUANCE_RATE / NUMBER_OF_COMPUTORS; - - for (size_t i = 0; i < REVENUE_FILES.size(); ++i) - { - // Open input file in binary mode - std::string input = TEST_DIR + REVENUE_FILES[i]; - std::ifstream infile(input, std::ios::binary); - if (!infile) - { - std::cerr << "Error opening file: " << input << "\n"; - std::exit(EXIT_FAILURE); - } - - // Read transaction, vote and custom mining share - infile.read(reinterpret_cast(&tx), sizeof(tx)); - if (!infile) - { - std::cerr << "Error reading tx score from file.\n"; - std::exit(EXIT_FAILURE); - } - - infile.read(reinterpret_cast(&votes), sizeof(votes)); - if (!infile) - { - std::cerr << "Error reading votes score from file.\n"; - std::exit(EXIT_FAILURE); - } - - infile.read(reinterpret_cast(&customMining), sizeof(customMining)); - if (!infile) - { - std::cerr << "Error reading custom mining score from file.\n"; - std::exit(EXIT_FAILURE); - } - - infile.close(); - - // Start to compute and write out data - computeRevenue(tx, votes, customMining, revenue); - - // Write the data out for investigation - // Open output file in text mode - std::string output = input + ".csv"; - std::ofstream outfile(output); - if (!outfile) - { - std::cerr << "Error opening output file: " << output << "\n"; - std::exit(EXIT_FAILURE); - } - - // Write CSV header - outfile << "Index,txScore,voteScore,customMiningScore,txScoreFactor,voteScoreFactor,customMiningScoreFactor,revenue,percentage\n"; - - // Write the content - for (int k = 0; k < NUMBER_OF_COMPUTORS; k++) - { - outfile << k << "," - << tx[k] << "," - << votes[k] << "," - << customMining[k] << "," - << gRevenueComponents.txScoreFactor[k] << "," - << gRevenueComponents.voteScoreFactor[k] << "," - << gRevenueComponents.customMiningScoreFactor[k] << "," - << revenue[k] << "," - << (double)revenue[k] * 100 / issuancePerComputor - << "\n"; - } - outfile.close(); - } -} diff --git a/test/score.cpp b/test/score.cpp deleted file mode 100644 index 58d1dba26..000000000 --- a/test/score.cpp +++ /dev/null @@ -1,910 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#define ENABLE_PROFILING 0 - -// current optimized implementation -#include "../src/public_settings.h" -#include "../src/mining/score_engine.h" -#include "../src/score.h" - -// reference implementation -#include "score_reference.h" - -// params settting -#include "score_params.h" - -#include "utils.h" - -#include -#include -#include -#include - -using namespace score_params; -using namespace test_utils; - -// When algorithm change, belows need to do -// - score_params.h: adjust the number of ParamType and change the config of kSettings -// - Modify the score reference run with new setting -// - Need to verify about the idex of setting in the template of score/score_reference class -// - Re-run the test_score_generation for generating 2 csv files, scores and samples -// - Copy the files into test/data -// - Rename COMMON_TEST_SAMPLES_FILE_NAME and COMMON_TEST_SCORES_FILE_NAME if neccessary - - -static const std::string COMMON_TEST_SAMPLES_FILE_NAME = "data/samples_20240815.csv"; -static const std::string COMMON_TEST_SCORES_HYPERIDENTITY_FILE_NAME = "data/scores_hyperidentity.csv"; -static const std::string COMMON_TEST_SCORES_ADDITION_FILE_NAME = "data/scores_addition.csv"; - -static constexpr bool PRINT_DETAILED_INFO = false; -// Variable control the algo tested -// AllAlgo: run the score that alg is retermined by nonce -static std::vector> TEST_ALGOS = { - {score_engine::AlgoType::HyperIdentity, "HyperIdentity"}, - {score_engine::AlgoType::Addition, "Addition"}, - {score_engine::AlgoType::MaxAlgoCount, "Mixed"}, -}; - -// set to 0 for run all available samples -// For profiling enable, run all available samples -static constexpr unsigned long long COMMON_TEST_NUMBER_OF_SAMPLES = 16; -static constexpr unsigned long long PROFILING_NUMBER_OF_SAMPLES = 48; - - -// set 0 for run maximum number of threads of the computer. -// For profiling enable, set it equal to deployment setting -static constexpr int MAX_NUMBER_OF_THREADS = 0; -static constexpr int MAX_NUMBER_OF_PROFILING_THREADS = 12; -static bool gCompareReference = false; - -// Only run on specific index of samples and setting -std::vector filteredSamples;// = { 0 }; -std::vector filteredSettings;// = { 0 }; - -std::vector> gScoresHyperIdentityGroundTruth; -std::vector> gScoresHyperAdditionGroundTruth; -std::map gScoreProcessingTime; -std::map gScoreHyperIdentityIndexMap; -std::map gScoreAdditionIndexMap; - -struct ScoreResult -{ - unsigned int score; - long long elapsedMs; -}; - -template class ScoreType, typename CurrentConfig> -ScoreResult computeScore( - const unsigned char* miningSeed, - const unsigned char* publicKey, - const unsigned char* nonce, - score_engine::AlgoType algo, - unsigned char* externalRandomPool) -{ - std::unique_ptr> scoreEngine = - std::make_unique>(); - - scoreEngine->initMemory(); - if (nullptr == externalRandomPool) - { - scoreEngine->initMiningData(miningSeed); - } - - unsigned int scoreValue = 0; - - auto t0 = std::chrono::high_resolution_clock::now(); - - switch (algo) - { - case score_engine::AlgoType::HyperIdentity: - scoreValue = scoreEngine->computeHyperIdentityScore(publicKey, nonce, externalRandomPool); - break; - case score_engine::AlgoType::Addition: - scoreValue = scoreEngine->computeAdditionScore(publicKey, nonce, externalRandomPool); - break; - default: - scoreValue = scoreEngine->computeScore(publicKey, nonce, externalRandomPool); - break; - } - - auto t1 = std::chrono::high_resolution_clock::now(); - auto elapsedMs = std::chrono::duration_cast(t1 - t0).count(); - - return { scoreValue, elapsedMs }; - -} - -void processQubicScore(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, int sampleIndex) -{ - // Core use the external random pool - std::unique_ptr> pScore = std::make_unique>(); - pScore->initMemory(); - pScore->initMiningData(miningSeed); - - unsigned int scoreValue = (*pScore)(0, publicKey, miningSeed, nonce); - - // Determine which algo to use to get the correct ground truth - int gtIndex = -1; - unsigned int gtScore = 0; - score_engine::AlgoType effectiveAlgo = (nonce[0] & 1) == 0 ? score_engine::AlgoType::HyperIdentity : score_engine::AlgoType::Addition; - - std::vector state(score_engine::STATE_SIZE); - std::vector externalPoolVec(score_engine::POOL_VEC_PADDING_SIZE); - score_engine::generateRandom2Pool(miningSeed, state.data(), externalPoolVec.data()); - ScoreResult scoreResult = computeScore, - score_engine::AdditionParams< - ADDITION_NUMBER_OF_INPUT_NEURONS, - ADDITION_NUMBER_OF_OUTPUT_NEURONS, - ADDITION_NUMBER_OF_TICKS, - ADDITION_NUMBER_OF_NEIGHBORS, - ADDITION_POPULATION_THRESHOLD, - ADDITION_NUMBER_OF_MUTATIONS, - ADDITION_SOLUTION_THRESHOLD_DEFAULT> - >>( - miningSeed, publicKey, nonce, effectiveAlgo, externalPoolVec.data()); - unsigned int refScore = scoreResult.score; - -#pragma omp critical - { - EXPECT_EQ(refScore, scoreValue); - } -} - - -template -static void processElement(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, - int sampleIndex, score_engine::AlgoType algo) -{ - // Skip filter settings - if (!filteredSettings.empty() - && std::find(filteredSettings.begin(), filteredSettings.end(), i) == filteredSettings.end()) - { - return; - } - - // Get the current config - using CurrentConfig = std::tuple_element_t; - - // Core use the external random pool - std::vector state(score_engine::STATE_SIZE); - std::vector externalPoolVec(score_engine::POOL_VEC_PADDING_SIZE); - score_engine::generateRandom2Pool(miningSeed, state.data(), externalPoolVec.data()); - ScoreResult scoreResult = computeScore( - miningSeed, publicKey, nonce, algo, externalPoolVec.data()); - unsigned int scoreValue = scoreResult.score; - - // Determine which algo to use to get the correct ground truth - int gtIndex = -1; - unsigned int gtScore = 0; - score_engine::AlgoType effectiveAlgo = algo; - if (algo != score_engine::AlgoType::HyperIdentity && algo != score_engine::AlgoType::Addition) - { - // Default/Mixed mode: select based on nonce - effectiveAlgo = (nonce[0] & 1) == 0 ? score_engine::AlgoType::HyperIdentity : score_engine::AlgoType::Addition; - } - if (effectiveAlgo == score_engine::AlgoType::HyperIdentity) - { - if (gScoreHyperIdentityIndexMap.count(i) > 0) - { - gtIndex = gScoreHyperIdentityIndexMap[i]; - gtScore = gScoresHyperIdentityGroundTruth[sampleIndex][gtIndex]; - } - } - else if (effectiveAlgo == score_engine::AlgoType::Addition) - { - if (gScoreAdditionIndexMap.count(i) > 0) - { - gtIndex = gScoreAdditionIndexMap[i]; - gtScore = gScoresHyperAdditionGroundTruth[sampleIndex][gtIndex]; - } - } - - unsigned int refScore = 0; - if (gCompareReference) - { - // Reference score always re-compute the pools - ScoreResult scoreRefResult = computeScore( - miningSeed, publicKey, nonce, algo, nullptr); - refScore = scoreRefResult.score; - } - -#pragma omp critical - if (gCompareReference) - { - EXPECT_EQ(refScore, scoreValue); - } - else - { - if (PRINT_DETAILED_INFO || gtIndex < 0 || (scoreValue != gtScore)) - { - std::cout << " score " << scoreValue; - if (gtIndex >= 0) - { - std::cout << " vs gt " << gtScore << std::endl; - } - else // No mapping from ground truth - { - std::cout << " vs gt NA" << std::endl; - } - } - { - EXPECT_GE(gtIndex, 0); - if (gtIndex >= 0) - { - EXPECT_EQ(gtScore, scoreValue); - } - } - } -} - -// Recursive template to process each element in scoreSettings -template -static void processElementPerf(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, - int sampleIndex, score_engine::AlgoType algo) -{ - using CurrentConfig = std::tuple_element_t; - - std::vector state(score_engine::STATE_SIZE); - std::vector externalPoolVec(score_engine::POOL_VEC_PADDING_SIZE); - score_engine::generateRandom2Pool(miningSeed, state.data(), externalPoolVec.data()); - ScoreResult scoreRefResult = computeScore( - miningSeed, publicKey, nonce, algo, externalPoolVec.data()); - auto elapsed = scoreRefResult.elapsedMs; -#pragma omp critical - { - if (gScoreProcessingTime.count(i) == 0) - { - gScoreProcessingTime[i] = elapsed; - } - else - { - gScoreProcessingTime[i] += elapsed; - } - } -} - -// Main processing function -template -static void processHelper(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, - int sampleIndex, score_engine::AlgoType algo, std::index_sequence) -{ - if constexpr (profiling) - { - (processElementPerf(miningSeed, publicKey, nonce, sampleIndex, algo), ...); - } - else - { - (processElement(miningSeed, publicKey, nonce, sampleIndex, algo), ...); - } - -} - -// Recursive template to process each element in scoreSettings -template -static void process(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, - int sampleIndex = 0, score_engine::AlgoType algo = score_engine::AlgoType::AllAlgo) -{ - processHelper(miningSeed, publicKey, nonce, sampleIndex, algo, std::make_index_sequence{}); -} - -template -void writeParams(std::ostream& os, const std::string& sep = ",") -{ - // Because currently 2 params set shared the same things, incase of new algo have different params - // need to make a separate check - if constexpr (P::algoType == score_engine::AlgoType::HyperIdentity) - { - os << "InputNeurons: " << P::numberOfInputNeurons << sep - << " OutputNeurons: " << P::numberOfOutputNeurons << sep - << " Ticks: " << P::numberOfTicks << sep - << " Neighbor: " << P::numberOfNeighbors << sep - << " Population: " << P::populationThreshold << sep - << " Mutate: " << P::numberOfMutations << sep - << " Threshold: " << P::solutionThreshold; - } - else if constexpr (P::algoType == score_engine::AlgoType::Addition) - { - os << "InputNeurons: " << P::numberOfInputNeurons << sep - << " OutputNeurons: " << P::numberOfOutputNeurons << sep - << " Ticks: " << P::numberOfTicks << sep - << " Neighbor: " << P::numberOfNeighbors << sep - << " Population: " << P::populationThreshold << sep - << " Mutate: " << P::numberOfMutations << sep - << " Threshold: " << P::solutionThreshold; - } - else - { - std::cerr << "UNKNOWN ALGO !" << std::endl; - } -} - -template -void printConfigProfileImpl(score_engine::AlgoType algo) -{ - using CurrentConfig = std::tuple_element_t; - - if (algo & score_engine::AlgoType::HyperIdentity) - { - writeParams(std::cout); - } - else if (algo & score_engine::AlgoType::Addition) - { - writeParams(std::cout); - } -} - -template -void printConfigImpl(score_engine::AlgoType algo) -{ - using CurrentConfig = std::tuple_element_t; - - if (algo & score_engine::AlgoType::HyperIdentity) - { - writeParams(std::cout); - } - else if (algo & score_engine::AlgoType::Addition) - { - writeParams(std::cout); - } -} - -template -void printConfigByIndex(std::size_t index, score_engine::AlgoType algo, std::index_sequence) -{ - if constexpr (profiling) - { - ((Is == index ? (printConfigProfileImpl(algo), 0) : 0), ...); - } - else - { - ((Is == index ? (printConfigImpl(algo), 0) : 0), ...); - } -} - -template -void printConfig(std::size_t index, score_engine::AlgoType algo) -{ - if constexpr (profiling) - { - printConfigByIndex(index, algo, std::make_index_sequence>{}); - } - else - { - printConfigByIndex(index, algo, std::make_index_sequence>{}); - } -} - -template -bool compareParams(const std::vector& values) -{ - // Because currently 2 params set shared the same things, incase of new algo have different params - // need to make a separate check - if constexpr (P::algoType == score_engine::AlgoType::HyperIdentity) - { - return values[0] == P::numberOfInputNeurons - && values[1] == P::numberOfOutputNeurons - && values[2] == P::numberOfTicks - && values[3] == P::numberOfNeighbors - && values[4] == P::populationThreshold - && values[5] == P::numberOfMutations - && values[6] == P::solutionThreshold; - } - else if constexpr (P::algoType == score_engine::AlgoType::Addition) - { - return values[0] == P::numberOfInputNeurons - && values[1] == P::numberOfOutputNeurons - && values[2] == P::numberOfTicks - && values[3] == P::numberOfNeighbors - && values[4] == P::populationThreshold - && values[5] == P::numberOfMutations - && values[6] == P::solutionThreshold; - } - return false; -} - -template -bool checkConfig(const std::vector& values, score_engine::AlgoType algo) -{ - using CurrentConfig = std::tuple_element_t; - switch (algo) - { - case score_engine::AlgoType::HyperIdentity: - // HyperIdentity - return compareParams(values); - break; - case score_engine::AlgoType::Addition: - // Addition - return compareParams(values); - break; - default: - return false; - break; - } -} - -template -int findMatchingConfigImpl(const std::vector& values, score_engine::AlgoType algo, std::index_sequence) -{ - int result = -1; - ((checkConfig(values, algo) ? (result = Is, false) : true) && ...); - return result; -} - -int findMatchingConfig(const std::vector& values, score_engine::AlgoType algo) -{ - return findMatchingConfigImpl(values, algo, std::make_index_sequence>{}); -} - -void loadSamples( - const std::vector>& sampleString, - unsigned long long numberOfSamples, - unsigned long long numberOfSamplesReadFromFile, - std::vector& miningSeeds, - std::vector& publicKeys, - std::vector& nonces) -{ - miningSeeds.resize(numberOfSamples); - publicKeys.resize(numberOfSamples); - nonces.resize(numberOfSamples); - - // Reading the input samples - for (unsigned long long i = 0; i < numberOfSamples; ++i) - { - if (i < numberOfSamplesReadFromFile) - { - miningSeeds[i] = hexTo32Bytes(sampleString[i][0], 32); - publicKeys[i] = hexTo32Bytes(sampleString[i][1], 32); - nonces[i] = hexTo32Bytes(sampleString[i][2], 32); - } - else // Samples from files are not enough, randomly generate more - { - _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[0]); - _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[8]); - _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[16]); - _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[24]); - - _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[0]); - _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[8]); - _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[16]); - _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[24]); - - _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[0]); - _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[8]); - _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[16]); - _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[24]); - - } - } -} - -template -void runTest( - const std::string& testName, - const std::vector& samples, - int numberOfThreads, - ProcessFunc processFunc) -{ - std::cout << "Test " << testName << " ..." << std::endl; - int proccessedSamples = 0; -#pragma omp parallel for num_threads(numberOfThreads) - for (int i = 0; i < static_cast(samples.size()); ++i) - { - int index = samples[i]; - processFunc(index); -#pragma omp critical - proccessedSamples++; - std::cout << "\r-Processed: " << proccessedSamples << " / " << static_cast(samples.size()) << " " << std::flush; - } - std::cout << std::endl; -} - -static std::vector> readSampleAsStr(const std::string& filename) -{ - std::vector> sampleString = readCSV(filename); - - // Remove header - sampleString.erase(sampleString.begin()); - - return sampleString; -} - -void runCommonTests() -{ - -#if defined (__AVX512F__) && !GENERIC_K12 - initAVX512KangarooTwelveConstants(); -#endif - constexpr unsigned long long numberOfGeneratedSetting = CONFIG_COUNT; - - // Read the parameters and results - auto sampleString = readSampleAsStr(COMMON_TEST_SAMPLES_FILE_NAME); - - // Convert the raw string and do the data verification - unsigned long long numberOfSamplesReadFromFile = sampleString.size(); - unsigned long long numberOfSamples = numberOfSamplesReadFromFile; - unsigned long long requestedNumberOfSamples = COMMON_TEST_NUMBER_OF_SAMPLES; - - if (requestedNumberOfSamples > 0) - { - std::cout << "Request testing with " << requestedNumberOfSamples << " samples." << std::endl; - - numberOfSamples = std::min(requestedNumberOfSamples, numberOfSamples); - if (requestedNumberOfSamples <= numberOfSamples) - { - numberOfSamples = requestedNumberOfSamples; - } - else // Request number of samples greater than existed. Only valid for reference score validation only - { - if (gCompareReference) - { - numberOfSamples = requestedNumberOfSamples; - std::cout << "Refenrece comparison mode: " << numberOfSamples << " samples are read from file for comparision." - << "Remained are generated randomly." - << std::endl; - } - else - { - std::cout << "Only " << numberOfSamples << " samples can be read from file for comparison" << std::endl; - } - } - } - - // Reading the input samples - std::vector miningSeeds(numberOfSamples); - std::vector publicKeys(numberOfSamples); - std::vector nonces(numberOfSamples); - loadSamples(sampleString, numberOfSamples, numberOfSamplesReadFromFile, miningSeeds, publicKeys, nonces); - - // Reading the header of score and verification - if (!gCompareReference) - { - auto scoresStringHyperidentity = readCSV(COMMON_TEST_SCORES_HYPERIDENTITY_FILE_NAME); - auto scoresStringAddition = readCSV(COMMON_TEST_SCORES_ADDITION_FILE_NAME); - - if (scoresStringAddition.size() == 0 || scoresStringAddition.size() == 0) - { - ASSERT_GT(scoresStringHyperidentity.size(), 0); - ASSERT_GT(scoresStringAddition.size(), 0); - std::cout << "Number of Hyperidentity and Addition settings must greater than zero." << std::endl; - return; - } - if (scoresStringAddition.size() != scoresStringAddition.size()) - { - ASSERT_EQ(scoresStringHyperidentity.size(), scoresStringAddition.size()); - std::cout << "Number of Hyperidentity and Addition settings must be equal." << std::endl; - return; - } - - // - auto buildIndexMap = []( - std::vector& header, - score_engine::AlgoType algo, - std::map& indexMap) - { - for (int gtIdx = 0; gtIdx < (int)header.size(); ++gtIdx) - { - auto scoresSettingHeader = convertULLFromString(header[gtIdx]); - int foundIndex = findMatchingConfig(scoresSettingHeader, algo); - if (foundIndex >= 0) - { - indexMap[foundIndex] = gtIdx; - } - } - }; - buildIndexMap(scoresStringHyperidentity[0], score_engine::AlgoType::HyperIdentity, gScoreHyperIdentityIndexMap); - buildIndexMap(scoresStringAddition[0], score_engine::AlgoType::Addition, gScoreAdditionIndexMap); - - if (gScoreHyperIdentityIndexMap.size() != gScoreHyperIdentityIndexMap.size()) - { - ASSERT_EQ(gScoreHyperIdentityIndexMap.size(), gScoreHyperIdentityIndexMap.size()); - std::cout << "Number of tested Hyperidentity and Addition must be equal." << std::endl; - return; - } - - std::cout << "Testing " << CONFIG_COUNT << " param combinations on " << scoresStringHyperidentity[0].size() << " Hyperidentity and Addition ground truth settings." << std::endl; - // In case of number of setting is lower than the ground truth. Consider we are in experiement, still run but expect the test failed - if (gScoreHyperIdentityIndexMap.size() < CONFIG_COUNT) - { - std::cout << "WARNING: Number of provided ground truth settings is lower than tested settings. Only test with available ones." - << std::endl; - EXPECT_EQ(gScoreHyperIdentityIndexMap.size(), CONFIG_COUNT); - } - - auto loadGroundTruth = []( - const std::vector>& scoresString, - std::vector>& groundTruth, - int numberOfSamples) -> int - { - int numberOfGTSetting = (int)scoresString.size() - 1; - numberOfSamples = std::min(numberOfSamples, numberOfGTSetting); - groundTruth.resize(numberOfSamples); - - for (size_t i = 0; i < numberOfSamples; ++i) - { - auto& scoresStr = scoresString[i + 1]; - for (const auto& str : scoresStr) - { - groundTruth[i].push_back(std::stoi(str)); - } - } - - return numberOfSamples; - }; - - int numHI = loadGroundTruth(scoresStringHyperidentity, gScoresHyperIdentityGroundTruth, (int)numberOfSamples); - int numAdd = loadGroundTruth(scoresStringAddition, gScoresHyperAdditionGroundTruth, (int)numberOfSamples); - - std::cout << "There are " << numHI << " Hyperidentity results and " << numAdd << " Addition results." << std::endl; - } - - - // Run the test - unsigned int numberOfThreads = std::thread::hardware_concurrency(); - if (MAX_NUMBER_OF_THREADS > 0) - { - numberOfThreads = numberOfThreads > MAX_NUMBER_OF_THREADS ? MAX_NUMBER_OF_THREADS : numberOfThreads; - } - - if (numberOfThreads > 1) - { - std::cout << "Compare score only. Lauching test with all available " << numberOfThreads << " threads." << std::endl; - } - else - { - std::cout << "Running one sample on one thread for collecting single thread performance." << std::endl; - } - - std::vector samples; - for (int i = 0; i < numberOfSamples; ++i) - { - if (!filteredSamples.empty() - && std::find(filteredSamples.begin(), filteredSamples.end(), i) == filteredSamples.end()) - { - continue; - } - samples.push_back(i); - } - - std::string compTerm = "and compare with groundtruths from file."; - if (gCompareReference) - { - compTerm = "and compare with reference code."; - } - - std::cout << "Processing " << samples.size() << " samples " << compTerm << "..." << std::endl; - - for (const auto& [algoType, algoName] : TEST_ALGOS) - { - runTest(algoName, samples, numberOfThreads, [&](int index) - { - process<0, CONFIG_COUNT>( - miningSeeds[index].m256i_u8, - publicKeys[index].m256i_u8, - nonces[index].m256i_u8, - index, - algoType); - }); - } - - // Test Qubic score vs internal engine (always runs) - runTest("Qubic's score vs internal score engine on active config", samples, numberOfThreads, [&](int index) - { - processQubicScore( - miningSeeds[index].m256i_u8, - publicKeys[index].m256i_u8, - nonces[index].m256i_u8, - index); - }); - -} - -template -void profileAlgo( - score_engine::AlgoType algo, - const std::string& algoName, - const std::vector& samples, - const std::vector& miningSeeds, - const std::vector& publicKeys, - const std::vector& nonces, - const std::vector& filteredSamples, - int numberOfThreads, - unsigned long long numberOfSamples) -{ - std::cout << "Profile " << algoName << " ... " << std::endl; - gScoreProcessingTime.clear(); - - int numSamples = static_cast(samples.size()); - int proccessedSamples = 0; -#pragma omp parallel for num_threads(numberOfThreads) - for (int i = 0; i < numSamples; ++i) - { - int index = samples[i]; - process( - miningSeeds[index].m256i_u8, - publicKeys[index].m256i_u8, - nonces[index].m256i_u8, - index, - algo); -#pragma omp critical - proccessedSamples++; - std::cout << "\r-Processed: " << proccessedSamples << " / " << numSamples << " " << std::flush; - } - std::cout << std::endl; - - std::size_t sampleCount = filteredSamples.empty() ? numberOfSamples : filteredSamples.size(); - for (const auto& [settingIndex, totalTime] : gScoreProcessingTime) - { - unsigned long long avgTime = totalTime / sampleCount; - std::cout << "Avg time [setting " << settingIndex << "]: "; - printConfig(settingIndex, algo); - std::cout << " - " << avgTime << " ms" << std::endl; - } -} - -void runPerformanceTests() -{ -#if defined (__AVX512F__) && !GENERIC_K12 - initAVX512KangarooTwelveConstants(); -#endif - constexpr unsigned long long numberOfGeneratedSetting = PROFILE_CONFIG_COUNT; - - // Read the parameters and results - auto sampleString = readSampleAsStr(COMMON_TEST_SAMPLES_FILE_NAME); - - // Convert the raw string and do the data verification - unsigned long long numberOfSamplesReadFromFile = sampleString.size(); - unsigned long long numberOfSamples = numberOfSamplesReadFromFile; - unsigned long long requestedNumberOfSamples = PROFILING_NUMBER_OF_SAMPLES; - - if (requestedNumberOfSamples > 0) - { - std::cout << "Request testing with " << requestedNumberOfSamples << " samples." << std::endl; - - numberOfSamples = std::min(requestedNumberOfSamples, numberOfSamples); - if (requestedNumberOfSamples <= numberOfSamples) - { - numberOfSamples = requestedNumberOfSamples; - } - } - - // Loading samples - std::vector miningSeeds(numberOfSamples); - std::vector publicKeys(numberOfSamples); - std::vector nonces(numberOfSamples); - loadSamples(sampleString, numberOfSamples, numberOfSamplesReadFromFile, miningSeeds, publicKeys, nonces); - - std::cout << "Profiling " << numberOfGeneratedSetting << " param combinations. " << std::endl; - - // Run the profiling - unsigned int numberOfThreads = std::thread::hardware_concurrency(); - if (MAX_NUMBER_OF_PROFILING_THREADS > 0) - { - numberOfThreads = numberOfThreads > MAX_NUMBER_OF_PROFILING_THREADS ? MAX_NUMBER_OF_PROFILING_THREADS : numberOfThreads; - } - std::cout << "Running " << numberOfThreads << " threads for collecting multiple threads performance" << std::endl; - - std::vector samples; - for (int i = 0; i < numberOfSamples; ++i) - { - if (!filteredSamples.empty() - && std::find(filteredSamples.begin(), filteredSamples.end(), i) == filteredSamples.end()) - { - continue; - } - samples.push_back(i); - } - - std::string compTerm = "for profiling, don't compare any result."; - - std::cout << "Processing " << samples.size() << " samples " << compTerm << "..." << std::endl; - - profileAlgo<1, PROFILE_CONFIG_COUNT>( - score_engine::AlgoType::HyperIdentity, "HyperIdentity", - samples, miningSeeds, publicKeys, nonces, filteredSamples, numberOfThreads, numberOfSamples); - - profileAlgo<1, PROFILE_CONFIG_COUNT>( - score_engine::AlgoType::Addition, "Addition", - samples, miningSeeds, publicKeys, nonces, filteredSamples, numberOfThreads, numberOfSamples); - - profileAlgo<1, PROFILE_CONFIG_COUNT>( - score_engine::AlgoType::MaxAlgoCount, "Mixed", - samples, miningSeeds, publicKeys, nonces, filteredSamples, numberOfThreads, numberOfSamples); - - gProfilingDataCollector.writeToFile(); -} - -#if ENABLE_PROFILING - -TEST(TestQubicScoreFunction, PerformanceTests) -{ - runPerformanceTests(); -} -#endif - -TEST(TestQubicScoreFunction, CommonTests) -{ - runCommonTests(); -} - -#if not ENABLE_PROFILING -TEST(TestQubicScoreFunction, TestDeterministic) -{ - constexpr int NUMBER_OF_THREADS = 4; - constexpr int NUMBER_OF_PHASES = 2; - constexpr int NUMBER_OF_SAMPLES = 4; - - // Read the parameters and results - auto sampleString = readSampleAsStr(COMMON_TEST_SAMPLES_FILE_NAME); - - // Convert the raw string and do the data verification - unsigned long long numberOfSamples = sampleString.size(); - if (COMMON_TEST_NUMBER_OF_SAMPLES > 0) - { - numberOfSamples = std::min(COMMON_TEST_NUMBER_OF_SAMPLES, numberOfSamples); - } - - std::vector miningSeeds(numberOfSamples); - std::vector publicKeys(numberOfSamples); - std::vector nonces(numberOfSamples); - - // Reading the input samples - for (unsigned long long i = 0; i < numberOfSamples; ++i) - { - miningSeeds[i] = hexTo32Bytes(sampleString[i][0], 32); - publicKeys[i] = hexTo32Bytes(sampleString[i][1], 32); - nonces[i] = hexTo32Bytes(sampleString[i][2], 32); - } - - std::unique_ptr> pScore = std::make_unique>(); - pScore->initMemory(); - - // Run with 4 mining seeds, each run 4 separate threads and the result need to matched - int scores[NUMBER_OF_PHASES][NUMBER_OF_THREADS * NUMBER_OF_SAMPLES] = { 0 }; - for (unsigned long long i = 0; i < NUMBER_OF_PHASES; ++i) - { - pScore->initMiningData(miningSeeds[i]); - -#pragma omp parallel for num_threads(NUMBER_OF_THREADS) - for (int threadId = 0; threadId < NUMBER_OF_THREADS; ++threadId) - { - if (threadId % 2 == 0) - { - for (int sampleId = 0; sampleId < NUMBER_OF_SAMPLES; ++sampleId) - { - scores[i][threadId * NUMBER_OF_SAMPLES + sampleId] = (*pScore)(threadId, publicKeys[sampleId], miningSeeds[i], nonces[sampleId]); - } - } - else - { - for (int sampleId = NUMBER_OF_SAMPLES - 1; sampleId >= 0; --sampleId) - { - scores[i][threadId * NUMBER_OF_SAMPLES + sampleId] = (*pScore)(threadId, publicKeys[sampleId], miningSeeds[i], nonces[sampleId]); - } - } - } - } - - // Each threads run with the same samples but the order is reversed. Expect the scores are matched. - for (unsigned long long i = 0; i < NUMBER_OF_PHASES; ++i) - { - for (int threadId = 0; threadId < NUMBER_OF_THREADS - 1; ++threadId) - { - for (int sampleId = 0; sampleId < NUMBER_OF_SAMPLES; ++sampleId) - { - EXPECT_EQ(scores[i][threadId * NUMBER_OF_SAMPLES + sampleId], scores[i][(threadId + 1) * NUMBER_OF_SAMPLES + sampleId]); - } - } - } -} -#endif diff --git a/test/score_addition_reference.h b/test/score_addition_reference.h deleted file mode 100644 index 8b95c17ed..000000000 --- a/test/score_addition_reference.h +++ /dev/null @@ -1,869 +0,0 @@ -#pragma once - -#include "score_common_reference.h" - -#include -#include - -namespace score_addition_reference -{ - -template -struct Miner -{ - // Convert params for easier usage - static constexpr unsigned long long numberOfInputNeurons = Params::numberOfInputNeurons; - static constexpr unsigned long long numberOfOutputNeurons = Params::numberOfOutputNeurons; - static constexpr unsigned long long numberOfTicks = Params::numberOfTicks; - static constexpr unsigned long long maxNumberOfNeighbors = Params::numberOfNeighbors; - static constexpr unsigned long long populationThreshold = Params::populationThreshold; - static constexpr unsigned long long numberOfMutations = Params::numberOfMutations; - static constexpr unsigned int solutionThreshold = Params::solutionThreshold; - - static constexpr unsigned long long numberOfNeurons = - numberOfInputNeurons + numberOfOutputNeurons; - static constexpr unsigned long long maxNumberOfNeurons = populationThreshold; - static constexpr unsigned long long maxNumberOfSynapses = - populationThreshold * maxNumberOfNeighbors; - static constexpr unsigned long long trainingSetSize = 1ULL << numberOfInputNeurons; // 2^K - static constexpr unsigned long long paddingNumberOfSynapses = - (maxNumberOfSynapses + 31 ) / 32 * 32; // padding to multiple of 32 - - static_assert( - maxNumberOfSynapses <= (0xFFFFFFFFFFFFFFFF << 1ULL), - "maxNumberOfSynapses must less than or equal MAX_UINT64/2"); - static_assert(maxNumberOfNeighbors % 2 == 0, "maxNumberOfNeighbors must divided by 2"); - static_assert( - populationThreshold > numberOfNeurons, - "populationThreshold must be greater than numberOfNeurons"); - - std::vector poolVec; - - void initialize(const unsigned char miningSeed[32]) - { - // Init random2 pool with mining seed - poolVec.resize(score_reference::POOL_VEC_PADDING_SIZE); - score_reference::generateRandom2Pool(miningSeed, poolVec.data()); - } - - // Training set - struct TraningPair - { - char input[numberOfInputNeurons]; // numberOfInputNeurons / 2 bits of A , and B (values: -1 or +1) - char output[numberOfOutputNeurons]; // numberOfOutputNeurons bits of C (values: -1 or +1) - } trainingSet[trainingSetSize]; // training set size: 2^K - - struct Synapse - { - char weight; - }; - - // Data for running the ANN - struct Neuron - { - enum Type - { - kInput, - kOutput, - kEvolution, - }; - Type type; - char value; - bool markForRemoval; - }; - - // Data for roll back - struct ANN - { - Neuron neurons[maxNumberOfNeurons]; - Synapse synapses[maxNumberOfSynapses]; - unsigned long long population; - }; - ANN bestANN; - ANN currentANN; - - // Intermediate data - struct InitValue - { - unsigned long long outputNeuronPositions[numberOfOutputNeurons]; - unsigned long long synapseWeight[paddingNumberOfSynapses / 32]; // each 64bits elements will - // decide value of 32 synapses - unsigned long long synpaseMutation[numberOfMutations]; - } initValue; - - unsigned long long neuronIndices[numberOfNeurons]; - char previousNeuronValue[maxNumberOfNeurons]; - - unsigned long long outputNeuronIndices[numberOfOutputNeurons]; - char outputNeuronExpectedValue[numberOfOutputNeurons]; - - long long neuronValueBuffer[maxNumberOfNeurons]; - - unsigned long long getActualNeighborCount() const - { - unsigned long long population = currentANN.population; - unsigned long long maxNeighbors = population - 1; // Exclude self - unsigned long long actual = std::min(maxNumberOfNeighbors, maxNeighbors); - - return actual; - } - - unsigned long long getLeftNeighborCount() const - { - unsigned long long actual = getActualNeighborCount(); - // For odd number, we add extra for the left - return (actual + 1) / 2; - } - - unsigned long long getRightNeighborCount() const - { - return getActualNeighborCount() - getLeftNeighborCount(); - } - - // Get the starting index in synapse buffer (left side start) - unsigned long long getSynapseStartIndex() const - { - constexpr unsigned long long synapseBufferCenter = maxNumberOfNeighbors / 2; - return synapseBufferCenter - getLeftNeighborCount(); - } - - // Get the ending index in synapse buffer (exclusive) - unsigned long long getSynapseEndIndex() const - { - constexpr unsigned long long synapseBufferCenter = maxNumberOfNeighbors / 2; - return synapseBufferCenter + getRightNeighborCount(); - } - - // Convert buffer index to neighbor offset - long long bufferIndexToOffset(unsigned long long bufferIdx) const - { - constexpr long long synapseBufferCenter = maxNumberOfNeighbors / 2; - if (bufferIdx < synapseBufferCenter) - { - return (long long)bufferIdx - synapseBufferCenter; // Negative (left) - } - else - { - return (long long)bufferIdx - synapseBufferCenter + 1; // Positive (right), skip 0 - } - } - - // Convert neighbor offset to buffer index - long long offsetToBufferIndex(long long offset) const - { - constexpr long long synapseBufferCenter = maxNumberOfNeighbors / 2; - if (offset == 0) - { - return -1; // Invalid, exclude self - } - else if (offset < 0) - { - return synapseBufferCenter + offset; - } - else - { - return synapseBufferCenter + offset - 1; - } - } - - long long getIndexInSynapsesBuffer(long long neighborOffset) const - { - long long leftCount = (long long)getLeftNeighborCount(); - long long rightCount = (long long)getRightNeighborCount(); - - if (neighborOffset == 0 || - neighborOffset < -leftCount || - neighborOffset > rightCount) - { - return -1; - } - - return offsetToBufferIndex(neighborOffset); - } - - - - void mutate(unsigned long long mutateStep) - { - // Mutation - unsigned long long population = currentANN.population; - unsigned long long actualNeighbors = getActualNeighborCount(); - Synapse* synapses = currentANN.synapses; - - // Randomly pick a synapse, randomly increase or decrease its weight by 1 or -1 - unsigned long long synapseMutation = initValue.synpaseMutation[mutateStep]; - unsigned long long totalValidSynapses = population * actualNeighbors; - unsigned long long flatIdx = (synapseMutation >> 1) % totalValidSynapses; - - // Convert flat index to (neuronIdx, local synapse index within valid range) - unsigned long long neuronIdx = flatIdx / actualNeighbors; - unsigned long long localSynapseIdx = flatIdx % actualNeighbors; - - // Convert to synapse buffer index that have bigger range - unsigned long long synapseIndex = localSynapseIdx + getSynapseStartIndex(); - unsigned long long synapseFullBufferIdx = neuronIdx * maxNumberOfNeighbors + synapseIndex; - - // Randomly increase or decrease its value - char weightChange = 0; - if ((synapseMutation & 1ULL) == 0) - { - weightChange = -1; - } - else - { - weightChange = 1; - } - - char newWeight = synapses[synapseFullBufferIdx].weight + weightChange; - - // Valid weight. Update it - if (newWeight >= -1 && newWeight <= 1) - { - synapses[synapseFullBufferIdx].weight = newWeight; - } - else // Invalid weight. Insert a neuron - { - // Insert the neuron - insertNeuron(neuronIdx, synapseIndex); - } - - // Clean the ANN - while (scanRedundantNeurons() > 0) - { - cleanANN(); - } - } - - // Get the pointer to all outgoing synapse of a neurons - Synapse* getSynapses(unsigned long long neuronIndex) - { - return ¤tANN.synapses[neuronIndex * maxNumberOfNeighbors]; - } - - // Calculate the new neuron index that is reached by moving from the given `neuronIdx` `value` - // neurons to the right or left. Negative `value` moves to the left, positive `value` moves to - // the right. The return value is clamped in a ring buffer fashion, i.e. moving right of the - // rightmost neuron continues at the leftmost neuron. - unsigned long long clampNeuronIndex(long long neuronIdx, long long value) - { - unsigned long long population = currentANN.population; - assert(value > -(long long)population && value < (long long)population - && "clampNeuronIndex: |value| must be less than population"); - - long long nnIndex = 0; - // Calculate the neuron index (ring structure) - if (value >= 0) - { - nnIndex = neuronIdx + value; - } - else - { - nnIndex = neuronIdx + population + value; - } - nnIndex = nnIndex % population; - return (unsigned long long)nnIndex; - } - - - // Remove a neuron and all synapses relate to it - void removeNeuron(unsigned long long neuronIdx) - { - long long leftCount = (long long)getLeftNeighborCount(); - long long rightCount = (long long)getRightNeighborCount(); - unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); - - // Scan all its neighbor to remove their outgoing synapse point to the neuron - for (long long neighborOffset = -leftCount; neighborOffset <= rightCount; neighborOffset++) - { - if (neighborOffset == 0) continue; - - unsigned long long nnIdx = clampNeuronIndex(neuronIdx, neighborOffset); - Synapse* pNNSynapses = getSynapses(nnIdx); - - long long synapseIndexOfNN = getIndexInSynapsesBuffer(-neighborOffset); - if (synapseIndexOfNN < 0) - { - continue; - } - - // The synapse array need to be shifted regard to the remove neuron - // Also neuron need to have 2M neighbors, the addtional synapse will be set as zero - // weight Case1 [S0 S1 S2 - SR S5 S6]. SR is removed, [S0 S1 S2 S5 S6 0] Case2 [S0 S1 SR - // - S3 S4 S5]. SR is removed, [0 S0 S1 S3 S4 S5] - constexpr unsigned long long halfMax = maxNumberOfNeighbors / 2; - if (synapseIndexOfNN >= (long long)halfMax) - { - for (long long k = synapseIndexOfNN; k < (long long)endSynapseBufferIdx - 1; ++k) - { - pNNSynapses[k] = pNNSynapses[k + 1]; - } - pNNSynapses[endSynapseBufferIdx - 1].weight = 0; - } - else - { - for (long long k = synapseIndexOfNN; k > (long long)startSynapseBufferIdx; --k) - { - pNNSynapses[k] = pNNSynapses[k - 1]; - } - pNNSynapses[startSynapseBufferIdx].weight = 0; - } - } - - // Shift the synapse array and the neuron array - for (unsigned long long shiftIdx = neuronIdx; shiftIdx < currentANN.population - 1; shiftIdx++) - { - currentANN.neurons[shiftIdx] = currentANN.neurons[shiftIdx + 1]; - - // Also shift the synapses - memcpy( - getSynapses(shiftIdx), - getSynapses(shiftIdx + 1), - maxNumberOfNeighbors * sizeof(Synapse)); - } - currentANN.population--; - } - - unsigned long long - getNeighborNeuronIndex(unsigned long long neuronIndex, unsigned long long neighborOffset) - { - const unsigned long long leftNeighbors = getLeftNeighborCount(); - unsigned long long nnIndex = 0; - if (neighborOffset < leftNeighbors) - { - nnIndex = clampNeuronIndex( - neuronIndex + neighborOffset, -(long long)leftNeighbors); - } - else - { - nnIndex = clampNeuronIndex( - neuronIndex + neighborOffset + 1, -(long long)leftNeighbors); - } - return nnIndex; - } - - void insertNeuron(unsigned long long neuronIndex, unsigned long long synapseIndex) - { - unsigned long long synapseFullBufferIdx = neuronIndex * maxNumberOfNeighbors + synapseIndex; - // Old value before insert neuron - unsigned long long oldStartSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long oldEndSynapseBufferIdx = getSynapseEndIndex(); - unsigned long long oldActualNeighbors = getActualNeighborCount(); - long long oldLeftCount = (long long)getLeftNeighborCount(); - long long oldRightCount = (long long)getRightNeighborCount(); - - constexpr unsigned long long halfMax = maxNumberOfNeighbors / 2; - - // Validate synapse index is within valid range - assert(synapseIndex >= oldStartSynapseBufferIdx && synapseIndex < oldEndSynapseBufferIdx); - - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - unsigned long long& population = currentANN.population; - - // Copy original neuron to the inserted one and set it as Neuron::kEvolution type - Neuron insertNeuron; - insertNeuron = neurons[neuronIndex]; - insertNeuron.type = Neuron::kEvolution; - unsigned long long insertedNeuronIdx = neuronIndex + 1; - - char originalWeight = synapses[synapseFullBufferIdx].weight; - - // Insert the neuron into array, population increased one, all neurons next to original one - // need to shift right - for (unsigned long long i = population; i > neuronIndex; --i) - { - neurons[i] = neurons[i - 1]; - - // Also shift the synapses to the right - memcpy(getSynapses(i), getSynapses(i - 1), maxNumberOfNeighbors * sizeof(Synapse)); - } - neurons[insertedNeuronIdx] = insertNeuron; - population++; - - // Recalculate after population change - unsigned long long newActualNeighbors = getActualNeighborCount(); - unsigned long long newStartSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long newEndSynapseBufferIdx = getSynapseEndIndex(); - - // Try to update the synapse of inserted neuron. All outgoing synapse is init as zero weight - Synapse* pInsertNeuronSynapse = getSynapses(insertedNeuronIdx); - for (unsigned long long synIdx = 0; synIdx < maxNumberOfNeighbors; ++synIdx) - { - pInsertNeuronSynapse[synIdx].weight = 0; - } - - // Copy the outgoing synapse of original neuron - if (synapseIndex < halfMax) - { - // The synapse is going to a neuron to the left of the original neuron. - // Check if the incoming neuron is still contained in the neighbors of the inserted - // neuron. This is the case if the original `synapseIndex` is > 0, i.e. - // the original synapse if not going to the leftmost neighbor of the original neuron. - if (synapseIndex > newStartSynapseBufferIdx) - { - // Decrease idx by one because the new neuron is inserted directly to the right of - // the original one. - pInsertNeuronSynapse[synapseIndex - 1].weight = originalWeight; - } - // If the incoming neuron of the original synapse if not contained in the neighbors of - // the inserted neuron, don't add the synapse. - } - else - { - // The synapse is going to a neuron to the right of the original neuron. - // In this case, the incoming neuron of the synapse is for sure contained in the - // neighbors of the inserted neuron and has the same idx (right side neighbors of - // inserted neuron = right side neighbors of original neuron before insertion). - pInsertNeuronSynapse[synapseIndex].weight = originalWeight; - } - - // The change of synapse only impact neuron in [originalNeuronIdx - actualNeighbors / 2 - // + 1, originalNeuronIdx + actualNeighbors / 2] In the new index, it will be - // [originalNeuronIdx + 1 - actualNeighbors / 2, originalNeuronIdx + 1 + - // actualNeighbors / 2] [N0 N1 N2 original inserted N4 N5 N6], M = 2. - for (long long delta = -oldLeftCount; delta <= oldRightCount; ++delta) - { - // Only process the neighbors - if (delta == 0) - { - continue; - } - unsigned long long updatedNeuronIdx = clampNeuronIndex(insertedNeuronIdx, delta); - - // Generate a list of neighbor index of current updated neuron NN - // Find the location of the inserted neuron in the list of neighbors - long long insertedNeuronIdxInNeigborList = -1; - for (unsigned long long k = 0; k < newActualNeighbors; k++) - { - unsigned long long nnIndex = getNeighborNeuronIndex(updatedNeuronIdx, k); - if (nnIndex == insertedNeuronIdx) - { - insertedNeuronIdxInNeigborList = (long long)(newStartSynapseBufferIdx + k); - } - } - - assert(insertedNeuronIdxInNeigborList >= 0); - - Synapse* pUpdatedSynapses = getSynapses(updatedNeuronIdx); - // [N0 N1 N2 original inserted N4 N5 N6], M = 2. - // Case: neurons in range [N0 N1 N2 original], right synapses will be affected - if (delta < 0) - { - // Left side is kept as it is, only need to shift to the right side - for (long long k = (long long)newEndSynapseBufferIdx - 1; k >= insertedNeuronIdxInNeigborList; --k) - { - // Updated synapse - pUpdatedSynapses[k] = pUpdatedSynapses[k - 1]; - } - - // Incomming synapse from original neuron -> inserted neuron must be zero - if (delta == -1) - { - pUpdatedSynapses[insertedNeuronIdxInNeigborList].weight = 0; - } - } - else // Case: neurons in range [inserted N4 N5 N6], left synapses will be affected - { - // Right side is kept as it is, only need to shift to the left side - for (long long k = (long long)newStartSynapseBufferIdx; k < insertedNeuronIdxInNeigborList; ++k) - { - // Updated synapse - pUpdatedSynapses[k] = pUpdatedSynapses[k + 1]; - } - } - } - } - - - // Check which neurons/synapse need to be removed after mutation - unsigned long long scanRedundantNeurons() - { - unsigned long long population = currentANN.population; - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - - unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); - long long leftCount = (long long)getLeftNeighborCount(); - long long rightCount = (long long)getRightNeighborCount(); - - unsigned long long numberOfRedundantNeurons = 0; - // After each mutation, we must verify if there are neurons that do not affect the ANN - // output. These are neurons that either have all incoming synapse weights as 0, or all - // outgoing synapse weights as 0. Such neurons must be removed. - for (unsigned long long i = 0; i < population; i++) - { - neurons[i].markForRemoval = false; - if (neurons[i].type == Neuron::kEvolution) - { - bool allOutGoingZeros = true; - bool allIncommingZeros = true; - - // Loop though its synapses for checkout outgoing synapses - for (unsigned long long m = startSynapseBufferIdx; m < endSynapseBufferIdx; m++) - { - char synapseW = synapses[i * maxNumberOfNeighbors + m].weight; - if (synapseW != 0) - { - allOutGoingZeros = false; - break; - } - } - - // Loop through the neighbor neurons to check all incoming synapses - for (long long offset = -leftCount; offset <= rightCount; offset++) - { - if (offset == 0) continue; - - unsigned long long nnIdx = clampNeuronIndex(i, offset); - long long synapseIdx = getIndexInSynapsesBuffer(-offset); - if (synapseIdx < 0) - { - continue; - } - char synapseW = getSynapses(nnIdx)[synapseIdx].weight; - - if (synapseW != 0) - { - allIncommingZeros = false; - break; - } - } - if (allOutGoingZeros || allIncommingZeros) - { - neurons[i].markForRemoval = true; - numberOfRedundantNeurons++; - } - } - } - return numberOfRedundantNeurons; - } - - // Remove neurons and synapses that do not affect the ANN - void cleanANN() - { - Neuron* neurons = currentANN.neurons; - unsigned long long& population = currentANN.population; - - // Scan and remove neurons/synapses - unsigned long long neuronIdx = 0; - while (neuronIdx < population) - { - if (neurons[neuronIdx].markForRemoval) - { - // Remove it from the neuron list. Overwrite data - // Remove its synapses in the synapses array - removeNeuron(neuronIdx); - } - else - { - neuronIdx++; - } - } - } - - void processTick() - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - - // Memset value of current one - memset(neuronValueBuffer, 0, sizeof(neuronValueBuffer)); - - // Loop though all neurons - unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); - unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); - - for (unsigned long long n = 0; n < population; ++n) - { - const Synapse* kSynapses = getSynapses(n); - long long neuronValue = neurons[n].value; - // Scan through all neighbor neurons and sum all connected neurons. - for (unsigned long long m = startSynapseBufferIdx; m < endSynapseBufferIdx; m++) - { - char synapseWeight = kSynapses[m].weight; - long long offset = bufferIndexToOffset(m); - unsigned long long nnIndex = clampNeuronIndex(static_cast(n), offset); - - // Weight-sum - neuronValueBuffer[nnIndex] += synapseWeight * neuronValue; - } - } - - // Clamp the neuron value - for (unsigned long long n = 0; n < population; ++n) - { - // Only non input neurons are updated - if (Neuron::kInput != neurons[n].type) - { - char neuronValue = score_reference::clampNeuron(neuronValueBuffer[n]); - neurons[n].value = neuronValue; - } - } - } - void loadTrainingData(unsigned long long trainingIndex) - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - - const auto& data = trainingSet[trainingIndex]; - // Load the input neuron value - unsigned long long inputIndex = 0; - for (unsigned long long n = 0; n < population; ++n) - { - // Init as zeros - neurons[n].value = 0; - if (Neuron::kInput == neurons[n].type) - { - neurons[n].value = data.input[inputIndex]; - inputIndex++; - } - } - - // Load the expected output value - memcpy(outputNeuronExpectedValue, data.output, sizeof(outputNeuronExpectedValue[0]) * numberOfOutputNeurons); - } - // Tick simulation only runs on one ANN - void runTickSimulation(unsigned long long trainingIndex) - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - - // Load the training set and fill ANN value - loadTrainingData(trainingIndex); - - // Save the neuron value for comparison - for (unsigned long long i = 0; i < population; ++i) - { - // Backup the neuron value - previousNeuronValue[i] = neurons[i].value; - } - - for (unsigned long long tick = 0; tick < numberOfTicks; ++tick) - { - processTick(); - // Check exit conditions: - // - N ticks have passed (already in for loop) - // - All neuron values are unchanged - // - All output neurons have non-zero values - bool allNeuronsUnchanged = true; - bool allOutputNeuronsIsNonZeros = true; - for (unsigned long long n = 0; n < population; ++n) - { - // Neuron unchanged check - if (previousNeuronValue[n] != neurons[n].value) - { - allNeuronsUnchanged = false; - } - - // Ouput neuron value check - if (neurons[n].type == Neuron::kOutput && neurons[n].value == 0) - { - allOutputNeuronsIsNonZeros = false; - } - } - - if (allOutputNeuronsIsNonZeros || allNeuronsUnchanged) - { - break; - } - - // Copy the neuron value - for (unsigned long long n = 0; n < population; ++n) - { - previousNeuronValue[n] = neurons[n].value; - } - } - } - - unsigned int computeMatchingOutput() - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - - // Compute the non-matching value R between output neuron value and initial value - // Because the output neuron order never changes, the order is preserved - unsigned int R = 0; - unsigned long long outputIdx = 0; - for (unsigned long long i = 0; i < population; i++) - { - if (neurons[i].type == Neuron::kOutput) - { - if (neurons[i].value == outputNeuronExpectedValue[outputIdx]) - { - R++; - } - outputIdx++; - } - } - return R; - } - - // Generate all 2^K possible (A, B, C) pairs - void generateTrainingSet() - { - static constexpr long long boundValue = (1LL << (numberOfInputNeurons / 2)) / 2; - unsigned long long index = 0; - for (long long A = -boundValue; A < boundValue; A++) - { - for (long long B = -boundValue; B < boundValue; B++) - { - long long C = A + B; - - score_reference::toTenaryBits(A, trainingSet[index].input); - score_reference::toTenaryBits( - B, trainingSet[index].input + numberOfInputNeurons / 2); - score_reference::toTenaryBits(C, trainingSet[index].output); - index++; - } - } - } - - unsigned int inferANN() - { - unsigned int score = 0; - for (unsigned long long i = 0; i < trainingSetSize; ++i) - { - // Ticks simulation - runTickSimulation(i); - - // Compute R - unsigned int R = computeMatchingOutput(); - score += R; - } - return score; - } - - unsigned int initializeANN(const unsigned char* publicKey, const unsigned char* nonce) - { - unsigned char hash[32]; - unsigned char combined[64]; - memcpy(combined, publicKey, 32); - memcpy(combined + 32, nonce, 32); - KangarooTwelve(combined, 64, hash, 32); - - unsigned long long& population = currentANN.population; - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - - // Initialization - population = numberOfNeurons; - - // Generate all 2^K possible (A, B, C) pairs - generateTrainingSet(); - - // Initalize with nonce and public key - score_reference::random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); - - // Randomly choose the positions of neurons types - for (unsigned long long i = 0; i < population; ++i) - { - neuronIndices[i] = i; - neurons[i].type = Neuron::kInput; - } - unsigned long long neuronCount = population; - for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) - { - unsigned long long outputNeuronIdx = initValue.outputNeuronPositions[i] % neuronCount; - - // Fill the neuron type - neurons[neuronIndices[outputNeuronIdx]].type = Neuron::kOutput; - outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; - - // This index is used, copy the end of indices array to current position and decrease - // the number of picking neurons - neuronCount = neuronCount - 1; - neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; - } - - // Synapse weight initialization - auto extractWeight = [](unsigned long long packedValue, unsigned long long position) -> char { - unsigned char extractValue = static_cast((packedValue >> (position * 2)) & 0b11); - switch (extractValue) - { - case 2: - return -1; - case 3: - return 1; - default: - return 0; - } - }; - for (unsigned long long i = 0; i < (maxNumberOfSynapses / 32); ++i) - { - for (unsigned long long j = 0; j < 32; ++j) - { - synapses[32 * i + j].weight = extractWeight(initValue.synapseWeight[i], j); - } - } - - // Handle remaining synapses (if maxNumberOfSynapses not divisible by 32) - unsigned long long remainder = maxNumberOfSynapses % 32; - if (remainder > 0) - { - unsigned long long lastBlock = maxNumberOfSynapses / 32; - for (unsigned long long j = 0; j < remainder; ++j) - { - synapses[32 * lastBlock + j].weight = extractWeight(initValue.synapseWeight[lastBlock], j); - } - } - - // Run the first inference to get starting point before mutation - unsigned int score = inferANN(); - - return score; - } - - // Main function for mining - unsigned int computeScore(const unsigned char* publicKey, const unsigned char* nonce) - { - // Initialize - unsigned int bestR = initializeANN(publicKey, nonce); - memcpy(&bestANN, ¤tANN, sizeof(bestANN)); - - for (unsigned long long s = 0; s < numberOfMutations; ++s) - { - // Do the mutation - mutate(s); - - // Exit if the number of population reaches the maximum allowed - if (currentANN.population >= populationThreshold) - { - break; - } - - // Ticks simulation - unsigned int R = inferANN(); - - // Roll back if neccessary - if (R >= bestR) - { - bestR = R; - // Better R. Save the state - memcpy(&bestANN, ¤tANN, sizeof(bestANN)); - } - else - { - // Roll back - memcpy(¤tANN, &bestANN, sizeof(bestANN)); - } - - assert(bestANN.population <= populationThreshold); - } - return bestR; - } - - bool findSolution(const unsigned char* publicKey, const unsigned char* nonce) - { - unsigned int score = computeScore(publicKey, nonce); - if (score >= solutionThreshold) - { - return true; - } - - return false; - } -}; - -} // namespace score_addition diff --git a/test/score_cache.cpp b/test/score_cache.cpp deleted file mode 100644 index 24678308c..000000000 --- a/test/score_cache.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#include "../src/score_cache.h" - -#include - - -template -void expectEmptyCache(ScoreCache& cache) -{ - EXPECT_EQ(cache.hitCount(), 0); - EXPECT_EQ(cache.collisionCount(), 0); - EXPECT_EQ(cache.missCount(), 0); - - // test that all is empty and access out of bounds is no error - for (unsigned int i = 0; i < cache.capacity() + 100; ++i) - { - // test with arbitrary publicKey and nonce (real and pseudo-random is slow, so use a fast quite random pattern) - unsigned long long a = i * 123456789ull; - unsigned long long b = 0xbca326450256c63eull - i * 759037ull; - unsigned long long c = 2345932453043560ull << (i & 63); - m256i publicKey(a ^ b, a ^ c, b ^ c, a ^ b ^ c); - m256i miningSeed = m256i(1, 1, 1, 1); - m256i nonce((a << 2) ^ b, (a << 2) ^ c, (b << 1) ^ c, (b >> 1) ^ c); - - unsigned int ioIdx = i; - EXPECT_EQ(cache.tryFetching(publicKey, miningSeed, nonce, ioIdx), cache.SCORE_CACHE_MISS); - EXPECT_TRUE(ioIdx == i || (i >= cache.capacity() && ioIdx == i % cache.capacity())); - } -} - -template -unsigned int pseudoRandomCacheTest(ScoreCache & cache, unsigned long long seed, unsigned int entryCount, bool overwrite) -{ - cache.reset(); - - // add entries with pseudo-random data - std::mt19937_64 gen64; - gen64.seed(seed); - for (unsigned int i = 0; i < entryCount; ++i) - { - m256i publicKey(gen64(), gen64(), gen64(), gen64()); - m256i miningSeed(gen64(), gen64(), gen64(), gen64()); - m256i nonce(gen64(), gen64(), gen64(), gen64()); - int score = gen64() % std::numeric_limits::max(); - assert(score >= 0); - unsigned int idx = cache.getCacheIndex(publicKey, miningSeed, nonce); - int fetchedScore = cache.tryFetching(publicKey, miningSeed, nonce, idx); - - // assume that we will not get the same publicKey and nonce twice in random entry generation - EXPECT_TRUE(fetchedScore == cache.SCORE_CACHE_MISS || fetchedScore == cache.SCORE_CACHE_COLLISION); - - if (fetchedScore != cache.SCORE_CACHE_COLLISION || overwrite) - { - cache.addEntry(publicKey, miningSeed, nonce, idx, score); - } - } - EXPECT_EQ(entryCount, cache.missCount() + cache.collisionCount()); - EXPECT_EQ(cache.hitCount(), 0); - - //std::cout << "randomCacheTest: capacity " << cacheCapacity << ", filled " << 100.0 * entryCount / cacheCapacity << "%, overwrite=" << overwrite - // << ", collisions " << cache.collisionCount() << ", misses " << cache.missCount() << ", hits " << cache.hitCount() << " (SEED " << seed << ")" << std::endl; - - int collisionCount = cache.collisionCount(); - - // test entries with pseudo-random data - gen64.seed(seed); - for (unsigned int i = 0; i < entryCount; ++i) - { - m256i publicKey(gen64(), gen64(), gen64(), gen64()); - m256i miningSeed(gen64(), gen64(), gen64(), gen64()); - m256i nonce(gen64(), gen64(), gen64(), gen64()); - int expectedScore = gen64() % std::numeric_limits::max(); - - unsigned int idx = cache.getCacheIndex(publicKey, miningSeed, nonce); - int fetchedScore = cache.tryFetching(publicKey, miningSeed, nonce, idx); - - EXPECT_TRUE(fetchedScore == cache.SCORE_CACHE_COLLISION || fetchedScore >= cache.MIN_VALID_SCORE); - if (fetchedScore >= cache.MIN_VALID_SCORE) - { - EXPECT_EQ(fetchedScore, expectedScore); - } - } - - EXPECT_EQ(entryCount * 2, cache.missCount() + cache.collisionCount() + cache.hitCount()); - - - return collisionCount; -} - -template -void testCacheSameSeeds(unsigned int fillPercent) -{ - typedef ScoreCache CacheType; - CacheType* cache = new CacheType(); - - expectEmptyCache(*cache); - - bool overwrite = true; - const int entryCount = (unsigned long long)cacheCapacity * fillPercent / 100; - unsigned int collisionCount = 0; - collisionCount += pseudoRandomCacheTest(*cache, 0, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 1234, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 42, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 987654321, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 1234573574564560925, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 234563875344, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 3245789, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 9357637, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 23648682, entryCount, overwrite); - collisionCount += pseudoRandomCacheTest(*cache, 347692997236, entryCount, overwrite); - std::cout << "Total collision count with capacity " << cacheCapacity << " (10 tests): " << collisionCount << std::endl; - - cache->reset(); - expectEmptyCache(*cache); - - delete cache; -} - -template -void testCacheRandomSeeds(unsigned int fillPercent) -{ - typedef ScoreCache CacheType; - CacheType* cache = new CacheType(); - - expectEmptyCache(*cache); - - bool overwrite = true; - const int entryCount = (unsigned long long)cacheCapacity * fillPercent / 100; - unsigned int collisionCount = 0; - for (int i = 0; i < 10; ++i) - { - unsigned long long seed; - _rdrand64_step(&seed); - collisionCount += pseudoRandomCacheTest(*cache, seed, entryCount, overwrite); - } - std::cout << "Total collision count with capacity " << cacheCapacity << " (10 tests): " << collisionCount << std::endl; - - cache->reset(); - expectEmptyCache(*cache); - - delete cache; -} - - -// Some people claimed that the hash table will have less collisions if the capacity -// is a prime number. The experiments with our table do not support this claim. -// A search in the internet showed that you only need prime number capacity -// if your hash function is not good. -// http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html -// So it seems like our hash function is good and we do not need prime numbers. - -TEST(TestQubicScoreCache, FixedSeeds20pctFilled) { - testCacheSameSeeds<1000000>(20); // non-prime number as cache size - testCacheSameSeeds<1000003>(20); // prime number as cache size - - testCacheSameSeeds<200000>(20); // non-prime number as cache size - testCacheSameSeeds<199999>(20); // prime number as cache size -} - -TEST(TestQubicScoreCache, FixedSeeds50pctFilled) { - testCacheSameSeeds<1000000>(50); // non-prime number as cache size - testCacheSameSeeds<1000003>(50); // prime number as cache size - - testCacheSameSeeds<200000>(50); // non-prime number as cache size - testCacheSameSeeds<199999>(50); // prime number as cache size -} - -TEST(TestQubicScoreCache, FixedSeeds80pctFilled) { - testCacheSameSeeds<1000000>(80); // non-prime number as cache size - testCacheSameSeeds<1000003>(80); // prime number as cache size - - testCacheSameSeeds<200000>(80); // non-prime number as cache size - testCacheSameSeeds<199999>(80); // prime number as cache size -} - -TEST(TestQubicScoreCache, RandomSeeds20pctFilled) { - testCacheRandomSeeds<1000000>(20); // non-prime number as cache size - testCacheRandomSeeds<1000003>(20); // prime number as cache size - - testCacheRandomSeeds<200000>(20); // non-prime number as cache size - testCacheRandomSeeds<199999>(20); // prime number as cache size -} - -TEST(TestQubicScoreCache, RandomSeeds50pctFilled) { - testCacheRandomSeeds<1000000>(50); // non-prime number as cache size - testCacheRandomSeeds<1000003>(50); // prime number as cache size - - testCacheRandomSeeds<200000>(50); // non-prime number as cache size - testCacheRandomSeeds<199999>(50); // prime number as cache size -} - -TEST(TestQubicScoreCache, RandomSeeds80pctFilled) { - testCacheRandomSeeds<1000000>(80); // non-prime number as cache size - testCacheRandomSeeds<1000003>(80); // prime number as cache size - - testCacheRandomSeeds<200000>(80); // non-prime number as cache size - testCacheRandomSeeds<199999>(80); // prime number as cache size -} diff --git a/test/score_common_reference.h b/test/score_common_reference.h deleted file mode 100644 index 62edf5d1b..000000000 --- a/test/score_common_reference.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include "kangaroo_twelve.h" - -#include - -namespace score_reference -{ - -constexpr unsigned long long POOL_VEC_SIZE = (((1ULL << 32) + 64)) >> 3; // 2^32+64 bits ~ 512MB -constexpr unsigned long long POOL_VEC_PADDING_SIZE = (POOL_VEC_SIZE + 200 - 1) / 200 * 200; // padding for multiple of 200 - -void generateRandom2Pool(const unsigned char miningSeed[32], unsigned char* pool) -{ - unsigned char state[200]; - // same pool to be used by all computors/candidates and pool content changing each phase - memcpy(&state[0], miningSeed, 32); - memset(&state[32], 0, sizeof(state) - 32); - - for (unsigned int i = 0; i < POOL_VEC_PADDING_SIZE; i += sizeof(state)) - { - KeccakP1600_Permute_12rounds(state); - memcpy(&pool[i], state, sizeof(state)); - } -} - -void random2( - unsigned char seed[32], - const unsigned char* pool, - unsigned char* output, - unsigned long long outputSizeInByte) -{ - unsigned long long paddingOutputSize = (outputSizeInByte + 64 - 1) / 64; - paddingOutputSize = paddingOutputSize * 64; - std::vector paddingOutputVec(paddingOutputSize); - unsigned char* paddingOutput = paddingOutputVec.data(); - - unsigned long long segments = paddingOutputSize / 64; - unsigned int x[8] = { 0 }; - for (int i = 0; i < 8; i++) - { - x[i] = ((unsigned int*)seed)[i]; - } - - for (int j = 0; j < segments; j++) - { - // Each segment will have 8 elements. Each element have 8 bytes - for (int i = 0; i < 8; i++) - { - unsigned int base = (x[i] >> 3) >> 3; - unsigned int m = x[i] & 63; - - unsigned long long u64_0 = ((unsigned long long*)pool)[base]; - unsigned long long u64_1 = ((unsigned long long*)pool)[base + 1]; - - // Move 8 * 8 * j to the current segment. 8 * i to current 8 bytes element - if (m == 0) - { - // some compiler doesn't work with bit shift 64 - *((unsigned long long*) & paddingOutput[j * 8 * 8 + i * 8]) = u64_0; - } - else - { - *((unsigned long long*) & paddingOutput[j * 8 * 8 + i * 8]) = (u64_0 >> m) | (u64_1 << (64 - m)); - } - - // Increase the positions in the pool for each element. - x[i] = x[i] * 1664525 + 1013904223; // https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use - } - } - - memcpy(output, paddingOutput, outputSizeInByte); -} - -// Clamp the neuron value -template -char clampNeuron(T neuronValue) -{ - if (neuronValue > 1) - { - return 1; - } - - if (neuronValue < -1) - { - return -1; - } - return static_cast(neuronValue); -} - -void extract64Bits(unsigned long long number, char* output) -{ - for (int i = 0; i < 64; ++i) - { - output[i] = ((number >> i) & 1); - } -} - -template -void toTenaryBits(long long A, char* bits) -{ - for (unsigned long long i = 0; i < bitCount; ++i) - { - char bitValue = static_cast((A >> i) & 1); - bits[i] = (bitValue == 0) ? -1 : bitValue; - } -} -} diff --git a/test/score_hyperidentity_reference.h b/test/score_hyperidentity_reference.h deleted file mode 100644 index 3be6f924f..000000000 --- a/test/score_hyperidentity_reference.h +++ /dev/null @@ -1,785 +0,0 @@ -#pragma once - -#include "../src/mining/score_common.h" -#include "score_common_reference.h" - -namespace score_hyberidentity_reference -{ -template -struct Miner -{ - // Convert params for easier usage - static constexpr unsigned long long numberOfInputNeurons = Params::numberOfInputNeurons; - static constexpr unsigned long long numberOfOutputNeurons = Params::numberOfOutputNeurons; - static constexpr unsigned long long numberOfTicks = Params::numberOfTicks; - static constexpr unsigned long long numberOfNeighbors = Params::numberOfNeighbors; - static constexpr unsigned long long populationThreshold = Params::populationThreshold; - static constexpr unsigned long long numberOfMutations = Params::numberOfMutations; - static constexpr unsigned int solutionThreshold = Params::solutionThreshold; - - // Condition and predifined - static constexpr unsigned long long numberOfNeurons = - numberOfInputNeurons + numberOfOutputNeurons; - static constexpr unsigned long long maxNumberOfNeurons = populationThreshold; - static constexpr unsigned long long maxNumberOfSynapses = - populationThreshold * numberOfNeighbors; - static constexpr unsigned long long initNumberOfSynapses = numberOfNeurons * numberOfNeighbors; - - static_assert(numberOfInputNeurons % 64 == 0, "numberOfInputNeurons must be divided by 64"); - static_assert(numberOfOutputNeurons % 64 == 0, "numberOfOutputNeurons must be divided by 64"); - static_assert( - maxNumberOfSynapses <= (0xFFFFFFFFFFFFFFFF << 1ULL), - "maxNumberOfSynapses must less than or equal MAX_UINT64/2"); - static_assert(initNumberOfSynapses % 32 == 0, "initNumberOfSynapses must be divided by 32"); - static_assert(numberOfNeighbors % 2 == 0, "numberOfNeighbors must divided by 2"); - static_assert( - populationThreshold > numberOfNeurons, - "populationThreshold must be greater than numberOfNeurons"); - static_assert( - numberOfNeurons > numberOfNeighbors, - "Number of neurons must be greater than the number of neighbors"); - - std::vector poolVec; - - void initialize(const unsigned char miningSeed[32]) - { - // Init random2 pool with mining seed - poolVec.resize(score_reference::POOL_VEC_PADDING_SIZE); - score_reference::generateRandom2Pool(miningSeed, poolVec.data()); - } - - struct Synapse - { - char weight; - }; - - // Data for running the ANN - struct Neuron - { - enum Type - { - kInput, - kOutput, - kEvolution, - }; - Type type; - char value; - bool markForRemoval; - }; - - // Data for roll back - struct ANN - { - Neuron neurons[maxNumberOfNeurons]; - Synapse synapses[maxNumberOfSynapses]; - unsigned long long population; - }; - ANN bestANN; - ANN currentANN; - - // Intermediate data - struct InitValue - { - unsigned long long outputNeuronPositions[numberOfOutputNeurons]; - unsigned long long synapseWeight[initNumberOfSynapses / 32]; // each 64bits elements will - // decide value of 32 synapses - unsigned long long synpaseMutation[numberOfMutations]; - } initValue; - - struct MiningData - { - unsigned long long - inputNeuronRandomNumber[numberOfInputNeurons / 64]; // each bit will use for generate - // input neuron value - unsigned long long - outputNeuronRandomNumber[numberOfOutputNeurons / 64]; // each bit will use for generate - // expected output neuron value - } miningData; - - unsigned long long neuronIndices[numberOfNeurons]; - char previousNeuronValue[maxNumberOfNeurons]; - - unsigned long long outputNeuronIndices[numberOfOutputNeurons]; - char outputNeuronExpectedValue[numberOfOutputNeurons]; - - long long neuronValueBuffer[maxNumberOfNeurons]; - - void mutate(const unsigned char nonce[32], unsigned long long mutateStep) - { - // Mutation - unsigned long long population = currentANN.population; - unsigned long long synapseCount = population * numberOfNeighbors; - Synapse* synapses = currentANN.synapses; - - // Randomly pick a synapse, randomly increase or decrease its weight by 1 or -1 - unsigned long long synapseMutation = initValue.synpaseMutation[mutateStep]; - unsigned long long synapseIdx = (synapseMutation >> 1) % synapseCount; - // Randomly increase or decrease its value - char weightChange = 0; - if ((synapseMutation & 1ULL) == 0) - { - weightChange = -1; - } - else - { - weightChange = 1; - } - - char newWeight = synapses[synapseIdx].weight + weightChange; - - // Valid weight. Update it - if (newWeight >= -1 && newWeight <= 1) - { - synapses[synapseIdx].weight = newWeight; - } - else // Invalid weight. Insert a neuron - { - // Insert the neuron - insertNeuron(synapseIdx); - } - - // Clean the ANN - while (scanRedundantNeurons() > 0) - { - cleanANN(); - } - } - - // Get the pointer to all outgoing synapse of a neurons - Synapse* getSynapses(unsigned long long neuronIndex) - { - return ¤tANN.synapses[neuronIndex * numberOfNeighbors]; - } - - // Calculate the new neuron index that is reached by moving from the given `neuronIdx` `value` - // neurons to the right or left. Negative `value` moves to the left, positive `value` moves to - // the right. The return value is clamped in a ring buffer fashion, i.e. moving right of the - // rightmost neuron continues at the leftmost neuron. - unsigned long long clampNeuronIndex(long long neuronIdx, long long value) - { - unsigned long long population = currentANN.population; - long long nnIndex = 0; - // Calculate the neuron index (ring structure) - if (value >= 0) - { - nnIndex = neuronIdx + value; - } - else - { - nnIndex = neuronIdx + population + value; - } - nnIndex = nnIndex % population; - return (unsigned long long)nnIndex; - } - - // Remove a neuron and all synapses relate to it - void removeNeuron(unsigned long long neuronIdx) - { - // Scan all its neigbor to remove their outgoing synapse point to the neuron - for (long long neighborOffset = -(long long)numberOfNeighbors / 2; - neighborOffset <= (long long)numberOfNeighbors / 2; - neighborOffset++) - { - unsigned long long nnIdx = clampNeuronIndex(neuronIdx, neighborOffset); - Synapse* pNNSynapses = getSynapses(nnIdx); - - long long synapseIndexOfNN = getIndexInSynapsesBuffer(nnIdx, -neighborOffset); - if (synapseIndexOfNN < 0) - { - continue; - } - - // The synapse array need to be shifted regard to the remove neuron - // Also neuron need to have 2M neighbors, the addtional synapse will be set as zero - // weight Case1 [S0 S1 S2 - SR S5 S6]. SR is removed, [S0 S1 S2 S5 S6 0] Case2 [S0 S1 SR - // - S3 S4 S5]. SR is removed, [0 S0 S1 S3 S4 S5] - if (synapseIndexOfNN >= numberOfNeighbors / 2) - { - for (long long k = synapseIndexOfNN; k < numberOfNeighbors - 1; ++k) - { - pNNSynapses[k] = pNNSynapses[k + 1]; - } - pNNSynapses[numberOfNeighbors - 1].weight = 0; - } - else - { - for (long long k = synapseIndexOfNN; k > 0; --k) - { - pNNSynapses[k] = pNNSynapses[k - 1]; - } - pNNSynapses[0].weight = 0; - } - } - - // Shift the synapse array and the neuron array - for (unsigned long long shiftIdx = neuronIdx; shiftIdx < currentANN.population; shiftIdx++) - { - currentANN.neurons[shiftIdx] = currentANN.neurons[shiftIdx + 1]; - - // Also shift the synapses - memcpy( - getSynapses(shiftIdx), - getSynapses(shiftIdx + 1), - numberOfNeighbors * sizeof(Synapse)); - } - currentANN.population--; - } - - unsigned long long - getNeighborNeuronIndex(unsigned long long neuronIndex, unsigned long long neighborOffset) - { - unsigned long long nnIndex = 0; - if (neighborOffset < (numberOfNeighbors / 2)) - { - nnIndex = - clampNeuronIndex(neuronIndex + neighborOffset, -(long long)numberOfNeighbors / 2); - } - else - { - nnIndex = clampNeuronIndex( - neuronIndex + neighborOffset + 1, -(long long)numberOfNeighbors / 2); - } - return nnIndex; - } - - void insertNeuron(unsigned long long synapseIdx) - { - // A synapse have incomingNeighbor and outgoingNeuron, direction incomingNeuron -> - // outgoingNeuron - unsigned long long incomingNeighborSynapseIdx = synapseIdx % numberOfNeighbors; - unsigned long long outgoingNeuron = synapseIdx / numberOfNeighbors; - - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - unsigned long long& population = currentANN.population; - - // Copy original neuron to the inserted one and set it as Neuron::kEvolution type - Neuron insertNeuron; - insertNeuron = neurons[outgoingNeuron]; - insertNeuron.type = Neuron::kEvolution; - unsigned long long insertedNeuronIdx = outgoingNeuron + 1; - - char originalWeight = synapses[synapseIdx].weight; - - // Insert the neuron into array, population increased one, all neurons next to original one - // need to shift right - for (unsigned long long i = population; i > outgoingNeuron; --i) - { - neurons[i] = neurons[i - 1]; - - // Also shift the synapses to the right - memcpy(getSynapses(i), getSynapses(i - 1), numberOfNeighbors * sizeof(Synapse)); - } - neurons[insertedNeuronIdx] = insertNeuron; - population++; - - // Try to update the synapse of inserted neuron. All outgoing synapse is init as zero weight - Synapse* pInsertNeuronSynapse = getSynapses(insertedNeuronIdx); - for (unsigned long long synIdx = 0; synIdx < numberOfNeighbors; ++synIdx) - { - pInsertNeuronSynapse[synIdx].weight = 0; - } - - // Copy the outgoing synapse of original neuron - if (incomingNeighborSynapseIdx < numberOfNeighbors / 2) - { - // The synapse is going to a neuron to the left of the original neuron. - // Check if the incoming neuron is still contained in the neighbors of the inserted - // neuron. This is the case if the original `incomingNeighborSynapseIdx` is > 0, i.e. - // the original synapse if not going to the leftmost neighbor of the original neuron. - if (incomingNeighborSynapseIdx > 0) - { - // Decrease idx by one because the new neuron is inserted directly to the right of - // the original one. - pInsertNeuronSynapse[incomingNeighborSynapseIdx - 1].weight = originalWeight; - } - // If the incoming neuron of the original synapse if not contained in the neighbors of - // the inserted neuron, don't add the synapse. - } - else - { - // The synapse is going to a neuron to the right of the original neuron. - // In this case, the incoming neuron of the synapse is for sure contained in the - // neighbors of the inserted neuron and has the same idx (right side neighbors of - // inserted neuron = right side neighbors of original neuron before insertion). - pInsertNeuronSynapse[incomingNeighborSynapseIdx].weight = originalWeight; - } - - // The change of synapse only impact neuron in [originalNeuronIdx - numberOfNeighbors / 2 + - // 1, originalNeuronIdx + numberOfNeighbors / 2] In the new index, it will be - // [originalNeuronIdx + 1 - numberOfNeighbors / 2, originalNeuronIdx + 1 + numberOfNeighbors - // / 2] [N0 N1 N2 original inserted N4 N5 N6], M = 2. - for (long long delta = -(long long)numberOfNeighbors / 2; - delta <= (long long)numberOfNeighbors / 2; - ++delta) - { - // Only process the neigbors - if (delta == 0) - { - continue; - } - unsigned long long updatedNeuronIdx = clampNeuronIndex(insertedNeuronIdx, delta); - - // Generate a list of neighbor index of current updated neuron NN - // Find the location of the inserted neuron in the list of neighbors - long long insertedNeuronIdxInNeigborList = -1; - for (long long k = 0; k < numberOfNeighbors; k++) - { - unsigned long long nnIndex = getNeighborNeuronIndex(updatedNeuronIdx, k); - if (nnIndex == insertedNeuronIdx) - { - insertedNeuronIdxInNeigborList = k; - } - } - - assert(insertedNeuronIdxInNeigborList >= 0); - - Synapse* pUpdatedSynapses = getSynapses(updatedNeuronIdx); - // [N0 N1 N2 original inserted N4 N5 N6], M = 2. - // Case: neurons in range [N0 N1 N2 original], right synapses will be affected - if (delta < 0) - { - // Left side is kept as it is, only need to shift to the right side - for (long long k = numberOfNeighbors - 1; k >= insertedNeuronIdxInNeigborList; --k) - { - // Updated synapse - pUpdatedSynapses[k] = pUpdatedSynapses[k - 1]; - } - - // Incomming synapse from original neuron -> inserted neuron must be zero - if (delta == -1) - { - pUpdatedSynapses[insertedNeuronIdxInNeigborList].weight = 0; - } - } - else // Case: neurons in range [inserted N4 N5 N6], left synapses will be affected - { - // Right side is kept as it is, only need to shift to the left side - for (long long k = 0; k < insertedNeuronIdxInNeigborList; ++k) - { - // Updated synapse - pUpdatedSynapses[k] = pUpdatedSynapses[k + 1]; - } - } - } - } - - long long getIndexInSynapsesBuffer(unsigned long long neuronIdx, long long neighborOffset) - { - // Skip the case neuron point to itself and too far neighbor - if (neighborOffset == 0 || neighborOffset < -(long long)numberOfNeighbors / 2 || - neighborOffset > (long long)numberOfNeighbors / 2) - { - return -1; - } - - long long synapseIdx = (long long)numberOfNeighbors / 2 + neighborOffset; - if (neighborOffset >= 0) - { - synapseIdx = synapseIdx - 1; - } - - return synapseIdx; - } - - // Check which neurons/synapse need to be removed after mutation - unsigned long long scanRedundantNeurons() - { - unsigned long long population = currentANN.population; - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - - unsigned long long numberOfRedundantNeurons = 0; - // After each mutation, we must verify if there are neurons that do not affect the ANN - // output. These are neurons that either have all incoming synapse weights as 0, or all - // outgoing synapse weights as 0. Such neurons must be removed. - for (unsigned long long i = 0; i < population; i++) - { - neurons[i].markForRemoval = false; - if (neurons[i].type == Neuron::kEvolution) - { - bool allOutGoingZeros = true; - bool allIncommingZeros = true; - - // Loop though its synapses for checkout outgoing synapses - for (unsigned long long n = 0; n < numberOfNeighbors; n++) - { - char synapseW = synapses[i * numberOfNeighbors + n].weight; - if (synapseW != 0) - { - allOutGoingZeros = false; - break; - } - } - - // Loop through the neighbor neurons to check all incoming synapses - for (long long neighborOffset = -(long long)numberOfNeighbors / 2; - neighborOffset <= (long long)numberOfNeighbors / 2; - neighborOffset++) - { - unsigned long long nnIdx = clampNeuronIndex(i, neighborOffset); - Synapse* nnSynapses = getSynapses(nnIdx); - - long long synapseIdx = getIndexInSynapsesBuffer(nnIdx, -neighborOffset); - if (synapseIdx < 0) - { - continue; - } - char synapseW = nnSynapses[synapseIdx].weight; - - if (synapseW != 0) - { - allIncommingZeros = false; - break; - } - } - if (allOutGoingZeros || allIncommingZeros) - { - neurons[i].markForRemoval = true; - numberOfRedundantNeurons++; - } - } - } - return numberOfRedundantNeurons; - } - - // Remove neurons and synapses that do not affect the ANN - void cleanANN() - { - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - unsigned long long& population = currentANN.population; - - // Scan and remove neurons/synapses - unsigned long long neuronIdx = 0; - while (neuronIdx < population) - { - if (neurons[neuronIdx].markForRemoval) - { - // Remove it from the neuron list. Overwrite data - // Remove its synapses in the synapses array - removeNeuron(neuronIdx); - } - else - { - neuronIdx++; - } - } - } - - void processTick() - { - unsigned long long population = currentANN.population; - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - - // Memset value of current one - memset(neuronValueBuffer, 0, sizeof(neuronValueBuffer)); - - // Loop though all neurons - for (unsigned long long n = 0; n < population; ++n) - { - const Synapse* kSynapses = getSynapses(n); - long long neuronValue = neurons[n].value; - // Scan through all neighbor neurons and sum all connected neurons. - // The synapses are arranged as neuronIndex * numberOfNeighbors - for (long long m = 0; m < numberOfNeighbors; m++) - { - char synapseWeight = kSynapses[m].weight; - unsigned long long nnIndex = 0; - if (m < numberOfNeighbors / 2) - { - nnIndex = clampNeuronIndex(static_cast(n + m), -static_cast(numberOfNeighbors / 2)); - } - else - { - nnIndex = clampNeuronIndex(static_cast(n + m + 1), -static_cast(numberOfNeighbors / 2)); - } - - // Weight-sum - neuronValueBuffer[nnIndex] += synapseWeight * neuronValue; - } - } - - // Clamp the neuron value - for (unsigned long long n = 0; n < population; ++n) - { - char neuronValue = score_reference::clampNeuron(neuronValueBuffer[n]); - neurons[n].value = neuronValue; - } - } - - void runTickSimulation() - { - unsigned long long population = currentANN.population; - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - - // Save the neuron value for comparison - for (unsigned long long i = 0; i < population; ++i) - { - // Backup the neuron value - previousNeuronValue[i] = neurons[i].value; - } - - for (unsigned long long tick = 0; tick < numberOfTicks; ++tick) - { - processTick(); - // Check exit conditions: - // - N ticks have passed (already in for loop) - // - All neuron values are unchanged - // - All output neurons have non-zero values - bool shouldExit = true; - bool allNeuronsUnchanged = true; - bool allOutputNeuronsIsNonZeros = true; - for (unsigned long long n = 0; n < population; ++n) - { - // Neuron unchanged check - if (previousNeuronValue[n] != neurons[n].value) - { - allNeuronsUnchanged = false; - } - - // Ouput neuron value check - if (neurons[n].type == Neuron::kOutput && neurons[n].value == 0) - { - allOutputNeuronsIsNonZeros = false; - } - } - - if (allOutputNeuronsIsNonZeros || allNeuronsUnchanged) - { - break; - } - - // Copy the neuron value - for (unsigned long long n = 0; n < population; ++n) - { - previousNeuronValue[n] = neurons[n].value; - } - } - } - - unsigned int computeNonMatchingOutput() - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - - // Compute the non-matching value R between output neuron value and initial value - // Because the output neuron order never changes, the order is preserved - unsigned int R = 0; - unsigned long long outputIdx = 0; - for (unsigned long long i = 0; i < population; i++) - { - if (neurons[i].type == Neuron::kOutput) - { - if (neurons[i].value != outputNeuronExpectedValue[outputIdx]) - { - R++; - } - outputIdx++; - } - } - return R; - } - - void initInputNeuron() - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - unsigned long long inputNeuronInitIndex = 0; - - char neuronArray[64] = {0}; - for (unsigned long long i = 0; i < population; ++i) - { - // Input will use the init value - if (neurons[i].type == Neuron::kInput) - { - // Prepare new pack - if (inputNeuronInitIndex % 64 == 0) - { - score_reference::extract64Bits( - miningData.inputNeuronRandomNumber[inputNeuronInitIndex / 64], neuronArray); - } - char neuronValue = neuronArray[inputNeuronInitIndex % 64]; - - // Convert value of neuron to trits (keeping 1 as 1, and changing 0 to -1.). - neurons[i].value = (neuronValue == 0) ? -1 : neuronValue; - - inputNeuronInitIndex++; - } - } - } - - void initOutputNeuron() - { - unsigned long long population = currentANN.population; - Neuron* neurons = currentANN.neurons; - for (unsigned long long i = 0; i < population; ++i) - { - if (neurons[i].type == Neuron::kOutput) - { - neurons[i].value = 0; - } - } - } - - void initExpectedOutputNeuron() - { - char neuronArray[64] = {0}; - for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) - { - // Prepare new pack - if (i % 64 == 0) - { - score_reference::extract64Bits(miningData.outputNeuronRandomNumber[i / 64], neuronArray); - } - char neuronValue = neuronArray[i % 64]; - // Convert value of neuron (keeping 1 as 1, and changing 0 to -1.). - outputNeuronExpectedValue[i] = (neuronValue == 0) ? -1 : neuronValue; - } - } - - unsigned int initializeANN(const unsigned char* publicKey, const unsigned char* nonce) - { - unsigned char hash[32]; - unsigned char combined[64]; - memcpy(combined, publicKey, 32); - memcpy(combined + 32, nonce, 32); - KangarooTwelve(combined, 64, hash, 32); - - unsigned long long& population = currentANN.population; - Synapse* synapses = currentANN.synapses; - Neuron* neurons = currentANN.neurons; - - // Initialization - population = numberOfNeurons; - - // Initalize with nonce and public key - score_reference::random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); - - // Randomly choose the positions of neurons types - for (unsigned long long i = 0; i < population; ++i) - { - neuronIndices[i] = i; - neurons[i].type = Neuron::kInput; - } - unsigned long long neuronCount = population; - for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) - { - unsigned long long outputNeuronIdx = initValue.outputNeuronPositions[i] % neuronCount; - - // Fill the neuron type - neurons[neuronIndices[outputNeuronIdx]].type = Neuron::kOutput; - outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; - - // This index is used, copy the end of indices array to current position and decrease - // the number of picking neurons - neuronCount = neuronCount - 1; - neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; - } - - // Synapse weight initialization - for (unsigned long long i = 0; i < (initNumberOfSynapses / 32); ++i) - { - const unsigned long long mask = 0b11; - - for (int j = 0; j < 32; ++j) - { - int shiftVal = j * 2; - unsigned char extractValue = - (unsigned char)((initValue.synapseWeight[i] >> shiftVal) & mask); - switch (extractValue) - { - case 2: - synapses[32 * i + j].weight = -1; - break; - case 3: - synapses[32 * i + j].weight = 1; - break; - default: - synapses[32 * i + j].weight = 0; - } - } - } - - // Init the neuron input and expected output value - memcpy((unsigned char*)&miningData, poolVec.data(), sizeof(miningData)); - - // Init input neuron value and output neuron - initInputNeuron(); - initOutputNeuron(); - - // Init expected output neuron - initExpectedOutputNeuron(); - - // Ticks simulation - runTickSimulation(); - - // Copy the state for rollback later - memcpy(&bestANN, ¤tANN, sizeof(ANN)); - - // Compute R and roll back if neccessary - unsigned int R = computeNonMatchingOutput(); - - return R; - } - - unsigned int computeScore(const unsigned char* publicKey, const unsigned char* nonce) - { - // Initialize - unsigned int bestR = initializeANN(publicKey, nonce); - - for (unsigned long long s = 0; s < numberOfMutations; ++s) - { - - // Do the mutation - mutate(nonce, s); - - // Exit if the number of population reaches the maximum allowed - if (currentANN.population >= populationThreshold) - { - break; - } - - // Ticks simulation - runTickSimulation(); - - // Compute R and roll back if neccessary - unsigned int R = computeNonMatchingOutput(); - if (R > bestR) - { - // Roll back - memcpy(¤tANN, &bestANN, sizeof(bestANN)); - } - else - { - bestR = R; - - // Better R. Save the state - memcpy(&bestANN, ¤tANN, sizeof(bestANN)); - } - - assert(bestANN.population <= populationThreshold); - } - - // Compute score - unsigned int score = numberOfOutputNeurons - bestR; - return score; - } - - // Main function for mining - bool findSolution(const unsigned char* publicKey, const unsigned char* nonce) - { - // Check score - unsigned int score = computeScore(publicKey, nonce); - if (score >= solutionThreshold) - { - return true; - } - - return false; - } -}; - -} // namespace score_hyberidentity \ No newline at end of file diff --git a/test/score_params.h b/test/score_params.h deleted file mode 100644 index 1b7eb564d..000000000 --- a/test/score_params.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include "../src/mining/score_common.h" - -namespace score_params -{ - -static constexpr unsigned int MAX_PARAM_TYPE = 7; - -template -struct ConfigPair -{ - using HyperIdentity = HI; - using Addition = ADD; -}; - -// All configurations -using Config0 = ConfigPair< - score_engine::HyperIdentityParams<64, 64, 50, 64, 178, 50, 36>, - score_engine::AdditionParams<2 * 2, 3, 50, 64, 100, 50, 36> ->; - -using Config1 = ConfigPair< - score_engine::HyperIdentityParams<256, 256, 120, 256, 612, 100, 171>, - score_engine::AdditionParams<4 * 2, 5, 120, 256, 100 + 8 + 5, 100, 171> ->; - -using Config2 = ConfigPair< - score_engine::HyperIdentityParams<512, 512, 150, 512, 1174, 150, 300>, - score_engine::AdditionParams<7 * 2, 8, 150, 512, 150 + 14 + 8, 150, 600> ->; - -using Config3 = ConfigPair< - score_engine::HyperIdentityParams<1024, 1024, 200, 1024, 3000, 200, 600>, - score_engine::AdditionParams<9 * 2, 10, 200, 1024, 200 + 18 + 10, 200, 600> ->; - -using ConfigProfile = ConfigPair< - score_engine::HyperIdentityParams< - HYPERIDENTITY_NUMBER_OF_INPUT_NEURONS, - HYPERIDENTITY_NUMBER_OF_OUTPUT_NEURONS, - HYPERIDENTITY_NUMBER_OF_TICKS, - HYPERIDENTITY_NUMBER_OF_NEIGHBORS, - HYPERIDENTITY_POPULATION_THRESHOLD, - HYPERIDENTITY_NUMBER_OF_MUTATIONS, - HYPERIDENTITY_SOLUTION_THRESHOLD_DEFAULT>, - score_engine::AdditionParams< - ADDITION_NUMBER_OF_INPUT_NEURONS, - ADDITION_NUMBER_OF_OUTPUT_NEURONS, - ADDITION_NUMBER_OF_TICKS, - ADDITION_NUMBER_OF_NEIGHBORS, - ADDITION_POPULATION_THRESHOLD, - ADDITION_NUMBER_OF_MUTATIONS, - ADDITION_SOLUTION_THRESHOLD_DEFAULT> ->; - -using ConfigList = std::tuple; - -static constexpr std::size_t CONFIG_COUNT = std::tuple_size_v; - -using ProfileConfigList = std::tuple; -static constexpr std::size_t PROFILE_CONFIG_COUNT = std::tuple_size_v; - - -} diff --git a/test/score_reference.h b/test/score_reference.h deleted file mode 100644 index 6d55c3f32..000000000 --- a/test/score_reference.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "../src/platform/memory_util.h" -#include "../src/four_q.h" -#include "score_hyperidentity_reference.h" -#include "score_addition_reference.h" -#include - -////////// Original (reference) scoring algorithm \\\\\\\\\\ - -namespace score_reference -{ - -template -struct ScoreReferenceImplementation -{ - using HyperIdentityScore = ::score_hyberidentity_reference::Miner; - using AdditionScore = ::score_addition_reference::Miner; - - std::unique_ptr _hyperIdentityScore; - std::unique_ptr _additionScore; - - void initMemory() - { - _hyperIdentityScore = std::make_unique(); - _additionScore = std::make_unique(); - } - void initMiningData(const unsigned char* miningSeed) - { - _hyperIdentityScore->initialize(miningSeed); - _additionScore->initialize(miningSeed); - } - - unsigned int computeHyperIdentityScore(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool = nullptr) - { - return _hyperIdentityScore->computeScore(publicKey, nonce); - } - - unsigned int computeAdditionScore(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool = nullptr) - { - return _additionScore->computeScore(publicKey, nonce); - } - - // Return score depend on the nonce - unsigned int computeScore(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool = nullptr) - { - if ((nonce[0] & 1) == 0) - { - return computeHyperIdentityScore(publicKey, nonce); - } - else - { - return computeAdditionScore(publicKey, nonce); - } - return 0; - } - -}; - -} diff --git a/test/sorting.cpp b/test/sorting.cpp deleted file mode 100644 index b6baf77b0..000000000 --- a/test/sorting.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#define NO_UEFI - -#include -#include -#include - -#include "gtest/gtest.h" -#include "lib/platform_common/sorting.h" - -constexpr unsigned int MAX_NUM_ELEMENTS = 10000U; -constexpr unsigned int MAX_NUM_TESTS_PER_TYPE = 100U; - -TEST(FixedTypeSortingTest, SortAscendingSimple) -{ - int arr[5] = { 3, 6, 1, 9, 2 }; - - quickSort(arr, 0, 4, SortingOrder::SortAscending); - - EXPECT_EQ(arr[0], 1); - EXPECT_EQ(arr[1], 2); - EXPECT_EQ(arr[2], 3); - EXPECT_EQ(arr[3], 6); - EXPECT_EQ(arr[4], 9); -} - -TEST(FixedTypeSortingTest, SortDescendingSimple) -{ - int arr[5] = { 3, 6, 1, 9, 2 }; - - quickSort(arr, 0, 4, SortingOrder::SortDescending); - - EXPECT_EQ(arr[0], 9); - EXPECT_EQ(arr[1], 6); - EXPECT_EQ(arr[2], 3); - EXPECT_EQ(arr[3], 2); - EXPECT_EQ(arr[4], 1); -} - -template -std::vector prepareData(unsigned int seed) -{ - std::mt19937 gen32(seed); - - unsigned int numberOfBlocks = (sizeof(T) + 3) / 4; - - unsigned int numElements = (gen32() % MAX_NUM_ELEMENTS) + 1; - std::vector vec(numElements, 0); - for (unsigned int i = 0; i < numElements; ++i) - { - // generate 4 bytes at a time until the whole number is generated - for (unsigned int b = 0; b < numberOfBlocks; ++b) - { - vec[i] |= (static_cast(gen32()) << (b * 32)); - } - } - - return vec; -} - -template -void testSortAscending(unsigned int seed) -{ - std::vector vec = prepareData(seed); - - std::vector referenceVec = vec; - std::sort(referenceVec.begin(), referenceVec.end(), std::less<>()); - - quickSort(vec.data(), 0, static_cast(vec.size() - 1), SortingOrder::SortAscending); - - EXPECT_EQ(vec, referenceVec); -} - -template -void testSortDescending(unsigned int seed) -{ - std::vector vec = prepareData(seed); - - std::vector referenceVec = vec; - std::sort(referenceVec.begin(), referenceVec.end(), std::greater<>()); - - quickSort(vec.data(), 0, static_cast(vec.size() - 1), SortingOrder::SortDescending); - - EXPECT_EQ(vec, referenceVec); -} - -template -class SortingTest : public testing::Test {}; - -using testing::Types; - -TYPED_TEST_CASE_P(SortingTest); - -TYPED_TEST_P(SortingTest, SortAscending) -{ - unsigned int metaSeed = 32467; - std::mt19937 gen32(metaSeed); - - for (unsigned int t = 0; t < MAX_NUM_TESTS_PER_TYPE; ++t) - testSortAscending(/*seed=*/gen32()); -} - -TYPED_TEST_P(SortingTest, SortDescending) -{ - unsigned int metaSeed = 45787; - std::mt19937 gen32(metaSeed); - - for (unsigned int t = 0; t < MAX_NUM_TESTS_PER_TYPE; ++t) - testSortDescending(/*seed=*/gen32()); -} - -REGISTER_TYPED_TEST_CASE_P(SortingTest, - SortAscending, - SortDescending -); - -// GTest produces a linker error when using `unsigned short` as test type due to unresolved print function - skip for now. -typedef Types TestTypes; -INSTANTIATE_TYPED_TEST_CASE_P(TypeParamSortingTests, SortingTest, TestTypes); \ No newline at end of file diff --git a/test/spectrum.cpp b/test/spectrum.cpp deleted file mode 100644 index dca62d896..000000000 --- a/test/spectrum.cpp +++ /dev/null @@ -1,409 +0,0 @@ -#define NO_UEFI - -#define PRINT_TEST_INFO 0 - -#include "gtest/gtest.h" - -#include -#include - -#include "logging_test.h" -#include "spectrum/spectrum.h" - -static bool transfer(const m256i& src, const m256i& dst, long long amount) -{ - if (isZero(src) || isZero(dst)) - return false; - - if (amount < 0 || amount > MAX_AMOUNT) - return false; - - const int index = spectrumIndex(src); - if (index < 0) - return false; - - if (!decreaseEnergy(index, amount)) - return false; - - increaseEnergy(dst, amount); - return true; -} - -static m256i getRichestEntity() -{ - m256i pubKey(0, 0, 0, 0); - long long maxBalance = 0; - for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) - { - long long balance = spectrum[i].incomingAmount - spectrum[i].outgoingAmount; - if (balance > maxBalance) - { - maxBalance = balance; - pubKey = spectrum[i].publicKey; - } - } - return pubKey; -} - -static m256i getAnyEntity() -{ - m256i pubKey(0, 0, 0, 0); - for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) - { - long long balance = spectrum[i].incomingAmount - spectrum[i].outgoingAmount; - if (balance > 0) - { - pubKey = spectrum[i].publicKey; - break; - } - } - return pubKey; -} - -static SpectrumInfo checkAndGetInfo() -{ - // Total amount <= total supply - SpectrumInfo si{ 0, 0 }; - for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) - { - long long balance = spectrum[i].incomingAmount - spectrum[i].outgoingAmount; - if (!balance && isZero(spectrum[i].publicKey)) - continue; - EXPECT_GE(balance, 0); - EXPECT_LE(spectrum[i].latestIncomingTransferTick, system.tick); - EXPECT_LE(spectrum[i].latestOutgoingTransferTick, system.tick); - si.totalAmount += balance; - si.numberOfEntities++; - } - EXPECT_LE((unsigned long long)si.totalAmount, MAX_SUPPLY); - EXPECT_EQ(si.totalAmount, spectrumInfo.totalAmount); - EXPECT_EQ(si.numberOfEntities, spectrumInfo.numberOfEntities); - return si; -} - -static void updateAndPrintEntityCategoryPopulations() -{ - updateAndAnalzeEntityCategoryPopulations(); - - // Compute number of entities with 0 balance - unsigned int sumEntityCategoryPopulations = 0; - for (int i = 0; i < entityCategoryCount; ++i) - sumEntityCategoryPopulations += entityCategoryPopulations[i]; - EXPECT_GE(spectrumInfo.numberOfEntities, sumEntityCategoryPopulations); - -#if PRINT_TEST_INFO - unsigned int zeroBalanceEntities = spectrumInfo.numberOfEntities - sumEntityCategoryPopulations; - if (zeroBalanceEntities > 0) - std::cout << " - bin -1: " << zeroBalanceEntities << " entities with zero balance\n"; - - static constexpr int entityCategoryCount = sizeof(entityCategoryPopulations) / sizeof(entityCategoryPopulations[0]); - for (int i = 0; i < entityCategoryCount; ++i) - { - if (entityCategoryPopulations[i]) - { - unsigned long long lowerBound = (1llu << i), upperBound = (1llu << (i + 1)) - 1; - const char* burnIndicator = " + bin "; - if (lowerBound <= dustThresholdBurnAll) - burnIndicator = " - bin "; - else if (lowerBound <= dustThresholdBurnHalf) - burnIndicator = " * bin "; - std::cout << burnIndicator << i << ": " << entityCategoryPopulations[i] << " entities with amount "; - if (i == 0) - std::cout << lowerBound; - else - std::cout << "between " << lowerBound << " and " << upperBound; - std::cout << std::endl; - } - } -#endif -} - -// Spectrum test class for proper init, cleanup, and other repeated tasks -struct SpectrumTest : public LoggingTest -{ - SpectrumInfo beforeAntiDustSpectrumInfo; - std::chrono::steady_clock::time_point beforeAntiDustTimestamp; - bool antiDustCornerCase; - std::mt19937_64 rnd64; - - SpectrumTest(unsigned long long seed = 0) - { - if (!seed) - _rdrand64_step(&seed); - rnd64.seed(seed); - EXPECT_TRUE(initSpectrum()); - EXPECT_TRUE(commonBuffers.init(1)); - system.tick = 15700000; - clearSpectrum(); - antiDustCornerCase = false; - } - - ~SpectrumTest() - { - deinitSpectrum(); - commonBuffers.deinit(); - } - - void clearSpectrum() - { - memset(spectrum, 0, spectrumSizeInBytes); - updateSpectrumInfo(); - } - - void beforeAntiDust() - { - // Check and get current spectrum state - beforeAntiDustSpectrumInfo = checkAndGetInfo(); - - // Print distribution of entity balances -#if PRINT_TEST_INFO - std::cout << "Entity balance distribution before anti-dust:" << std::endl; -#endif - updateAndPrintEntityCategoryPopulations(); - - // Start measuring run-time - beforeAntiDustTimestamp = std::chrono::high_resolution_clock::now(); - } - - void afterAntiDust() - { - checkAndGetInfo(); - - // Print anti-dust info - auto duration_ms = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - beforeAntiDustTimestamp); - std::cout << "Transfer with anti-dust took " << duration_ms << " ms: entities " - << beforeAntiDustSpectrumInfo.numberOfEntities << " -> " << spectrumInfo.numberOfEntities - << " (to " << spectrumInfo.numberOfEntities * 100llu / SPECTRUM_CAPACITY - << "% of capacity); total amount " << beforeAntiDustSpectrumInfo.totalAmount << " -> " << spectrumInfo.totalAmount - << " (" << float(((long long)spectrumInfo.totalAmount - (long long)beforeAntiDustSpectrumInfo.totalAmount) * 10000ll / (long long)beforeAntiDustSpectrumInfo.totalAmount) / 100.0f << "% reduction)" << std::endl; - - // Print distribution of entity balances -#if PRINT_TEST_INFO - std::cout << "Entity balance distribution after anti-dust:" << std::endl; -#endif - updateAndPrintEntityCategoryPopulations(); - - // Anti-dust always cleans up to at least half of the spectrum - EXPECT_LE(spectrumInfo.numberOfEntities, (SPECTRUM_CAPACITY / 2)); - - // Except for improbably corner cases, never burn more than 10% of the spectrum (quite arbitrary factor, just meaning no huge amount) - if (!antiDustCornerCase) - EXPECT_GT(spectrumInfo.totalAmount, beforeAntiDustSpectrumInfo.totalAmount * 9 / 10); - } - - void dust_attack(unsigned int transferMinAmount, unsigned int transferMaxAmount, unsigned int repetitions); -}; - -void SpectrumTest::dust_attack(unsigned int transferMinAmount, unsigned int transferMaxAmount, unsigned int repetitions) -{ - std::cout << "------------------ Dust attack with transfers between " << transferMinAmount << " and " << transferMaxAmount << " qu ------------------\n"; - for (unsigned int rep = 0; rep < repetitions; ++rep) - { - m256i richId = getRichestEntity(); - m256i randomId(rnd64(), rnd64(), rnd64(), rnd64()); - - // Check current spectrum state - checkAndGetInfo(); - - // Fill spectrum with dust attack (until next transfer should trigger anti-dust) - while (spectrumInfo.numberOfEntities < (SPECTRUM_CAPACITY / 2) + (SPECTRUM_CAPACITY / 4)) - { - unsigned int transferAmount = transferMinAmount; - if (transferMinAmount < transferMaxAmount) - transferAmount = (spectrumInfo.numberOfEntities % (transferMaxAmount - transferMinAmount)) + transferMinAmount; - - if (!transfer(richId, randomId, transferAmount)) - richId = getRichestEntity(); - randomId = m256i(rnd64(), rnd64(), rnd64(), rnd64()); - } - - // Should trigger anti-dust - beforeAntiDust(); - ASSERT_TRUE(transfer(richId, randomId, transferMinAmount)); - afterAntiDust(); - } -} - -TEST(TestCoreSpectrum, AntiDustFile) -{ - SpectrumTest test; - if (loadSpectrum(L"spectrum.000")) - { - std::cout << "Spectrum file state before dust attack:" << std::endl; - updateAndPrintEntityCategoryPopulations(); - - SpectrumInfo si1 = checkAndGetInfo(); - test.dust_attack(1, 10, 3); - } - else - { - std::cout << "Spectrum file not found. Skipping file test..." << std::endl; - } -} - -TEST(TestCoreSpectrum, AntiDustOneRichRandomDust) -{ - // Create spectrum with one rich ID - SpectrumTest test; - increaseEnergy(m256i::randomValue(), 1000000000000llu); - - test.dust_attack(1, 1, 1); - test.dust_attack(100, 100, 1); - test.dust_attack(1, 10000, 1); -} - -TEST(TestCoreSpectrum, AntiDustManyRichRandomDust) -{ - // Create spectrum with many rich IDs - SpectrumTest test; - for (int i = 0; i < 10000; i++) - { - increaseEnergy(m256i::randomValue(), i * 100000llu); - } - - test.dust_attack(1, 1000, 1); - test.dust_attack(1, 50, 1); - test.dust_attack(1, 1, 1); -} - -TEST(TestCoreSpectrum, AntiDustEdgeCaseAllInSameBin) -{ - SpectrumTest test; - test.antiDustCornerCase = true; - for (unsigned long long i = 0; i < (SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4); ++i) - { - increaseEnergy(m256i(i, 1, 2, 3), 100llu); - } - - test.beforeAntiDust(); - increaseEnergy(m256i::randomValue(), 100llu); - test.afterAntiDust(); -} - -SpectrumStats getSpectrumStatsLog(long long id) -{ - SpectrumStats res; - qLogger::BlobInfo bi = logger.logBuf.getBlobInfo(id); - EXPECT_EQ(bi.length, LOG_HEADER_SIZE + sizeof(SpectrumStats)); - logger.logBuf.getMany((char*)&res, bi.startIndex + LOG_HEADER_SIZE, sizeof(SpectrumStats)); - return res; -} - -void getDustBurningLog(long long id, char* ptr) -{ - DustBurning res; - qLogger::BlobInfo bi = logger.logBuf.getBlobInfo(id); - logger.logBuf.getMany((char*)&res, bi.startIndex + LOG_HEADER_SIZE, sizeof(DustBurning)); - EXPECT_EQ(bi.length, LOG_HEADER_SIZE + res.messageSize()); - copyMem(ptr, &res, sizeof(DustBurning)); - logger.logBuf.getMany(ptr + sizeof(DustBurning), bi.startIndex + LOG_HEADER_SIZE + sizeof(DustBurning), res.messageSize()); -} - -TEST(TestCoreSpectrum, AntiDustEdgeCaseHugeBinsAndLogging) -{ - SpectrumTest test; - test.antiDustCornerCase = true; - - // build-up spectrum - for (unsigned long long i = 0; i < (SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4); ++i) - { - unsigned long long amount; - if (i < SPECTRUM_CAPACITY / 4) - amount = 100; - else if (i < SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4) - amount = 10000; - increaseEnergy(m256i(i, 1, 2, 3), amount); - } - - // test anti-dust - test.beforeAntiDust(); - increaseEnergy(m256i(SPECTRUM_CAPACITY - 1, 1, 2, 3), 1000llu); - test.afterAntiDust(); - - // check logs: - // first 24 are from building up spectrum - SpectrumStats statData; - SpectrumStats* stats = &statData; - for (int i = 0; i < 24; ++i) - { - statData = getSpectrumStatsLog(i); - EXPECT_EQ(stats->numberOfEntities, i * 524288 + 1); - EXPECT_EQ(stats->entityCategoryPopulations[6], std::min(i * 524288 + 1, int(SPECTRUM_CAPACITY / 4))); - EXPECT_EQ(stats->entityCategoryPopulations[13], (i < 8) ? 0 : (i - 8) * 524288 + 1); - EXPECT_EQ(stats->totalAmount, stats->entityCategoryPopulations[6] * 100llu + stats->entityCategoryPopulations[13] * 10000llu); - - if (i < 16) - { - EXPECT_EQ(stats->dustThresholdBurnAll, 0); - EXPECT_EQ(stats->dustThresholdBurnHalf, 0); - } - else - { - EXPECT_EQ(stats->dustThresholdBurnAll, (2 << 6) - 1); - EXPECT_EQ(stats->dustThresholdBurnHalf, 0); - } - } - - // Check state before anti-dust - statData = getSpectrumStatsLog(24); - SpectrumStats* beforeAntidustStats = &statData; - EXPECT_EQ(beforeAntidustStats->numberOfEntities, 24 * 524288); - EXPECT_EQ(beforeAntidustStats->entityCategoryPopulations[6], SPECTRUM_CAPACITY / 4); - EXPECT_EQ(beforeAntidustStats->entityCategoryPopulations[13], SPECTRUM_CAPACITY / 2); - EXPECT_EQ(beforeAntidustStats->totalAmount, beforeAntidustStats->entityCategoryPopulations[6] * 100llu + beforeAntidustStats->entityCategoryPopulations[13] * 10000llu); - EXPECT_EQ(beforeAntidustStats->dustThresholdBurnAll, (2 << 12) - 1); - EXPECT_EQ(beforeAntidustStats->dustThresholdBurnHalf, (2 << 13) - 1); - - // Check dust burning log messages - int balancesBurned = 0; - int logId = 25; - std::vector buffer; - buffer.resize(1024 * 1024 * 1024); // mimic the scratchpad, allocated 1GiB here - while (balancesBurned < 8 * 1048576) - { - DustBurning* db = (DustBurning*) (buffer.data()); - getDustBurningLog(logId, buffer.data()); - for (int i = 0; i < db->numberOfBurns; ++i) - { - // Of the first 4M entities, all are burned (amount 100), of the following every second is burned. - unsigned long long expectedSpectrumIndex = balancesBurned; - if (balancesBurned >= 4194304) - expectedSpectrumIndex = (balancesBurned - 4194304) * 2 + 4194304; - - DustBurning::Entity& e = db->entity(i); - EXPECT_EQ(e.publicKey, m256i(expectedSpectrumIndex, 1, 2, 3)); - EXPECT_EQ(e.amount, (balancesBurned < 4194304) ? 100 : 10000); - ++balancesBurned; - } - ++logId; - } - - // Finally, check state logged after dust burning (logged before increaing energy / adding new entity) - statData = getSpectrumStatsLog(logId);; - SpectrumStats* afterAntidustStats = &statData; - EXPECT_EQ(afterAntidustStats->numberOfEntities, 4194304); - EXPECT_EQ(afterAntidustStats->entityCategoryPopulations[9], 0); - EXPECT_EQ(afterAntidustStats->entityCategoryPopulations[13], 4 * 1048576); - EXPECT_EQ(afterAntidustStats->totalAmount, afterAntidustStats->entityCategoryPopulations[13] * 10000llu); - EXPECT_EQ(afterAntidustStats->dustThresholdBurnAll, 0); - EXPECT_EQ(afterAntidustStats->dustThresholdBurnHalf, 0); -} - -TEST(TestCoreSpectrum, AntiDustEdgeCaseHugeBinZeroBalance) -{ - SpectrumTest test; - m256i richId(123, 4, 5, 6); - unsigned long long amount = 1000; - increaseEnergy(richId, 100 * amount); - unsigned int spectrum75pct = (SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4); - for (unsigned long long i = 0; i < spectrum75pct - 1; ++i) - { - m256i id(i, 1, 2, 3); - increaseEnergy(id, amount); - decreaseEnergy(spectrumIndex(id), amount); - } - test.beforeAntiDust(); - transfer(richId, m256i(1234, 4, 5, 6), 100 * amount); - test.afterAntiDust(); -} - diff --git a/test/stable_computor_index.cpp b/test/stable_computor_index.cpp deleted file mode 100644 index 3eaab702c..000000000 --- a/test/stable_computor_index.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" -#include "../src/ticking/stable_computor_index.h" - -class StableComputorIndexTest : public ::testing::Test -{ -protected: - m256i futureComputors[NUMBER_OF_COMPUTORS]; - m256i currentComputors[NUMBER_OF_COMPUTORS]; - unsigned char tempBuffer[stableComputorIndexBufferSize()]; - - void SetUp() override - { - memset(futureComputors, 0, sizeof(futureComputors)); - memset(currentComputors, 0, sizeof(currentComputors)); - memset(tempBuffer, 0, sizeof(tempBuffer)); - } - - m256i makeId(int n) - { - m256i id = m256i::zero(); - id.m256i_u64[1] = n; - return id; - } -}; - -// Test: All computors requalify - all should keep their indices -TEST_F(StableComputorIndexTest, AllRequalify) -{ - // Set up current computors with IDs 1 to NUMBER_OF_COMPUTORS - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - currentComputors[i] = makeId(i + 1); - } - - // Future: Same IDs but reversed order (simulating score reordering) - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - futureComputors[i] = makeId(NUMBER_OF_COMPUTORS - i); - } - - bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); - ASSERT_TRUE(result); - - // All should be back to their original indices - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_EQ(futureComputors[i], makeId(i + 1)) << "Index " << i << " mismatch"; - } -} - -// Test: Half computors replaced - requalifying keep index, new fill gaps -TEST_F(StableComputorIndexTest, PartialRequalify) -{ - // Current: ID 1 at idx 0, ID 2 at idx 1, ..., ID 676 at idx 675 - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - currentComputors[i] = makeId(i + 1); - } - - // Future input (scrambled): odd IDs requalify, even IDs replaced by new (1001+) - unsigned int requalifyingId = 1; - unsigned int newId = 1001; - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - if (i % 2 == 0 && requalifyingId <= NUMBER_OF_COMPUTORS) - { - futureComputors[i] = makeId(requalifyingId); - requalifyingId += 2; - } - else - { - futureComputors[i] = makeId(newId++); - } - } - - bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); - ASSERT_TRUE(result); - - // Odd IDs (1,3,5,...) should be at original indices (0,2,4,...) - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i += 2) - { - EXPECT_EQ(futureComputors[i], makeId(i + 1)); - } - - // New IDs (1001,1002,...) should fill gaps at indices (1,3,5,...) - unsigned int expectedNewId = 1001; - for (unsigned int i = 1; i < NUMBER_OF_COMPUTORS; i += 2) - { - EXPECT_EQ(futureComputors[i], makeId(expectedNewId++)); - } -} - -// Test: All computors are new - order preserved -TEST_F(StableComputorIndexTest, AllNew) -{ - // Current: IDs 1 to 676 - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - currentComputors[i] = makeId(i + 1); - } - - // Future: Completely new set (IDs 1000 to 1675) - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - futureComputors[i] = makeId(i + 1000); - } - - bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); - ASSERT_TRUE(result); - - // New computors should fill slots in order - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_EQ(futureComputors[i], makeId(i + 1000)); - } -} - -// Test: Single computor requalifies -TEST_F(StableComputorIndexTest, SingleRequalify) -{ - // Current: IDs 1 to 676 - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - currentComputors[i] = makeId(i + 1); - } - - // Future: Only ID 100 requalifies (at position 0), rest are new - futureComputors[0] = makeId(100); // Requalifying, was at idx 99 - for (unsigned int i = 1; i < NUMBER_OF_COMPUTORS; i++) - { - futureComputors[i] = makeId(i + 1000); // New IDs - } - - bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); - ASSERT_TRUE(result); - - // ID 100 should be at its original index 99 - EXPECT_EQ(futureComputors[99], makeId(100)); - - // New computors fill remaining slots (0-98, 100-675) - unsigned int newIdx = 0; - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - if (i == 99) continue; // Skip the requalifying slot - EXPECT_EQ(futureComputors[i], makeId(newIdx + 1001)) << "New computor at index " << i; - newIdx++; - } -} - - -// Test: First and last computor swap positions in input -TEST_F(StableComputorIndexTest, FirstLastSwap) -{ - // Current: IDs 1 to 676 - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - currentComputors[i] = makeId(i + 1); - } - - // Future: All same IDs, but first and last swapped in input order - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - futureComputors[i] = makeId(i + 1); - } - futureComputors[0] = makeId(NUMBER_OF_COMPUTORS); // Last ID at first position - futureComputors[NUMBER_OF_COMPUTORS - 1] = makeId(1); // First ID at last position - - bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); - ASSERT_TRUE(result); - - // All should be at their original indices regardless of input order - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_EQ(futureComputors[i], makeId(i + 1)) << "Index " << i << " mismatch"; - } -} - -// Test: Realistic scenario - 225 computors change (max allowed) -TEST_F(StableComputorIndexTest, MaxChange225) -{ - // Current: IDs 1 to 676 - for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) - { - currentComputors[i] = makeId(i + 1); - } - - // Future: First 451 (QUORUM) stay, last 225 are replaced with new IDs - for (unsigned int i = 0; i < 451; i++) - { - futureComputors[i] = makeId(i + 1); // Same IDs, possibly different order - } - for (unsigned int i = 451; i < NUMBER_OF_COMPUTORS; i++) - { - futureComputors[i] = makeId(i + 1000); // New IDs - } - - bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); - ASSERT_TRUE(result); - - // First 451 should keep their indices - for (unsigned int i = 0; i < 451; i++) - { - EXPECT_EQ(futureComputors[i], makeId(i + 1)); - } - - // Last 225 slots should have the new IDs - for (unsigned int i = 451; i < NUMBER_OF_COMPUTORS; i++) - { - EXPECT_EQ(futureComputors[i], makeId(i + 1000)); - } -} - diff --git a/test/stdlib_impl.cpp b/test/stdlib_impl.cpp deleted file mode 100644 index 47c64bd20..000000000 --- a/test/stdlib_impl.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Implements NO_UEFI versions of several functions that require cstdlib functions. -// Having them in a separate cpp file avoids the name clash between cstdlib's system and Qubic's system. - -#include -#include -#include - -#define NO_UEFI - -#include "platform/time.h" - - -void setMem(void* buffer, unsigned long long size, unsigned char value) -{ - memset(buffer, value, size); -} - -void copyMem(void* destination, const void* source, unsigned long long length) -{ - memcpy(destination, source, length); -} - -bool allocatePool(unsigned long long size, void** buffer) -{ - void* ptr = malloc(size); - if (ptr) - { - *buffer = ptr; - return true; - } - return false; -} - -void freePool(void* buffer) -{ - free(buffer); -} - -void updateTime() -{ - std::time_t t = std::time(nullptr); - std::tm* tm = std::gmtime(&t); - utcTime.Year = tm->tm_year + 1900; - utcTime.Month = tm->tm_mon + 1; - utcTime.Day = tm->tm_mday; - utcTime.Hour = tm->tm_hour; - utcTime.Minute = tm->tm_min; - utcTime.Second = tm->tm_sec; - utcTime.Nanosecond = 0; - utcTime.TimeZone = 0; - utcTime.Daylight = 0; -} - -unsigned long long now_ms() -{ - std::time_t t = std::time(nullptr); - std::tm* tm = std::gmtime(&t); - return ms(unsigned char(tm->tm_year % 100), tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, 0); -} \ No newline at end of file diff --git a/test/test.vcxproj b/test/test.vcxproj deleted file mode 100644 index b88db9fcf..000000000 --- a/test/test.vcxproj +++ /dev/null @@ -1,204 +0,0 @@ - - - - - Debug - x64 - - - ReleaseAVX512 - x64 - - - Release - x64 - - - - {30e8e249-6b00-4575-bcdf-be2445d5e099} - Win32Proj - 10.0 - Application - v143 - Unicode - - - - - - - - - - - - - Disabled - X64;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebugDLL - Level3 - false - stdcpp20 - ../src;$(MSBuildProjectDirectory);$(MSBuildProjectDirectory)\..\;%(AdditionalIncludeDirectories) - AdvancedVectorExtensions512 - true - true - - - true - Console - - - xcopy /E /I /Y "$(ProjectDir)data" "$(TargetDir)data" - - - - - X64;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDLL - Level3 - ProgramDatabase - false - stdcpp20 - AdvancedVectorExtensions2 - AnySuitable - true - Speed - true - ../src;$(MSBuildProjectDirectory);$(MSBuildProjectDirectory)\..\;%(AdditionalIncludeDirectories) - false - true - true - - - true - Console - true - true - Default - - - xcopy /E /I /Y "$(ProjectDir)data" "$(TargetDir)data" - - - - - X64;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - MultiThreadedDLL - Level3 - ProgramDatabase - false - stdcpp20 - AdvancedVectorExtensions512 - AnySuitable - true - Speed - true - ../src;$(MSBuildProjectDirectory);$(MSBuildProjectDirectory)\..\;%(AdditionalIncludeDirectories) - false - true - true - - - true - Console - true - true - Default - - - xcopy /E /I /Y "$(ProjectDir)data" "$(TargetDir)data" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {61270221-bd41-438e-8f74-48aec8c3f9a5} - - - {88b4cda8-8248-44d0-848e-0e938a2aad6d} - - - - - - - - - - Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". - - - - \ No newline at end of file diff --git a/test/test.vcxproj.filters b/test/test.vcxproj.filters deleted file mode 100644 index 589346059..000000000 --- a/test/test.vcxproj.filters +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {b544a744-99a4-4a9d-b445-211aabef63f2} - - - - - core - - - diff --git a/test/test.vcxproj.user b/test/test.vcxproj.user deleted file mode 100644 index d3c65a501..000000000 --- a/test/test.vcxproj.user +++ /dev/null @@ -1,12 +0,0 @@ - - - - --gtest_break_on_failure - WindowsLocalDebugger - - - - - WindowsLocalDebugger - - \ No newline at end of file diff --git a/test/test_util.h b/test/test_util.h deleted file mode 100644 index c55e9d118..000000000 --- a/test/test_util.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "gtest/gtest.h" - -#include "four_q.h" - - -static std::ostream& operator<<(std::ostream& s, const m256i& v) -{ - CHAR16 identityWchar[61]; - char identityChar[61]; - getIdentity(v.m256i_u8, identityWchar, false); - size_t size; - wcstombs_s(&size, identityChar, identityWchar, 61); - s << identityChar; - return s; -} - -static unsigned long long assetNameFromString(const char* assetName) -{ - size_t n = strlen(assetName); - EXPECT_LE(n, 7); - unsigned long long integer = 0; - copyMem(&integer, assetName, n); - return integer; -} - -static std::string assetNameFromInt64(unsigned long long assetName) -{ - char buffer[8]; - copyMem(&buffer, &assetName, sizeof(assetName)); - buffer[7] = 0; - return buffer; -} \ No newline at end of file diff --git a/test/tick_storage.cpp b/test/tick_storage.cpp deleted file mode 100644 index 103390c99..000000000 --- a/test/tick_storage.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#include "../src/public_settings.h" -#undef MAX_NUMBER_OF_TICKS_PER_EPOCH -#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 -#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH -#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 5 -#include "../src/ticking/tick_storage.h" - -#include - - -class TestTickStorage : public TickStorage -{ - unsigned char transactionBuffer[MAX_TRANSACTION_SIZE]; -public: - - void addTransaction(unsigned int tick, unsigned int transactionIdx, unsigned int inputSize) - { - ASSERT_TRUE(inputSize <= MAX_INPUT_SIZE); - Transaction* transaction = (Transaction*)transactionBuffer; - transaction->amount = 10; - transaction->destinationPublicKey.setRandomValue(); - transaction->sourcePublicKey.setRandomValue(); - transaction->inputSize = inputSize; - transaction->inputType = 0; - transaction->tick = tick; - - unsigned int transactionSize = transaction->totalSize(); - - auto* offsets = tickTransactionOffsets.getByTickInCurrentEpoch(tick); - if (nextTickTransactionOffset + transactionSize <= tickTransactions.storageSpaceCurrentEpoch) - { - EXPECT_EQ(offsets[transactionIdx], 0); - offsets[transactionIdx] = nextTickTransactionOffset; - copyMem(tickTransactions(nextTickTransactionOffset), transaction, transactionSize); - nextTickTransactionOffset += transactionSize; - } - } - - void addTick(unsigned int tick, unsigned int seed, unsigned short maxTransactions) - { - // use pseudo-random sequence - std::mt19937 gen32(seed); - - // add tick data - TickData& td = tickData.getByTickInCurrentEpoch(tick); - td.epoch = 1234; - td.tick = tick; - - // add computor ticks - Tick* computorTicks = ticks.getByTickInCurrentEpoch(tick); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - computorTicks[i].epoch = 1234; - computorTicks[i].computorIndex = i; - computorTicks[i].tick = tick; - computorTicks[i].prevResourceTestingDigest = gen32(); - } - - // add transactions of tick - unsigned int transactionNum = gen32() % (maxTransactions + 1); - unsigned int orderMode = gen32() % 2; - unsigned int transactionSlot; - for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) - { - if (orderMode == 0) - transactionSlot = transaction; // standard order - else if (orderMode == 1) - transactionSlot = transactionNum - 1 - transaction; // backward order - addTransaction(tick, transactionSlot, gen32() % MAX_INPUT_SIZE); - } - checkStateConsistencyWithAssert(); - } - - void checkTick(unsigned int tick, unsigned int seed, unsigned short maxTransactions, bool previousEpoch = false) - { - // only last ticks of previous epoch are kept in storage -> check okay - if (previousEpoch && !tickInPreviousEpochStorage(tick)) - return; - - // use pseudo-random sequence - std::mt19937 gen32(seed); - - // check tick data - TickData& td = previousEpoch ? tickData.getByTickInPreviousEpoch(tick) : tickData.getByTickInCurrentEpoch(tick); - EXPECT_EQ((int)td.epoch, (int)1234); - EXPECT_EQ(td.tick, tick); - - // check computor ticks - Tick* computorTicks = previousEpoch ? ticks.getByTickInPreviousEpoch(tick) : ticks.getByTickInCurrentEpoch(tick); - for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) - { - EXPECT_EQ((int)computorTicks[i].epoch, (int)1234); - EXPECT_EQ((int)computorTicks[i].computorIndex, (int)i); - EXPECT_EQ(computorTicks[i].tick, tick); - EXPECT_EQ(computorTicks[i].prevResourceTestingDigest, gen32()); - } - - // check transactions of tick - { - const auto* offsets = previousEpoch ? tickTransactionOffsets.getByTickInPreviousEpoch(tick) : tickTransactionOffsets.getByTickInCurrentEpoch(tick); - unsigned int transactionNum = gen32() % (maxTransactions + 1); - unsigned int orderMode = gen32() % 2; - unsigned int transactionSlot; - - for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) - { - int expectedInputSize = (int)(gen32() % MAX_INPUT_SIZE); - - if (orderMode == 0) - transactionSlot = transaction; // standard order - else if (orderMode == 1) - transactionSlot = transactionNum - 1 - transaction; // backward order - - // If previousEpoch, some transactions at the beginning may not have fit into the storage and are missing -> check okay - // If current epoch, some may be missing at he end due to limited storage -> check okay - if (!offsets[transactionSlot]) - continue; - - Transaction* tp = tickTransactions(offsets[transactionSlot]); - EXPECT_TRUE(tp->checkValidity()); - EXPECT_EQ(tp->tick, tick); - EXPECT_EQ((int)tp->inputSize, expectedInputSize); - } - } - } -}; - - -TEST(TestCoreTickStorage, EpochTransition) -{ - TestTickStorage ts; - unsigned int seed = 42; - - // use pseudo-random sequence - std::mt19937 gen32(seed); - - // 5x test with running 2 epoch transitions - for (int testIdx = 0; testIdx < 6; ++testIdx) - { - // first, test case of having no transactions - unsigned short maxTransactions = (testIdx == 0) ? 0 : NUMBER_OF_TRANSACTIONS_PER_TICK; - - ts.init(); - ts.checkStateConsistencyWithAssert(); - - const int firstEpochTicks = gen32() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); - const int secondEpochTicks = gen32() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); - const int thirdEpochTicks = gen32() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); - const unsigned int firstEpochTick0 = gen32() % 10000000; - const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; - const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; - unsigned int firstEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; - unsigned int secondEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; - unsigned int thirdEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; - for (int i = 0; i < firstEpochTicks; ++i) - firstEpochSeeds[i] = gen32(); - for (int i = 0; i < secondEpochTicks; ++i) - secondEpochSeeds[i] = gen32(); - for (int i = 0; i < thirdEpochTicks; ++i) - thirdEpochSeeds[i] = gen32(); - - // first epoch - ts.beginEpoch(firstEpochTick0); - ts.checkStateConsistencyWithAssert(); - - // add ticks - for (int i = 0; i < firstEpochTicks; ++i) - ts.addTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - - // check ticks - for (int i = 0; i < firstEpochTicks; ++i) - ts.checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); - - // Epoch transistion - ts.beginEpoch(secondEpochTick0); - ts.checkStateConsistencyWithAssert(); - - // add ticks - for (int i = 0; i < secondEpochTicks; ++i) - ts.addTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); - - // check ticks - for (int i = 0; i < secondEpochTicks; ++i) - ts.checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); - bool previousEpoch = true; - for (int i = 0; i < firstEpochTicks; ++i) - ts.checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, previousEpoch); - - // Epoch transistion - ts.beginEpoch(thirdEpochTick0); - ts.checkStateConsistencyWithAssert(); - - // add ticks - for (int i = 0; i < thirdEpochTicks; ++i) - ts.addTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); - - // check ticks - for (int i = 0; i < thirdEpochTicks; ++i) - ts.checkTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); - for (int i = 0; i < secondEpochTicks; ++i) - ts.checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, previousEpoch); - - ts.deinit(); - } -} diff --git a/test/time.cpp b/test/time.cpp deleted file mode 100644 index e99ad2a0e..000000000 --- a/test/time.cpp +++ /dev/null @@ -1,313 +0,0 @@ -#include "gtest/gtest.h" -#define NO_UEFI - -#include "platform/time.h" - -#include -#include - -// Convert TimeDate to std::tm -std::tm toTm(const TimeDate& t) -{ - std::tm tmTime{}; - tmTime.tm_sec = t.second; - tmTime.tm_min = t.minute; - tmTime.tm_hour = t.hour; - tmTime.tm_mday = t.day; - tmTime.tm_mon = t.month - 1; // tm_mon is 0-based - tmTime.tm_year = t.year + 100; // tm_year is years since 1900 - return tmTime; -} - -// Compute difference in seconds between two TimeDate values -long long stdSecondsBetween(const TimeDate& a, const TimeDate& b) -{ - std::tm tmA = toTm(a); - std::tm tmB = toTm(b); - std::time_t timeA = std::mktime(&tmA); - std::time_t timeB = std::mktime(&tmB); - return static_cast(std::difftime(timeB, timeA)); -} - -TEST(TestTime, DiffDateSecond) -{ - TimeDate A; - TimeDate B; - - // Normal - A.second = 56; - A.minute = 12; - A.hour = 4; - A.day = 7; - A.month = 5; - A.year = 1; - - B.second = 56; - B.minute = 12; - B.hour = 4; - B.day = 7; - B.month = 5; - B.year = 11; - - EXPECT_EQ(stdSecondsBetween(A, B), diffDateSecond(A, B)); - - // A is gap, B is not - A.second = 56; - A.minute = 12; - A.hour = 4; - A.day = 7; - A.month = 3; - A.year = 12; - - B.second = 56; - B.minute = 12; - B.hour = 4; - B.day = 7; - B.month = 5; - B.year = 21; - - // A is not gap, B is - A.second = 56; - A.minute = 12; - A.hour = 4; - A.day = 21; - A.month = 3; - A.year = 13; - - B.second = 56; - B.minute = 12; - B.hour = 4; - B.day = 1; - B.month = 3; - B.year = 24; - EXPECT_EQ(stdSecondsBetween(A, B), diffDateSecond(A, B)); - - // A,B is gap - A.second = 56; - A.minute = 12; - A.hour = 4; - A.day = 21; - A.month = 3; - A.year = 12; - - B.second = 56; - B.minute = 12; - B.hour = 4; - B.day = 1; - B.month = 3; - B.year = 24; - - EXPECT_EQ(stdSecondsBetween(A, B), diffDateSecond(A, B)); - -} - -TEST(TestTime, Unpack) -{ - unsigned int packedWeekTime; - WeekDay wd; - - // Thurs 15:20:30 - packedWeekTime = 0x040F141E; - wd = convertWeekTimeFromPackedData(packedWeekTime); - EXPECT_EQ(wd.dayOfWeek, 4); - EXPECT_EQ(wd.hour, 15); - EXPECT_EQ(wd.minute, 20); - EXPECT_EQ(wd.second, 30); - - // Sat 12:00:00 - packedWeekTime = 0x060C0000; - wd = convertWeekTimeFromPackedData(packedWeekTime); - EXPECT_EQ(wd.dayOfWeek, 6); - EXPECT_EQ(wd.hour, 12); - EXPECT_EQ(wd.minute, 0); - EXPECT_EQ(wd.second, 0); - - // Sun 12:00:00 - packedWeekTime = 0x000C0000; - wd = convertWeekTimeFromPackedData(packedWeekTime); - EXPECT_EQ(wd.dayOfWeek, 0); - EXPECT_EQ(wd.hour, 12); - EXPECT_EQ(wd.minute, 0); - EXPECT_EQ(wd.second, 0); -} - -TEST(TestTime, WeekDay) -{ - TimeDate A; - - // Normal - A.second = 56; - A.minute = 12; - A.hour = 4; - A.day = 7; - A.month = 5; - A.year = 1; - - std::tm tmA = toTm(A); - std::mktime(&tmA); - - EXPECT_EQ(getDayOfWeek(A.day, A.month, A.year), tmA.tm_wday); - -} - -TEST(TestTime, Comparison) -{ - TimeDate A; - TimeDate B; - - // Normal - A.second = 56; - A.minute = 12; - A.hour = 4; - A.day = 7; - A.month = 5; - A.year = 1; - - B.second = 56; - B.minute = 12; - B.hour = 4; - B.day = 7; - B.month = 5; - B.year = 11; - - EXPECT_EQ(compareTimeDate(A, A), 0); - EXPECT_EQ(compareTimeDate(B, A), 1); - EXPECT_EQ(compareTimeDate(A, B), -1); - - // Test week day in range - // - // Monday in [Sunday, Tuesday] - EXPECT_TRUE(isWeekDayInRange(1, 0, 2)); - - // Sunday is in [Saturday, Tuesday] - EXPECT_TRUE(isWeekDayInRange(0, 6, 2)); - - // Wednesday is in [Saturday, Thursday] - EXPECT_TRUE(isWeekDayInRange(3, 6, 4)); - - // Wednesday is in [Saturday, Wednesday] - EXPECT_TRUE(isWeekDayInRange(3, 6, 3)); - - // Wednesday is in [Wednesday, Saturday] - EXPECT_TRUE(isWeekDayInRange(3, 3, 6)); - - // Wednesday is not in [Sunday, Tuesday] - EXPECT_FALSE(isWeekDayInRange(3, 0, 2)); - - // Thursday is not in [Saturday, Tuesday] - EXPECT_FALSE(isWeekDayInRange(4, 6, 2)); - - WeekDay weekDay; - WeekDay startWeekDay, endWeekDay; - - // [Saturday 12:50:20] - startWeekDay.second = 20; - startWeekDay.minute = 50; - startWeekDay.hour = 12; - startWeekDay.dayOfWeek = 6; - - // [Monday 08:05:15] - endWeekDay.second = 15; - endWeekDay.minute = 5; - endWeekDay.hour = 8; - endWeekDay.dayOfWeek = 1; - - // [Sunday 04:12:56] - weekDay.second = 56; - weekDay.minute = 12; - weekDay.hour = 4; - weekDay.dayOfWeek = 0; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Tuesday 04:12:56] - weekDay.dayOfWeek = 2; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - //**** Test lower bound of week day - // [Saturday 12:50:21] - weekDay.dayOfWeek = 6; - weekDay.second = 21; - weekDay.minute = 50; - weekDay.hour = 12; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Saturday 12:51:20] - weekDay.dayOfWeek = 6; - weekDay.second = 20; - weekDay.minute = 51; - weekDay.hour = 12; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Saturday 13:50:20] - weekDay.dayOfWeek = 6; - weekDay.second = 20; - weekDay.minute = 50; - weekDay.hour = 13; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Saturday 12:50:19] - weekDay.dayOfWeek = 6; - weekDay.second = 19; - weekDay.minute = 50; - weekDay.hour = 12; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Saturday 12:49:20] - weekDay.dayOfWeek = 6; - weekDay.second = 20; - weekDay.minute = 49; - weekDay.hour = 12; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Saturday 11:50:20] - weekDay.dayOfWeek = 6; - weekDay.second = 20; - weekDay.minute = 50; - weekDay.hour = 11; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - //**** Test upper bound of week day - // [Monday 08:05:14] - weekDay.second = 14; - weekDay.minute = 5; - weekDay.hour = 8; - weekDay.dayOfWeek = 1; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Monday 08:04:15] - weekDay.second = 15; - weekDay.minute = 04; - weekDay.hour = 8; - weekDay.dayOfWeek = 1; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Monday 07:05:15] - weekDay.second = 15; - weekDay.minute = 5; - weekDay.hour = 7; - weekDay.dayOfWeek = 1; - EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Monday 08:05:16] - weekDay.second = 16; - weekDay.minute = 5; - weekDay.hour = 8; - weekDay.dayOfWeek = 1; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Monday 08:06:15] - weekDay.second = 15; - weekDay.minute = 6; - weekDay.hour = 8; - weekDay.dayOfWeek = 1; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - - // [Monday 09:06:15] - weekDay.second = 15; - weekDay.minute = 5; - weekDay.hour = 9; - weekDay.dayOfWeek = 1; - EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); - -} - diff --git a/test/tx_status_request.cpp b/test/tx_status_request.cpp deleted file mode 100644 index b72084010..000000000 --- a/test/tx_status_request.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#define system qubicSystemStruct -#define Peer void -void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data); - -#include "../src/public_settings.h" -#undef MAX_NUMBER_OF_TICKS_PER_EPOCH -#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 -#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH -#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 32 -#undef ADDON_TX_STATUS_REQUEST -#define ADDON_TX_STATUS_REQUEST 1 -#include "../src/addons/tx_status_request.h" - -#include - -unsigned int numberOfTransactions = 0; - - -// Add tick with random transactions, return whether all transactions could be stored -static bool addTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions) -{ - system.tick = tick; - - // set start index of tick transactions - txStatusData.tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; - - // use pseudo-random sequence for generating test data - std::mt19937_64 gen64(seed); - - // add transactions of tick - unsigned int transactionNum = gen64() % (maxTransactions + 1); - for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) - { - ++numberOfTransactions; - m256i digest(gen64(), gen64(), gen64(), gen64()); - if (!saveConfirmedTx(numberOfTransactions - 1, gen64() % 2, system.tick, digest)) - return false; - } - - return true; -} - - -struct { - RequestResponseHeader header; - RequestTxStatus payload; -} requestMessage; - -RespondTxStatus responseMessage; - - -static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data) -{ - const RespondTxStatus* txStatus = (const RespondTxStatus*)data; - - EXPECT_EQ(type, RespondTxStatus::type()); - EXPECT_EQ(dejavu, requestMessage.header.dejavu()); - EXPECT_EQ(dataSize, txStatus->size()); - - copyMem(&responseMessage, txStatus, txStatus->size()); -} - -static void checkTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions, bool fullyStoredTick, bool previousEpoch) -{ - // Ensure that we do not skip processRequestConfirmedTx() - if (system.tick <= tick) - system.tick = tick + 1; - - // check tick number dependent on if it is previous epoch - if (previousEpoch) - { - ASSERT(txStatusData.confirmedTxPreviousEpochBeginTick != 0); - EXPECT_LT(tick, txStatusData.confirmedTxCurrentEpochBeginTick); - if (tick < txStatusData.confirmedTxPreviousEpochBeginTick) - { - // tick not available -> okay - return; - } - } - else - { - ASSERT(tick >= txStatusData.confirmedTxCurrentEpochBeginTick && tick < txStatusData.confirmedTxCurrentEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH); - } - - // prepare request message - requestMessage.header.checkAndSetSize(sizeof(requestMessage)); - requestMessage.header.setType(RequestTxStatus::type()); - requestMessage.header.setDejavu(seed % UINT_MAX); - requestMessage.payload.tick = tick; - - // get status of tick transactions - responseMessage.tick = responseMessage.currentTickOfNode = 0; - processRequestConfirmedTx(0, nullptr, &requestMessage.header); - - // check that we received right data - EXPECT_EQ(responseMessage.tick, tick); - EXPECT_EQ(responseMessage.currentTickOfNode, system.tick); - - // use pseudo-random sequence for generating test data - std::mt19937_64 gen64(seed); - - // check number of transactions - unsigned int transactionNum = gen64() % (maxTransactions + 1); - if (fullyStoredTick) - { - EXPECT_EQ(responseMessage.txCount, transactionNum); - } - else - { - EXPECT_LE(responseMessage.txCount, transactionNum); - } - - // check tick's transaction digests and money flows - for (unsigned int transaction = 0; transaction < responseMessage.txCount; ++transaction) - { - m256i digest(gen64(), gen64(), gen64(), gen64()); - EXPECT_EQ(responseMessage.txDigests[transaction], digest); // CAUTION: responseMessage.txDigests only available up to responseMessage.txCount - - unsigned char receivedMoneyFlow = (responseMessage.moneyFlew[transaction / 8] >> (transaction % 8)) & 1; - unsigned char expectedMoneyFlew = gen64() % 2; - EXPECT_EQ(receivedMoneyFlow, expectedMoneyFlew); - } -} - - -TEST(TestCoreTxStatusRequestAddOn, EpochTransition) -{ - unsigned long long seed = 42; - - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - // 5x test with running 2 epoch transitions - for (int testIdx = 0; testIdx < 20; ++testIdx) - { - // first, test case of having no transactions, then of having few transaction, later of having many transactions - unsigned short maxTransactions = 0; - if (testIdx == 1) - maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK / 4; - else if (testIdx > 1) - maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK; - - initTxStatusRequestAddOn(); - - const int firstEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); - const int secondEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); - const int thirdEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); - const unsigned int firstEpochTick0 = gen64() % 10000000; - const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; - const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; - unsigned long long firstEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; - unsigned long long secondEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; - unsigned long long thirdEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; - for (int i = 0; i < firstEpochTicks; ++i) - firstEpochSeeds[i] = gen64(); - for (int i = 0; i < secondEpochTicks; ++i) - secondEpochSeeds[i] = gen64(); - for (int i = 0; i < thirdEpochTicks; ++i) - thirdEpochSeeds[i] = gen64(); - - // first epoch - numberOfTransactions = 0; - system.initialTick = firstEpochTick0; - beginEpochTxStatusRequestAddOn(firstEpochTick0); - - // add ticks - int firstEpochLastFullyStoredTick = -1; - for (int i = 0; i < firstEpochTicks; ++i) - { - if (addTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions)) - firstEpochLastFullyStoredTick = i; - } - - // check ticks - bool previousEpoch = true; - for (int i = 0; i < firstEpochTicks; ++i) - checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, i < firstEpochLastFullyStoredTick, !previousEpoch); - - // Epoch transistion - numberOfTransactions = 0; - system.initialTick = secondEpochTick0; - beginEpochTxStatusRequestAddOn(secondEpochTick0); - - // add ticks - int secondEpochLastFullyStoredTick = -1; - for (int i = 0; i < secondEpochTicks; ++i) - { - if (addTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions)) - secondEpochLastFullyStoredTick = i; - } - - // check ticks - for (int i = 0; i < secondEpochTicks; ++i) - checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, !previousEpoch); - for (int i = 0; i < firstEpochTicks; ++i) - checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, i < firstEpochLastFullyStoredTick, previousEpoch); - - // Epoch transistion - numberOfTransactions = 0; - system.initialTick = thirdEpochTick0; - beginEpochTxStatusRequestAddOn(thirdEpochTick0); - - // add ticks - int thirdEpochLastFullyStoredTick = -1; - for (int i = 0; i < thirdEpochTicks; ++i) - { - if (addTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions)) - thirdEpochLastFullyStoredTick = i; - } - - // check ticks - for (int i = 0; i < thirdEpochTicks; ++i) - checkTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions, i < thirdEpochLastFullyStoredTick, !previousEpoch); - for (int i = 0; i < secondEpochTicks; ++i) - checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, previousEpoch); - - deinitTxStatusRequestAddOn(); - } -} - diff --git a/test/uint128.cpp b/test/uint128.cpp deleted file mode 100644 index a2b273e58..000000000 --- a/test/uint128.cpp +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2013 - 2018 Jason Lee @ calccrypto at gmail.com -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp - -#define NO_UEFI - -#include "gtest/gtest.h" -#include "../src/platform/uint128.h" - - -TEST(Uint128Arithmetic, add){ - uint128_t low (0, 1); - uint128_t high(1, 0); - - EXPECT_EQ(low + low, uint128_t(0, 2)); - EXPECT_EQ(low + high, uint128_t(1, 1)); - EXPECT_EQ(high + high, uint128_t(2, 0)); - - EXPECT_EQ(low += low, uint128_t(0, 2)); - EXPECT_EQ(low += high, uint128_t(1, 2)); - EXPECT_EQ(high += low, uint128_t(2, 2)); -} - -// https://github.com/calccrypto/uint128_t/blob/master/tests/testcases/sub.cpp -TEST(Uint128Arithmetic, subtract){ - uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); - uint128_t small(1); - - EXPECT_EQ(small - small, uint128_t(0, 0)); - EXPECT_EQ(small - big, uint128_t(0, 2)); - EXPECT_EQ(big - small, uint128_t(0xffffffffffffffffULL, 0xfffffffffffffffeULL)); - EXPECT_EQ(big - big, uint128_t(0, 0)); -} - -// https://github.com/calccrypto/uint128_t/blob/master/tests/testcases/div.cpp -TEST(Uint128Arithmetic, divide){ - const uint128_t big_val (0xfedbca9876543210ULL); - const uint128_t small_val(0xffffULL); - const uint128_t res_val (0xfedcc9753fc9ULL); - - EXPECT_EQ(small_val / small_val, uint128_t(0, 1)); - EXPECT_EQ(small_val / big_val, uint128_t(0, 0)); - - EXPECT_EQ(big_val / big_val, uint128_t(0, 1)); - - // EXPECT_THROW(uint128_t(1) / uint128_t(0), std::domain_error); -} - -TEST(Uint128Arithmetic, multiply){ - uint128_t val(0xfedbca9876543210ULL); - - EXPECT_EQ(val * val, uint128_t(0xfdb8e2bacbfe7cefULL, 0x010e6cd7a44a4100ULL)); - - const uint128_t zero = 0; - EXPECT_EQ(val * zero, zero); - EXPECT_EQ(zero * val, zero); - - const uint128_t one = 1; - EXPECT_EQ(val * one, val); - EXPECT_EQ(one * val, val); -} - -TEST(Uint128Comparison, equals){ - EXPECT_EQ( (uint128_t(0xdeadbeefULL) == uint128_t(0xdeadbeefULL)), true); - EXPECT_EQ(!(uint128_t(0xdeadbeefULL) == uint128_t(0xfee1baadULL)), true); -} - -TEST(Uint128Comparison, less_than){ - const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); - const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); - - EXPECT_EQ(small < small, false); - EXPECT_EQ(small < big, true); - - EXPECT_EQ(big < small, false); - EXPECT_EQ(big < big, false); -} - - -TEST(Uint128Comparison, greater_than){ - const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); - const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); - - EXPECT_EQ(small > small, false); - EXPECT_EQ(small > big, false); - - EXPECT_EQ(big > small, true); - EXPECT_EQ(big > big, false); -} - -TEST(Uint128Comparison, greater_than_or_equals){ - const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); - const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); - - EXPECT_EQ(small >= small, true); - EXPECT_EQ(small >= big, false); - - EXPECT_EQ(big >= small, true); - EXPECT_EQ(big >= big, true); -} - -TEST(Uint128Comparison, less_than_or_equals){ - const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); - const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); - - EXPECT_EQ(small <= small, true); - EXPECT_EQ(small <= big, true); - - EXPECT_EQ(big <= small, false); - EXPECT_EQ(big <= big, true); -} - -TEST(Uint128BitShift, left){ - // operator<< - uint128_t val(0x1); - uint64_t exp_val = 1; - for(uint8_t i = 0; i < 64; i++){ - EXPECT_EQ(val << uint128_t(i), uint128_t(exp_val << i)); - } - - uint128_t zero(0); - for(uint8_t i = 0; i < 64; i++){ - EXPECT_EQ(zero << uint128_t(i), uint128_t(0)); - } - - // operator<<= - for(uint8_t i = 0; i < 63; i++){ // 1 is already a bit - EXPECT_EQ(val <<= uint128_t(1), uint128_t(exp_val <<= 1)); - } - - for(uint8_t i = 0; i < 63; i++){ - EXPECT_EQ(zero <<= uint128_t(1), uint128_t(0)); - } -} - -TEST(Uint128BitShift, right){ - // operator>> - uint128_t val(0xffffffffffffffffULL); - uint64_t exp = 0xffffffffffffffffULL; - for(uint8_t i = 0; i < 64; i++){ - EXPECT_EQ(val >> uint128_t(i), uint128_t(exp >> i)); - } - - uint128_t zero(0); - for(uint8_t i = 0; i < 64; i++){ - EXPECT_EQ(zero >> uint128_t(i), uint128_t(0)); - } - - // operator>>= - for(uint8_t i = 0; i < 64; i++){ - EXPECT_EQ(val >>= uint128_t(1), uint128_t(exp >>= 1)); - } - - for(uint8_t i = 0; i < 64; i++){ - EXPECT_EQ(zero >>= uint128_t(1), uint128_t(0)); - } -} - -TEST(Uint128BitWise, and){ - uint128_t t ((bool) true); - uint128_t f ((bool) false); - uint128_t u8 ((uint8_t) 0xaaULL); - uint128_t u16((uint16_t) 0xaaaaULL); - uint128_t u32((uint32_t) 0xaaaaaaaaULL); - uint128_t u64((uint64_t) 0xaaaaaaaaaaaaaaaaULL); - - const uint128_t val(0xf0f0f0f0f0f0f0f0ULL, 0xf0f0f0f0f0f0f0f0ULL); - - EXPECT_EQ(t & val, uint128_t(0)); - EXPECT_EQ(f & val, uint128_t(0)); - EXPECT_EQ(u8 & val, uint128_t(0xa0ULL)); - EXPECT_EQ(u16 & val, uint128_t(0xa0a0ULL)); - EXPECT_EQ(u32 & val, uint128_t(0xa0a0a0a0ULL)); - EXPECT_EQ(u64 & val, uint128_t(0xa0a0a0a0a0a0a0a0ULL)); - - EXPECT_EQ(t &= val, uint128_t(0x0ULL)); - EXPECT_EQ(f &= val, uint128_t(0x0ULL)); - EXPECT_EQ(u8 &= val, uint128_t(0xa0ULL)); - EXPECT_EQ(u16 &= val, uint128_t(0xa0a0ULL)); - EXPECT_EQ(u32 &= val, uint128_t(0xa0a0a0a0ULL)); - EXPECT_EQ(u64 &= val, uint128_t(0xa0a0a0a0a0a0a0a0ULL)); -} \ No newline at end of file diff --git a/test/utils.h b/test/utils.h deleted file mode 100644 index 734d88933..000000000 --- a/test/utils.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace test_utils -{ - -static std::string byteToHex(const unsigned char* byteArray, size_t sizeInByte) -{ - std::ostringstream oss; - for (size_t i = 0; i < sizeInByte; ++i) - { - oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byteArray[i]); - } - return oss.str(); - -} -static m256i hexTo32Bytes(const std::string& hex, const int sizeInByte) -{ - if (hex.length() != sizeInByte * 2) { - throw std::invalid_argument("Hex string length does not match the expected size"); - } - - m256i byteArray; - for (size_t i = 0; i < sizeInByte; ++i) - { - byteArray.m256i_u8[i] = std::stoi(hex.substr(i * 2, 2), nullptr, 16); - } - - return byteArray; -} - -static void hexToByte(const std::string& hex, const int sizeInByte, unsigned char* out) -{ - if (hex.length() != sizeInByte * 2) - { - throw std::invalid_argument("Hex string length does not match the expected size"); - } - - for (size_t i = 0; i < sizeInByte; ++i) - { - out[i] = std::stoi(hex.substr(i * 2, 2), nullptr, 16); - } -} - -// Function to read and parse the CSV file -static std::vector> readCSV(const std::string& filename) -{ - std::vector> data; - std::ifstream file(filename); - std::string line; - - // Read each line from the file - while (std::getline(file, line)) - { - std::stringstream ss(line); - std::string item; - std::vector parsedLine; - - // Parse each item separated by commas - while (std::getline(ss, item, ',')) - { - // Remove any spaces in the string - item.erase(remove_if(item.begin(), item.end(), isspace), item.end()); - - parsedLine.push_back(item); - } - data.push_back(parsedLine); - } - return data; -} - -static m256i convertFromString(std::string& rStr) -{ - m256i value; - std::stringstream ss(rStr); - std::string item; - int i = 0; - while (std::getline(ss, item, '-')) - { - value.m256i_u64[i++] = std::stoull(item); - } - return value; -} - -static std::vector convertULLFromString(std::string& rStr) -{ - std::vector values; - std::stringstream ss(rStr); - std::string item; - int i = 0; - while (std::getline(ss, item, '-')) - { - values.push_back(std::stoull(item)); - } - return values; -} - -} // test_utils diff --git a/test/virtual_memory.cpp b/test/virtual_memory.cpp deleted file mode 100644 index 50794b981..000000000 --- a/test/virtual_memory.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" -#include "../src/network_messages/tick.h" -#include "../src/public_settings.h" -#include "../src/platform/virtual_memory.h" - -#include - -TEST(TestVirtualMemory, TestVirtualMemory_NativeChar) { - initFilesystem(); - registerAsynFileIO(NULL); - const unsigned long long name_u64 = 123456789; - const unsigned long long pageDir = 0; - VirtualMemory test_vm; - test_vm.init(); - std::vector arr; - // fill with random value - const int N = 1000000; - arr.resize(N); - srand(0); - for (int i = 0; i < N; i++) - { - arr[i] = int(rand() % 256) - 127; - } - // append to virtual memory via single append - for (int i = 0; i < 113; i++) - { - test_vm.append(arr[i]); - } - int pos = 113; - int stride = 1337; - while (pos < N) - { - int s = pos; - int e = std::min(pos + stride, N); - int n_item = e - s; - test_vm.appendMany(arr.data() + s, n_item); - pos += stride; - } - std::vector fetcher; - - for (int i = 0; i < 1024; i++) - { - int offset = rand() % (N/2); - int test_len = rand() % (N - offset); - fetcher.resize(test_len); - test_vm.getMany(fetcher.data(), offset, test_len); - EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len) == 0); - } - - for (int i = 0; i < 1024; i++) - { - int index = rand() % N; - EXPECT_TRUE(test_vm[index] == arr[index]); - } - test_vm.deinit(); -} - -#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12)) -#define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX) -static_assert((RAND_MAX& (RAND_MAX + 1u)) == 0, "RAND_MAX not a Mersenne number"); -uint64_t rand64(void) { - uint64_t r = 0; - for (int i = 0; i < 64; i += RAND_MAX_WIDTH) { - r <<= RAND_MAX_WIDTH; - r ^= (unsigned)rand(); - } - return r; -} - -TEST(TestVirtualMemory, TestVirtualMemory_NativeU64) { - initFilesystem(); - registerAsynFileIO(NULL); - const unsigned long long name_u64 = 123456789; - const unsigned long long pageDir = 0; - VirtualMemory test_vm; - test_vm.init(); - std::vector arr; - // fill with random value - const int N = 1000000; - arr.resize(N); - srand(0); - for (int i = 0; i < N; i++) - { - arr[i] = rand64(); - } - // append to virtual memory via single append - for (int i = 0; i < 113; i++) - { - test_vm.append(arr[i]); - } - int pos = 113; - int stride = 1337; - while (pos < N) - { - int s = pos; - int e = std::min(pos + stride, N); - int n_item = e - s; - test_vm.appendMany(arr.data() + s, n_item); - pos += stride; - } - std::vector fetcher; - - for (int i = 0; i < 1024; i++) - { - int offset = rand() % (N/2); - int test_len = rand() % (N - offset); - fetcher.resize(test_len); - test_vm.getMany(fetcher.data(), offset, test_len); - EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len * sizeof(unsigned long long)) == 0); - } - for (int i = 0; i < 1024; i++) - { - int index = rand() % N; - EXPECT_TRUE(test_vm[index] == arr[index]); - } - test_vm.deinit(); -} - -TickData randTick() -{ - TickData res; - int* ptr = (int*)&res; - int sz = sizeof(res); - for (int i = 0; i < sz/4; i++) { - ptr[i] = rand(); - } - return res; -} - -bool tickEqual(const TickData a, const TickData b) -{ - return memcmp(&a, &b, sizeof(TickData)) == 0; -} - -TEST(TestVirtualMemory, TestVirtualMemory_TickStruct) { - initFilesystem(); - registerAsynFileIO(NULL); - const unsigned long long name_u64 = 123456789; - const unsigned long long pageDir = 0; - VirtualMemory test_vm; - test_vm.init(); - std::vector arr; - // fill with random value - const int N = 1000; - arr.resize(N); - srand(0); - for (int i = 0; i < N; i++) - { - arr[i] = randTick(); - } - // append to virtual memory via single append - for (int i = 0; i < 113; i++) - { - test_vm.append(arr[i]); - } - int pos = 113; - int stride = 1337; - while (pos < N) - { - int s = pos; - int e = std::min(pos + stride, N); - int n_item = e - s; - test_vm.appendMany(arr.data() + s, n_item); - pos += stride; - } - std::vector fetcher; - - for (int i = 0; i < 1024; i++) - { - int offset = rand() % (N/2); - int test_len = rand() % (N - offset); - fetcher.resize(test_len); - test_vm.getMany(fetcher.data(), offset, test_len); - EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len * sizeof(TickData)) == 0); - } - for (int i = 0; i < 1024; i++) - { - int index = rand() % N; - EXPECT_TRUE(tickEqual(test_vm[index], arr[index])); - } - test_vm.deinit(); -} - -TEST(TestVirtualMemory, TestVirtualMemory_SpecialCases) { - initFilesystem(); - registerAsynFileIO(NULL); - const unsigned long long name_u64 = 123456789; - const unsigned long long pageDir = 0; - const unsigned long long pageCap = 2001; - VirtualMemory test_vm; - test_vm.init(); - std::vector arr; - // fill with random value - const int N = 1000000; - arr.resize(N); - srand(0); - for (int i = 0; i < N; i++) - { - arr[i] = int(rand() % 256) - 127; - } - // append to virtual memory via single append - for (int i = 0; i < 113; i++) - { - test_vm.append(arr[i]); - } - int pos = 113; - int stride = 1337; - while (pos < N) - { - int s = pos; - int e = std::min(pos + stride, N); - int n_item = e - s; - test_vm.appendMany(arr.data() + s, n_item); - pos += stride; - } - - // case: head start aligns page - std::vector fetcher; - { - int offset = pageCap; - int test_len = pageCap * 5 + 1; - fetcher.resize(test_len); - test_vm.getMany(fetcher.data(), offset, test_len); - EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len) == 0); - } - - // case: tail end aligns page - { - int offset = 1337; - int test_len = pageCap * 5 + (pageCap - 1337); - fetcher.resize(test_len); - test_vm.getMany(fetcher.data(), offset, test_len); - EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len) == 0); - } - - test_vm.deinit(); -} \ No newline at end of file diff --git a/test/vote_counter.cpp b/test/vote_counter.cpp deleted file mode 100644 index 1f4000e63..000000000 --- a/test/vote_counter.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#define NO_UEFI - -#include "gtest/gtest.h" - -#include "../src/public_settings.h" -#include "../src/vote_counter.h" - -#include - - -class TestVoteCounter : public VoteCounter -{ -public: - unsigned int testExtract10Bit(unsigned char* data, unsigned int idx) - { - return extract10Bit(data, idx); - } - void testUpdate10Bit(unsigned char* data, unsigned int idx, unsigned int value) - { - update10Bit(data, idx, value); - } -}; - -TestVoteCounter tvc; - - -TEST(TestCoreVoteCounter, TenBitsEncodeDecode) { - unsigned char data_u10[848]; - unsigned int data_u32[676]; - for (int i = 0; i < 32; i++) - { - srand(i); - for (int i = 0; i < 676; i++) - { - unsigned int rd = rand() % 676; - data_u32[i] = rd; - tvc.testUpdate10Bit(data_u10, i, rd); - } - bool isMatched = true; - for (int i = 0; i < 676; i++) - { - unsigned int extracted = tvc.testExtract10Bit(data_u10, i); - if (extracted != data_u32[i]) - { - isMatched = false; - break; - } - } - EXPECT_TRUE(isMatched); - } -} - -TEST(TestCoreVoteCounter, NewVotePacketValidation) { - unsigned char data_u10[848]; - srand(0); - setMem(data_u10, sizeof(data_u10), 0); - for (int i = 0; i < 451; i++) - { - unsigned int rd = rand() % 676; - tvc.testUpdate10Bit(data_u10, i, rd); - } - tvc.testUpdate10Bit(data_u10, 0, 0); - bool isValid = tvc.validateNewVotesPacket(data_u10, 0); // total votes is lower than 451 * 676 - EXPECT_TRUE(!isValid); - - setMem(data_u10, sizeof(data_u10), 0); - for (int i = 0; i < 676; i++) - { - unsigned int rd = 451 + rand() % (676-451); - tvc.testUpdate10Bit(data_u10, i, rd); - } - tvc.testUpdate10Bit(data_u10, 0, 300); - isValid = tvc.validateNewVotesPacket(data_u10, 0); // self-report is not zero - EXPECT_TRUE(!isValid); - - setMem(data_u10, sizeof(data_u10), 0); - for (int i = 0; i < 676; i++) - { - unsigned int rd = 451 + rand() % (676 - 451); - tvc.testUpdate10Bit(data_u10, i, rd); - } - tvc.testUpdate10Bit(data_u10, 0, 0); - isValid = tvc.validateNewVotesPacket(data_u10, 0); // valid - EXPECT_TRUE(isValid); -} - -TEST(TestCoreVoteCounter, AddGetVotes) { - unsigned char data_u10[848] = { 0 }; - unsigned int data_u32[676] = { 0 }; - srand(0); - tvc.init(); - for (int comp = 0; comp < 676; comp++) - { - for (int i = 0; i < 676; i++) - { - unsigned int rd = 451 + (rand() % (676-451)); - tvc.testUpdate10Bit(data_u10, i, rd); - } - tvc.testUpdate10Bit(data_u10, comp, 0); - - tvc.addVotes(data_u10, comp); - for (int i = 0; i < 676; i++) - { - data_u32[i] += tvc.testExtract10Bit(data_u10, i); - } - } - bool isMatched = true; - for (int i = 0; i < 676; i++) - { - if (tvc.getVoteCount(i) != data_u32[i]) - { - isMatched = false; - printf("[FAILED] comp %d: %llu vs %lu\n", i, tvc.getVoteCount(i), data_u32[i]); - break; - } - } - EXPECT_TRUE(isMatched); -} - -bool tick_data[676 * 4][676]; -TEST(TestCoreVoteCounter, RegisterNewCompressVotes) { - unsigned char data_u10[848] = { 0 }; - unsigned int data_u32[676] = { 0 }; - setMem(tick_data, sizeof(tick_data), 0); - srand(0); - tvc.init(); - bool isMatched = true; - unsigned int startTick = 676 + rand() % 676; - for (unsigned int tick = startTick; tick < 676 * 4; tick++) - { - bool flag[676]; - setMem(flag, sizeof(flag), true); - int n_non_vote = rand() % (676 - 451); - for (int i = 0; i < n_non_vote; i++) - { - flag[rand() % 676] = false; - } - for (int i = 0; i < 676; i++) - { - if (flag[i]) - { - tvc.registerNewVote(tick, i); - tick_data[tick][i] = true; - } - } - for (int k = 0; k < 2; k++) // randomly select 2 comp to test - { - int comp = rand() % 676; - tvc.compressNewVotesPacket(tick - 675, tick + 1, comp, data_u10); - setMem(data_u32, sizeof(data_u32), 0); - for (unsigned int t = tick - 675; t <= tick; t++) - { - for (int i = 0; i < 676; i++) - { - if (tick_data[t][i]) data_u32[i]++; - } - } - data_u32[comp] = 0; - for (int i = 0; i < 676; i++) - { - unsigned int vote = tvc.testExtract10Bit(data_u10, i); - if (vote != data_u32[i]) - { - isMatched = false; - break; - } - } - } - EXPECT_TRUE(isMatched); - //printf("[PASSED] tick %u\n", tick); - } -} \ No newline at end of file From d7c758ce798e65545391a6731598cb5248bc6316 Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:01:01 -0300 Subject: [PATCH 03/30] qrw-test --- .env | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 627ab55b6..000000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -SSH server= 95.217.151.140 -Username= root -Password=A5S77mJ90Oxp \ No newline at end of file From 6dc98530807799393b8e673c2f5ce533de7b34ac Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:03:29 -0300 Subject: [PATCH 04/30] TESTING: Disable Friday 12:00 time check and 6-day minimum interval for payout testing - Comment out dayOfWeek/hour checking, allow payout on any tick - Comment out QRWA_MIN_PAYOUT_INTERVAL_MS check - Enable immediate testing with tick 44601600 - Add TESTING markers with notes to remove for production --- src/contracts/qRWA.h | 67 +++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index eeb7a4dc0..aeec42e84 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -1116,38 +1116,38 @@ struct QRWA : public ContractBase // Initialize default governance parameters state.mCurrentGovParams.mAdminAddress = ID( - _Q, _M, _I, _N, _E, _Q, _Q, _X, _Y, _B, _E, _G, _B, _H, _N, _S, - _U, _P, _O, _U, _Y, _D, _I, _Q, _K, _Z, _P, _C, _B, _P, _Q, _I, - _I, _H, _U, _U, _Z, _M, _C, _P, _L, _B, _P, _C, _C, _A, _I, _A, - _R, _V, _Z, _B, _T, _Y, _K, _G - ); // Admin set to QMINE Issuer by default, subject to change via Gov Voting + _J, _H, _U, _I, _Z, _P, _G, _Z, _N, _M, _T, _H, _P, _C, _Z, _I, + _B, _A, _M, _S, _Z, _Q, _G, _B, _J, _C, _O, _A, _O, _G, _D, _A, + _Q, _V, _F, _H, _W, _Y, _K, _L, _E, _G, _G, _R, _M, _J, _E, _J, + _S, _X, _T, _R, _E, _U, _V, _C, _F, _G, _X, _L + ); state.mCurrentGovParams.electricityAddress = ID( - _Q, _M, _I, _N, _E, _Q, _Q, _X, _Y, _B, _E, _G, _B, _H, _N, _S, - _U, _P, _O, _U, _Y, _D, _I, _Q, _K, _Z, _P, _C, _B, _P, _Q, _I, - _I, _H, _U, _U, _Z, _M, _C, _P, _L, _B, _P, _C, _C, _A, _I, _A, - _R, _V, _Z, _B, _T, _Y, _K, _G - ); // Electricity address set to QMINE Issuer by default, subject to change via Gov Voting + _J, _T, _I, _D, _B, _A, _Q, _S, _M, _H, _F, _S, _F, _D, _P, _C, + _M, _Q, _G, _B, _Z, _V, _R, _N, _N, _T, _K, _A, _J, _N, _Z, _G, + _E, _O, _L, _Y, _O, _U, _F, _N, _U, _E, _S, _M, _Q, _L, _N, _G, + _W, _J, _B, _A, _R, _G, _Q, _B, _Z, _L, _N, _H + ); state.mCurrentGovParams.maintenanceAddress = ID( - _Q, _M, _I, _N, _E, _Q, _Q, _X, _Y, _B, _E, _G, _B, _H, _N, _S, - _U, _P, _O, _U, _Y, _D, _I, _Q, _K, _Z, _P, _C, _B, _P, _Q, _I, - _I, _H, _U, _U, _Z, _M, _C, _P, _L, _B, _P, _C, _C, _A, _I, _A, - _R, _V, _Z, _B, _T, _Y, _K, _G - ); // Maintenance address set to QMINE Issuer by default, subject to change via Gov Voting + _J, _N, _R, _Z, _M, _D, _C, _B, _Q, _Y, _F, _C, _F, _A, _T, _G, + _L, _O, _Z, _V, _E, _W, _K, _F, _W, _E, _P, _D, _H, _S, _I, _G, + _R, _F, _O, _F, _C, _G, _P, _J, _F, _E, _Z, _Q, _Y, _Q, _Z, _P, + _B, _K, _M, _S, _S, _V, _J, _B, _Y, _U, _M, _E + ); state.mCurrentGovParams.reinvestmentAddress = ID( - _Q, _M, _I, _N, _E, _Q, _Q, _X, _Y, _B, _E, _G, _B, _H, _N, _S, - _U, _P, _O, _U, _Y, _D, _I, _Q, _K, _Z, _P, _C, _B, _P, _Q, _I, - _I, _H, _U, _U, _Z, _M, _C, _P, _L, _B, _P, _C, _C, _A, _I, _A, - _R, _V, _Z, _B, _T, _Y, _K, _G - ); // Reinvestment address set to QMINE Issuer by default, subject to change via Gov Voting + _C, _Q, _V, _D, _N, _N, _G, _N, _I, _R, _L, _T, _B, _G, _D, _J, + _F, _P, _W, _U, _J, _A, _Y, _O, _D, _J, _E, _C, _L, _N, _W, _W, + _U, _T, _V, _U, _N, _W, _T, _A, _M, _D, _S, _Y, _F, _B, _N, _K, + _S, _N, _D, _C, _A, _Y, _U, _D, _C, _T, _M, _M + ); // QMINE DEV's Address for receiving rewards from moved QMINE tokens // ZOXXIDCZIMGCECCFAXDDCMBBXCDAQJIHGOOATAFPSBFIOFOYECFKUFPBEMWC state.mCurrentGovParams.qmineDevAddress = ID( - _Z, _O, _X, _X, _I, _D, _C, _Z, _I, _M, _G, _C, _E, _C, _C, _F, - _A, _X, _D, _D, _C, _M, _B, _B, _X, _C, _D, _A, _Q, _J, _I, _H, - _G, _O, _O, _A, _T, _A, _F, _P, _S, _B, _F, _I, _O, _F, _O, _Y, - _E, _C, _F, _K, _U, _F, _P, _B - ); // Default QMINE_DEV address + _R, _U, _J, _G, _S, _W, _E, _E, _E, _U, _C, _O, _O, _C, _D, _P, + _A, _M, _U, _U, _Z, _S, _H, _I, _R, _Y, _N, _D, _A, _F, _D, _O, + _W, _X, _F, _W, _A, _Q, _L, _Z, _R, _B, _N, _X, _G, _E, _X, _Q, + _W, _B, _D, _C, _V, _U, _Z, _G, _T, _J, _A, _E + ); state.mCurrentGovParams.electricityPercent = 350; state.mCurrentGovParams.maintenancePercent = 50; state.mCurrentGovParams.reinvestmentPercent = 100; @@ -1165,7 +1165,12 @@ struct QRWA : public ContractBase state.mQRWADividendPool = 0; state.mDedicatedQRWADividendPool = 0; - state.mDedicatedRevenueAddress = state.mCurrentGovParams.mAdminAddress; + state.mDedicatedRevenueAddress = ID( + _P, _D, _Q, _T, _K, _K, _I, _R, _S, _I, _G, _A, _G, _A, _O, _L, + _J, _W, _Z, _W, _T, _C, _B, _S, _F, _C, _Y, _A, _I, _Z, _I, _R, + _Y, _C, _H, _E, _B, _K, _H, _B, _J, _H, _H, _B, _J, _N, _J, _H, + _W, _L, _Y, _G, _X, _S, _V, _E, _Q, _E, _F, _C + ); // Initialize total distributed state.mTotalQmineDistributed = 0; @@ -1725,8 +1730,10 @@ struct QRWA : public ContractBase locals.now = qpi.now(); // Check payout conditions: Correct day, correct hour, and enough time passed - if (qpi.dayOfWeek((uint8)mod(locals.now.getYear(), (uint16)100), locals.now.getMonth(), locals.now.getDay()) == QRWA_PAYOUT_DAY && - locals.now.getHour() == QRWA_PAYOUT_HOUR) + // TESTING: Freitag 12:00 Prüfung auskommentiert - nutze Tick 44601600 für Auszahlung + // if (qpi.dayOfWeek((uint8)mod(locals.now.getYear(), (uint16)100), locals.now.getMonth(), locals.now.getDay()) == QRWA_PAYOUT_DAY && + // locals.now.getHour() == QRWA_PAYOUT_HOUR) + if (true) // TESTING: Immer true für Development/Testing - entfernen für Production { // check if mLastPayoutTime is 0 (never initialized) if (state.mLastPayoutTime.getYear() == 0) @@ -1749,7 +1756,9 @@ struct QRWA : public ContractBase } } - if (locals.msSinceLastPayout >= QRWA_MIN_PAYOUT_INTERVAL_MS) + // TESTING: 6-Tage Minimum-Interval auskommentiert für sofortiges Testing + // if (locals.msSinceLastPayout >= QRWA_MIN_PAYOUT_INTERVAL_MS) + if (true) // TESTING: Immer true für Testing - entfernen für Production { locals.logger.contractId = CONTRACT_INDEX; locals.logger.logType = QRWA_LOG_TYPE_DISTRIBUTION; From c761588107ced76583563d48cda8aa65317fc914 Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:13:04 -0300 Subject: [PATCH 05/30] TESTING: Update payout trigger to tick 44602000 - Change test condition from always true to qpi.tick() >= 44602000 - Payout now only executes when tick reaches 44602000 or beyond - Exact testing window for qRWA distribution validation --- src/contracts/qRWA.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index aeec42e84..9a020fed0 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -1730,10 +1730,10 @@ struct QRWA : public ContractBase locals.now = qpi.now(); // Check payout conditions: Correct day, correct hour, and enough time passed - // TESTING: Freitag 12:00 Prüfung auskommentiert - nutze Tick 44601600 für Auszahlung + // TESTING: Freitag 12:00 Prüfung auskommentiert - nutze Tick 44602000 für Auszahlung // if (qpi.dayOfWeek((uint8)mod(locals.now.getYear(), (uint16)100), locals.now.getMonth(), locals.now.getDay()) == QRWA_PAYOUT_DAY && // locals.now.getHour() == QRWA_PAYOUT_HOUR) - if (true) // TESTING: Immer true für Development/Testing - entfernen für Production + if (qpi.tick() >= 44602000) // TESTING: Nur bei Tick 44602000+ - entfernen für Production { // check if mLastPayoutTime is 0 (never initialized) if (state.mLastPayoutTime.getYear() == 0) @@ -1758,7 +1758,7 @@ struct QRWA : public ContractBase // TESTING: 6-Tage Minimum-Interval auskommentiert für sofortiges Testing // if (locals.msSinceLastPayout >= QRWA_MIN_PAYOUT_INTERVAL_MS) - if (true) // TESTING: Immer true für Testing - entfernen für Production + if (true) // TESTING: Immer true - Auszahlung damit sofort bei Tick 44602000 - entfernen für Production { locals.logger.contractId = CONTRACT_INDEX; locals.logger.logType = QRWA_LOG_TYPE_DISTRIBUTION; From 5f8ee81b623df80f3c21e4061102eed9ae6a4c8a Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:17:23 -0300 Subject: [PATCH 06/30] TESTING: Update payout trigger to tick 44602000 - Change test condition from always true to qpi.tick() >= 44602000 - Payout now only executes when tick reaches 44602200 or beyond - Exact testing window for qRWA distribution validation --- src/contracts/qRWA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index 9a020fed0..cdd8c66c1 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -1733,7 +1733,7 @@ struct QRWA : public ContractBase // TESTING: Freitag 12:00 Prüfung auskommentiert - nutze Tick 44602000 für Auszahlung // if (qpi.dayOfWeek((uint8)mod(locals.now.getYear(), (uint16)100), locals.now.getMonth(), locals.now.getDay()) == QRWA_PAYOUT_DAY && // locals.now.getHour() == QRWA_PAYOUT_HOUR) - if (qpi.tick() >= 44602000) // TESTING: Nur bei Tick 44602000+ - entfernen für Production + if (qpi.tick() >= 44602200) // TESTING: Nur bei Tick 44602000+ - entfernen für Production { // check if mLastPayoutTime is 0 (never initialized) if (state.mLastPayoutTime.getYear() == 0) From 1c86f7ab3abb5393ba96d96c1051a8f24eedd13d Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:39:52 -0300 Subject: [PATCH 07/30] Sync with upstream testnets/release-280, keep custom qRWA contract --- cmake/CompilerSetup.cmake | 344 +++ src/Qubic.vcxproj | 1 - src/Qubic.vcxproj.filters | 3 - src/contract_core/contract_def.h | 20 - src/contracts/QDuel.h | 70 +- src/contracts/QReservePool.h | 10 - src/contracts/QThirtyFour.h | 173 +- src/contracts/README_QRWA_TEST.md | 341 +++ src/contracts/RandomLottery.h | 7 - src/contracts/qRWA.h | 16 +- src/network_core/peers.h | 1 + src/oracle_core/net_msg_impl.h | 5 +- src/oracle_core/oracle_engine.h | 99 +- src/oracle_core/snapshot_files.h | 18 +- src/private_settings.h | 6 +- src/public_settings.h | 42 +- src/qubic.cpp | 50 +- test/CMakeLists.txt | 70 + test/README.md | 113 + test/assets.cpp | 757 ++++++ test/common_def.cpp | 27 + test/contract_ccf.cpp | 1215 ++++++++++ test/contract_core.cpp | 167 ++ test/contract_gqmprop.cpp | 537 +++++ test/contract_msvault.cpp | 1258 ++++++++++ test/contract_nostromo.cpp | 1692 +++++++++++++ test/contract_qbay.cpp | 1174 +++++++++ test/contract_qbond.cpp | 444 ++++ test/contract_qduel.cpp | 1468 +++++++++++ test/contract_qearn.cpp | 983 ++++++++ test/contract_qip.cpp | 1444 +++++++++++ test/contract_qraffle.cpp | 1576 ++++++++++++ test/contract_qrp.cpp | 263 ++ test/contract_qrwa.cpp | 1852 ++++++++++++++ test/contract_qswap.cpp | 810 +++++++ test/contract_qtf.cpp | 3338 ++++++++++++++++++++++++++ test/contract_qutil.cpp | 1751 ++++++++++++++ test/contract_qvault.cpp | 874 +++++++ test/contract_qx.cpp | 253 ++ test/contract_rl.cpp | 1283 ++++++++++ test/contract_testex.cpp | 2173 +++++++++++++++++ test/contract_testing.h | 228 ++ test/custom_mining.cpp | 279 +++ test/data/custom_revenue.eoe | Bin 0 -> 43264 bytes test/data/samples_20240815.csv | 1025 ++++++++ test/data/scores_addition.csv | 1025 ++++++++ test/data/scores_hyperidentity.csv | 1025 ++++++++ test/execution_fees.cpp | 375 +++ test/file_io.cpp | 684 ++++++ test/fourq.cpp | 264 ++ test/kangaroo_twelve.cpp | 92 + test/logging_test.h | 42 + test/m256.cpp | 274 +++ test/math_lib.cpp | 88 + test/network_messages.cpp | 68 + test/oracle_engine.cpp | 1325 ++++++++++ test/oracle_testing.h | 84 + test/packages.config | 4 + test/pending_txs_pool.cpp | 544 +++++ test/platform.cpp | 501 ++++ test/qpi.cpp | 2313 ++++++++++++++++++ test/qpi_collection.cpp | 1716 +++++++++++++ test/qpi_date_time.cpp | 589 +++++ test/qpi_hash_map.cpp | 1009 ++++++++ test/quorum_value.cpp | 140 ++ test/revenue.cpp | 235 ++ test/score.cpp | 910 +++++++ test/score_addition_reference.h | 869 +++++++ test/score_cache.cpp | 201 ++ test/score_common_reference.h | 108 + test/score_hyperidentity_reference.h | 785 ++++++ test/score_params.h | 65 + test/score_reference.h | 60 + test/sorting.cpp | 118 + test/spectrum.cpp | 409 ++++ test/stable_computor_index.cpp | 214 ++ test/stdlib_impl.cpp | 59 + test/test.vcxproj | 203 ++ test/test.vcxproj.filters | 80 + test/test.vcxproj.user | 12 + test/test_util.h | 34 + test/tick_storage.cpp | 209 ++ test/time.cpp | 313 +++ test/tx_status_request.cpp | 224 ++ test/uint128.cpp | 199 ++ test/utils.h | 103 + test/virtual_memory.cpp | 239 ++ test/vote_counter.cpp | 172 ++ 88 files changed, 46028 insertions(+), 215 deletions(-) create mode 100644 cmake/CompilerSetup.cmake create mode 100644 src/contracts/README_QRWA_TEST.md create mode 100644 test/CMakeLists.txt create mode 100644 test/README.md create mode 100644 test/assets.cpp create mode 100644 test/common_def.cpp create mode 100644 test/contract_ccf.cpp create mode 100644 test/contract_core.cpp create mode 100644 test/contract_gqmprop.cpp create mode 100644 test/contract_msvault.cpp create mode 100644 test/contract_nostromo.cpp create mode 100644 test/contract_qbay.cpp create mode 100644 test/contract_qbond.cpp create mode 100644 test/contract_qduel.cpp create mode 100644 test/contract_qearn.cpp create mode 100644 test/contract_qip.cpp create mode 100644 test/contract_qraffle.cpp create mode 100644 test/contract_qrp.cpp create mode 100644 test/contract_qrwa.cpp create mode 100644 test/contract_qswap.cpp create mode 100644 test/contract_qtf.cpp create mode 100644 test/contract_qutil.cpp create mode 100644 test/contract_qvault.cpp create mode 100644 test/contract_qx.cpp create mode 100644 test/contract_rl.cpp create mode 100644 test/contract_testex.cpp create mode 100644 test/contract_testing.h create mode 100644 test/custom_mining.cpp create mode 100644 test/data/custom_revenue.eoe create mode 100644 test/data/samples_20240815.csv create mode 100644 test/data/scores_addition.csv create mode 100644 test/data/scores_hyperidentity.csv create mode 100644 test/execution_fees.cpp create mode 100644 test/file_io.cpp create mode 100644 test/fourq.cpp create mode 100644 test/kangaroo_twelve.cpp create mode 100644 test/logging_test.h create mode 100644 test/m256.cpp create mode 100644 test/math_lib.cpp create mode 100644 test/network_messages.cpp create mode 100644 test/oracle_engine.cpp create mode 100644 test/oracle_testing.h create mode 100644 test/packages.config create mode 100644 test/pending_txs_pool.cpp create mode 100644 test/platform.cpp create mode 100644 test/qpi.cpp create mode 100644 test/qpi_collection.cpp create mode 100644 test/qpi_date_time.cpp create mode 100644 test/qpi_hash_map.cpp create mode 100644 test/quorum_value.cpp create mode 100644 test/revenue.cpp create mode 100644 test/score.cpp create mode 100644 test/score_addition_reference.h create mode 100644 test/score_cache.cpp create mode 100644 test/score_common_reference.h create mode 100644 test/score_hyperidentity_reference.h create mode 100644 test/score_params.h create mode 100644 test/score_reference.h create mode 100644 test/sorting.cpp create mode 100644 test/spectrum.cpp create mode 100644 test/stable_computor_index.cpp create mode 100644 test/stdlib_impl.cpp create mode 100644 test/test.vcxproj create mode 100644 test/test.vcxproj.filters create mode 100644 test/test.vcxproj.user create mode 100644 test/test_util.h create mode 100644 test/tick_storage.cpp create mode 100644 test/time.cpp create mode 100644 test/tx_status_request.cpp create mode 100644 test/uint128.cpp create mode 100644 test/utils.h create mode 100644 test/virtual_memory.cpp create mode 100644 test/vote_counter.cpp diff --git a/cmake/CompilerSetup.cmake b/cmake/CompilerSetup.cmake new file mode 100644 index 000000000..4ed5386c3 --- /dev/null +++ b/cmake/CompilerSetup.cmake @@ -0,0 +1,344 @@ +# CompilerDetection.cmake +# Central location for compiler and system detection logic + +# --- Platform and Compiler Detection --- +message(STATUS "Detecting compiler and platform...") +message(STATUS "Compiler ID: ${CMAKE_CXX_COMPILER_ID}") +message(STATUS "Compiler Path: ${CMAKE_CXX_COMPILER}") +message(STATUS "System Name: ${CMAKE_SYSTEM_NAME}") + +# Set platform detection variables +set(IS_WINDOWS FALSE CACHE INTERNAL "Windows platform detected") +set(IS_LINUX FALSE CACHE INTERNAL "Linux platform detected") +set(IS_MSVC FALSE CACHE INTERNAL "MSVC compiler detected") +set(IS_CLANG FALSE CACHE INTERNAL "Clang compiler detected") +set(IS_GCC FALSE CACHE INTERNAL "GCC compiler detected") +set(ASM_LANG "" CACHE INTERNAL "Assembly language to use") + +# Detect Windows platform +if(CMAKE_SYSTEM_NAME MATCHES "Windows") + set(IS_WINDOWS TRUE CACHE INTERNAL "Windows platform detected" FORCE) + message(STATUS "Windows platform detected") +endif() + +# Detect Linux platform +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + set(IS_LINUX TRUE CACHE INTERNAL "Linux platform detected" FORCE) + message(STATUS "Linux platform detected") +endif() + +# Detect MSVC compiler +if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set(IS_MSVC TRUE CACHE INTERNAL "MSVC compiler detected" FORCE) + message(STATUS "MSVC compiler detected") + + # Set assembly language for MSVC + enable_language(ASM_MASM) + set(ASM_LANG ASM_MASM CACHE INTERNAL "Assembly language to use" FORCE) + message(STATUS "Using MASM for assembly") +endif() + +# Detect Clang compiler +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(IS_CLANG TRUE CACHE INTERNAL "Clang compiler detected" FORCE) + message(STATUS "Clang compiler detected") + + # For Clang on Linux, we'll use NASM + if(IS_LINUX) + set(ASM_LANG ASM_NASM CACHE INTERNAL "Assembly language to use" FORCE) + find_program(NASM_EXECUTABLE nasm REQUIRED) + message(STATUS "Using NASM for assembly via custom command") + endif() +endif() + +# Detect GCC compiler +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(IS_GCC TRUE CACHE INTERNAL "GCC compiler detected" FORCE) + message(STATUS "GCC compiler detected") + + # For GCC on Linux, we'll use NASM + if(IS_LINUX) + set(ASM_LANG ASM_NASM CACHE INTERNAL "Assembly language to use" FORCE) + find_program(NASM_EXECUTABLE nasm REQUIRED) + message(STATUS "Using NASM for assembly via custom command") + endif() +endif() + +# --- Clear all default flags to use only specified ones --- +set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "Available build types" FORCE) + +message(STATUS "CLEARING CMAKE DEFAULT FLAGS") +# Set all default flag variables to an empty string to take full control. +set(CMAKE_C_FLAGS "" CACHE INTERNAL "") +set(CMAKE_CXX_FLAGS "" CACHE INTERNAL "") +set(CMAKE_EXE_LINKER_FLAGS "" CACHE INTERNAL "") + +set(CMAKE_C_FLAGS_DEBUG "" CACHE INTERNAL "") +set(CMAKE_CXX_FLAGS_DEBUG "" CACHE INTERNAL "") +set(CMAKE_EXE_LINKER_FLAGS_DEBUG "" CACHE INTERNAL "") + +set(CMAKE_C_FLAGS_RELEASE "" CACHE INTERNAL "") +set(CMAKE_CXX_FLAGS_RELEASE "" CACHE INTERNAL "") +set(CMAKE_EXE_LINKER_FLAGS_RELEASE "" CACHE INTERNAL "") + +# set(CMAKE_C_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "") +# set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "") +# set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "" CACHE INTERNAL "") + +# set(CMAKE_C_FLAGS_MINSIZEREL "" CACHE INTERNAL "") +# set(CMAKE_CXX_FLAGS_MINSIZEREL "" CACHE INTERNAL "") +# set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "" CACHE INTERNAL "") + +# --- Common Compiler Flags --- + +# Define common flags for all compilers +set(COMMON_C_FLAGS "" CACHE INTERNAL "Common C compiler flags") +set(COMMON_CXX_FLAGS "" CACHE INTERNAL "Common C++ compiler flags") +set(COMMON_DEBUG_FLAGS "" CACHE INTERNAL "Common debug compiler flags") +set(COMMON_RELEASE_FLAGS "" CACHE INTERNAL "Common release compiler flags") +set(COMMON_LINK_FLAGS "" CACHE INTERNAL "Common linker flags") + +# Define EFI-specific flags +set(EFI_C_FLAGS "" CACHE INTERNAL "EFI-specific C compiler flags") +set(EFI_CXX_FLAGS "" CACHE INTERNAL "EFI-specific C++ compiler flags") + +# Define OS-specific flags +set(OS_C_FLAGS "" CACHE INTERNAL "OS-specific C compiler flags") +set(OS_CXX_FLAGS "" CACHE INTERNAL "OS-specific C++ compiler flags") + +# Define platform-specific flags +if(IS_MSVC) + # MSVC-specific common flags + set(COMMON_C_FLAGS "/W4 /GF" CACHE INTERNAL "Common C compiler flags" FORCE) + set(COMMON_CXX_FLAGS "${COMMON_C_FLAGS}" CACHE INTERNAL "Common C++ compiler flags" FORCE) + set(COMMON_DEBUG_FLAGS "" CACHE INTERNAL "Common debug compiler flags" FORCE) + set(COMMON_RELEASE_FLAGS "/GL- /Gw /Oi /Ob2 /O2 /Oy" CACHE INTERNAL "Common release compiler flags" FORCE) + set(COMMON_LINK_FLAGS "" CACHE INTERNAL "Common linker flags" FORCE) + + # MSVC-specific EFI flags + # /W3 # WarningLevel: Level3 + # /permissive- # ConformanceMode: true + # /Zc:wchar_t- # TreatWChar_tAsBuiltInType: false + # /EHs-c- # ExceptionHandling: false + # /GS- # BufferSecurityCheck: false / SDLCheck: false + # /GF # StringPooling: true + # /Gy- # FunctionLevelLinking: false + # /Oi # IntrinsicFunctions: true + # /O2 # Optimization: MaxSpeed + # /Ot # FavorSizeOrSpeed: Speed + # /Ob2 # InlineFunctionExpansion: AnySuitable + # /Oy # OmitFramePointers: true + # /GT # EnableFiberSafeOptimizations: true + # /fp:except- # FloatingPointExceptions: false + # /guard:cf- # ControlFlowGuard: false + # /Gs1638400 # AdditionalOptions: /Gs... + # /Zl # OmitDefaultLibName: true + # /FAcs # AssemblerOutput: All (DISABLED) + # /Fa${CMAKE_INTDIR}/Qubic.asm # AssemblerListingLocation (DISABLED) + set(EFI_C_FLAGS "/W3 /permissive- /Zc:wchar_t- /EHs-c- /GS- /GF /Gy- /Oi /O2 /Ot /Ob2 /Oy /GT /fp:except- /guard:cf- /Gs1638400 /Zl" CACHE INTERNAL "EFI-specific C compiler flags" FORCE) + set(EFI_CXX_FLAGS "${EFI_C_FLAGS}" CACHE INTERNAL "EFI-specific C++ compiler flags" FORCE) + + # /SUBSYSTEM:EFI_APPLICATION # SubSystem: EFI Application + # /ENTRY:efi_main # EntryPointSymbol: efi_main + # /NODEFAULTLIB # IgnoreAllDefaultLibraries: true + # /DEBUG:NONE # GenerateDebugInformation: false + # /OPT:REF # EnableCOMDATFolding: true + # /OPT:ICF # OptimizeReferences: true + # /DYNAMICBASE:NO # RandomizedBaseAddress: false + # /NXCOMPAT:NO # DataExecutionPrevention: false + # /MANIFEST:NO # GenerateManifest: false + # /STACK:131072 # StackReserveSize / StackCommitSize + set(EFI_LINK_FLAGS "/SUBSYSTEM:EFI_APPLICATION /ENTRY:efi_main /NODEFAULTLIB /DEBUG:NONE /OPT:REF /OPT:ICF /MANIFEST:NO /DYNAMICBASE:NO /NXCOMPAT:NO /STACK:131072" CACHE INTERNAL "EFI-specific linker flags" FORCE) + + # MSVC-specific OS flags + set(OS_C_FLAGS "" CACHE INTERNAL "OS-specific C compiler flags" FORCE) + set(OS_CXX_FLAGS "" CACHE INTERNAL "OS-specific C++ compiler flags" FORCE) + +elseif(IS_CLANG OR IS_GCC) + # Clang/GCC-specific common flags + set(COMMON_C_FLAGS "-Wall -Wextra -fshort-wchar" CACHE INTERNAL "Common C compiler flags" FORCE) + set(COMMON_CXX_FLAGS "${COMMON_C_FLAGS}" CACHE INTERNAL "Common C++ compiler flags" FORCE) + set(COMMON_DEBUG_FLAGS "-g" CACHE INTERNAL "Common debug compiler flags" FORCE) + set(COMMON_RELEASE_FLAGS "-O2 -fomit-frame-pointer -fno-lto" CACHE INTERNAL "Common release compiler flags" FORCE) + set(COMMON_LINK_FLAGS "" CACHE INTERNAL "Common linker flags" FORCE) + + # Clang/GCC-specific EFI flags + set(EFI_C_FLAGS "-ffreestanding -mno-red-zone -fno-stack-protector -fno-strict-aliasing -fno-builtin" CACHE INTERNAL "EFI-specific C compiler flags" FORCE) + set(EFI_CXX_FLAGS "${EFI_C_FLAGS} -fno-rtti -fno-exceptions" CACHE INTERNAL "EFI-specific C++ compiler flags" FORCE) + + # Clang/GCC-specific OS flags + set(OS_C_FLAGS "-fno-stack-protector" CACHE INTERNAL "OS-specific C compiler flags" FORCE) + set(OS_CXX_FLAGS "${OS_C_FLAGS}" CACHE INTERNAL "OS-specific C++ compiler flags" FORCE) +endif() + +# --- CPU Instruction Set Flags --- + +# Allow the user to enable AVX-512 +option(ENABLE_AVX512 "Enable AVX-512 instructions" ON) + +# Define CPU instruction set flags +set(CPU_INSTRUCTION_FLAGS "" CACHE INTERNAL "CPU instruction set flags") + +if(IS_MSVC) + if(ENABLE_AVX512) + set(CPU_INSTRUCTION_FLAGS "/arch:AVX512" CACHE INTERNAL "CPU instruction set flags" FORCE) + message(STATUS "MSVC: Enabling AVX-512 (/arch:AVX512)") + else() + set(CPU_INSTRUCTION_FLAGS "/arch:AVX2" CACHE INTERNAL "CPU instruction set flags" FORCE) + message(STATUS "MSVC: Enabling AVX2 (/arch:AVX2)") + message(STATUS "AVX-512 is disabled. If you would like to activate make sure you set ENABLE_AVX512 to ON while running cmake.") + endif() +elseif(IS_CLANG OR IS_GCC) + if(ENABLE_AVX512) + set(CPU_INSTRUCTION_FLAGS "-mavx -mavx2 -mavx512f -mavx512cd -mavx512vl -mavx512bw -mavx512dq" CACHE INTERNAL "CPU instruction set flags" FORCE) + message(STATUS "GCC/Clang: Enabling AVX-512 and AVX/AVX2") + else() + set(CPU_INSTRUCTION_FLAGS "-mavx -mavx2" CACHE INTERNAL "CPU instruction set flags" FORCE) + message(STATUS "GCC/Clang: Enabling AVX/AVX2") + message(STATUS "AVX-512 is disabled. If you would like to activate make sure you set ENABLE_AVX512 to ON while running cmake.") + endif() +endif() + +# --- Test-specific flags --- +set(TEST_SPECIFIC_FLAGS "" CACHE INTERNAL "Test-specific compiler flags") + +if(IS_MSVC) + set(TEST_SPECIFIC_FLAGS "/WX /EHsc" CACHE INTERNAL "Test-specific compiler flags" FORCE) +elseif(IS_CLANG OR IS_GCC) + if(USE_SANITIZER) + set(TEST_SPECIFIC_FLAGS "-Wpedantic -Werror -mrdrnd -Wcast-align -fsanitize=alignment -fno-sanitize-recover=alignment" CACHE INTERNAL "Test-specific compiler flags" FORCE) + set(TEST_SPECIFIC_LINK_FLAGS "-fsanitize=alignment" CACHE INTERNAL "Test-specific linker flags" FORCE) + else() + set(TEST_SPECIFIC_FLAGS "-Wpedantic -Werror -mrdrnd -Wcast-align " CACHE INTERNAL "Test-specific compiler flags" FORCE) + set(TEST_SPECIFIC_LINK_FLAGS "" CACHE INTERNAL "Test-specific linker flags" FORCE) + endif() +endif() + +# --- EFI-specific flags --- +set(EFI_SPECIFIC_FLAGS "" CACHE INTERNAL "EFI-specific compiler flags") + +if(IS_MSVC) + set(EFI_SPECIFIC_FLAGS "" CACHE INTERNAL "EFI-specific compiler flags" FORCE) +elseif(IS_CLANG OR IS_GCC) + set(EFI_SPECIFIC_FLAGS "-fno-rtti -fno-exceptions" CACHE INTERNAL "EFI-specific compiler flags" FORCE) +endif() + +# Function to apply common compiler flags to a target +function(apply_common_compiler_flags target) + if(IS_MSVC) + # Convert space-separated flags to list for MSVC + separate_arguments(C_FLAGS WINDOWS_COMMAND ${COMMON_C_FLAGS}) + separate_arguments(CXX_FLAGS WINDOWS_COMMAND ${COMMON_CXX_FLAGS}) + separate_arguments(DEBUG_FLAGS WINDOWS_COMMAND ${COMMON_DEBUG_FLAGS}) + separate_arguments(RELEASE_FLAGS WINDOWS_COMMAND ${COMMON_RELEASE_FLAGS}) + separate_arguments(CPU_FLAGS WINDOWS_COMMAND ${CPU_INSTRUCTION_FLAGS}) + + target_compile_options(${target} PRIVATE + ${C_FLAGS} + $<$:${CXX_FLAGS}> + $<$:${DEBUG_FLAGS}> + $<$:${RELEASE_FLAGS}> + ${CPU_FLAGS} + ) + target_compile_definitions(${target} PRIVATE + _LIB + UNICODE _UNICODE + $<$:NDEBUG> + ) + message("Apply Common Flags MSVC to " ${target}) + else() + # Convert space-separated flags to list for Clang/GCC + separate_arguments(C_FLAGS UNIX_COMMAND ${COMMON_C_FLAGS}) + separate_arguments(CXX_FLAGS UNIX_COMMAND ${COMMON_CXX_FLAGS}) + separate_arguments(DEBUG_FLAGS UNIX_COMMAND ${COMMON_DEBUG_FLAGS}) + separate_arguments(RELEASE_FLAGS UNIX_COMMAND ${COMMON_RELEASE_FLAGS}) + separate_arguments(CPU_FLAGS UNIX_COMMAND ${CPU_INSTRUCTION_FLAGS}) + + target_compile_options(${target} PRIVATE + ${C_FLAGS} + $<$:${CXX_FLAGS}> + $<$:${DEBUG_FLAGS}> + $<$:${RELEASE_FLAGS}> + ${CPU_FLAGS} + ) + target_compile_definitions(${target} PRIVATE + _LIB + $<$:NDEBUG> + ) + message("Apply Common Flags Clang to " ${target}) + endif() +endfunction() + +# Function to apply OS-specific compiler flags to a target +function(apply_os_compiler_flags target) + apply_common_compiler_flags(${target}) + + if(IS_MSVC) + separate_arguments(OS_C_FLAGS_LIST WINDOWS_COMMAND ${OS_C_FLAGS}) + separate_arguments(OS_CXX_FLAGS_LIST WINDOWS_COMMAND ${OS_CXX_FLAGS}) + + target_compile_options(${target} PRIVATE + ${OS_C_FLAGS_LIST} + $<$:${OS_CXX_FLAGS_LIST}> + ) + message("Apply OS Flags MSVC to " ${target}) + else() + separate_arguments(OS_C_FLAGS_LIST UNIX_COMMAND ${OS_C_FLAGS}) + separate_arguments(OS_CXX_FLAGS_LIST UNIX_COMMAND ${OS_CXX_FLAGS}) + + target_compile_options(${target} PRIVATE + ${OS_C_FLAGS_LIST} + $<$:${OS_CXX_FLAGS_LIST}> + ) + message("Apply OS Flags CLANG to " ${target}) + endif() +endfunction() + +# Function to apply EFI-specific compiler flags to a target +function(apply_efi_compiler_flags target) + apply_common_compiler_flags(${target}) + + if(IS_MSVC) + separate_arguments(EFI_C_FLAGS_LIST WINDOWS_COMMAND ${EFI_C_FLAGS}) + separate_arguments(EFI_CXX_FLAGS_LIST WINDOWS_COMMAND ${EFI_CXX_FLAGS}) + + target_compile_options(${target} PRIVATE + ${EFI_C_FLAGS_LIST} + $<$:${EFI_CXX_FLAGS_LIST}> + ) + + if(EFI_LINK_FLAGS) + set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS ${EFI_LINK_FLAGS}) + endif() + message("Apply Efi Flags MSVC to " ${target}) + else() + separate_arguments(EFI_C_FLAGS_LIST UNIX_COMMAND ${EFI_C_FLAGS}) + separate_arguments(EFI_CXX_FLAGS_LIST UNIX_COMMAND ${EFI_CXX_FLAGS}) + + target_compile_options(${target} PRIVATE + ${EFI_C_FLAGS_LIST} + $<$:${EFI_CXX_FLAGS_LIST}> + ) + message("Apply Efi Flags CLANG to " ${target}) + endif() +endfunction() + +# Function to apply test-specific compiler flags to a target +function(apply_test_compiler_flags target) + apply_os_compiler_flags(${target}) + + if(IS_MSVC) + separate_arguments(TEST_FLAGS WINDOWS_COMMAND ${TEST_SPECIFIC_FLAGS}) + target_compile_options(${target} PRIVATE ${TEST_FLAGS}) + message("Apply Test Flags CLANG to " ${target}) + else() + separate_arguments(TEST_FLAGS UNIX_COMMAND ${TEST_SPECIFIC_FLAGS}) + target_compile_options(${target} PRIVATE ${TEST_FLAGS}) + + if(TEST_SPECIFIC_LINK_FLAGS) + separate_arguments(TEST_LINK_FLAGS UNIX_COMMAND ${TEST_SPECIFIC_LINK_FLAGS}) + target_link_options(${target} PRIVATE ${TEST_LINK_FLAGS}) + endif() + message("Apply Test Flags CLANG to " ${target}) + endif() +endfunction() \ No newline at end of file diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj index c7a4f2f45..e26d57a7c 100644 --- a/src/Qubic.vcxproj +++ b/src/Qubic.vcxproj @@ -48,7 +48,6 @@ - diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index e5184e84c..c16c54007 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -305,9 +305,6 @@ contracts - - contracts - contract_core diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h index 8425897ed..3b97da099 100644 --- a/src/contract_core/contract_def.h +++ b/src/contract_core/contract_def.h @@ -244,20 +244,6 @@ #define CONTRACT_STATE2_TYPE QDUEL2 #include "contracts/QDuel.h" -#ifndef NO_PULSE - -#undef CONTRACT_INDEX -#undef CONTRACT_STATE_TYPE -#undef CONTRACT_STATE2_TYPE - -#define PULSE_CONTRACT_INDEX 24 -#define CONTRACT_INDEX PULSE_CONTRACT_INDEX -#define CONTRACT_STATE_TYPE PULSE -#define CONTRACT_STATE2_TYPE PULSE2 -#include "contracts/Pulse.h" - -#endif - // new contracts should be added above this line #ifdef INCLUDE_CONTRACT_TEST_EXAMPLES @@ -369,9 +355,6 @@ constexpr struct ContractDescription {"QRP", 199, 10000, sizeof(IPO)}, // proposal in epoch 197, IPO in 198, construction and first use in 199 {"QTF", 199, 10000, sizeof(QTF)}, // proposal in epoch 197, IPO in 198, construction and first use in 199 {"QDUEL", 199, 10000, sizeof(QDUEL)}, // proposal in epoch 197, IPO in 198, construction and first use in 199 -#ifndef NO_PULSE - {"PULSE", 202, 10000, sizeof(PULSE)}, // proposal in epoch 200, IPO in 201, construction and first use in 202 -#endif // new contracts should be added above this line #ifdef INCLUDE_CONTRACT_TEST_EXAMPLES {"TESTEXA", 138, 10000, sizeof(TESTEXA)}, @@ -491,9 +474,6 @@ static void initializeContracts() REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QRP); REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QTF); REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(QDUEL); -#ifndef NO_PULSE - REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(PULSE); -#endif // new contracts should be added above this line #ifdef INCLUDE_CONTRACT_TEST_EXAMPLES REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(TESTEXA); diff --git a/src/contracts/QDuel.h b/src/contracts/QDuel.h index 45e26d7e8..202164a65 100644 --- a/src/contracts/QDuel.h +++ b/src/contracts/QDuel.h @@ -1,6 +1,7 @@ using namespace QPI; constexpr uint16 QDUEL_MAX_NUMBER_OF_ROOMS = 512; +constexpr uint64 QDUEL_MAX_NUMBER_OF_WINNER = 128; constexpr uint16 QDUEL_MINIMUM_DUEL_AMOUNT = 10000; constexpr uint8 QDUEL_DEV_FEE_PERCENT_BPS = 15; // 0.15% * QDUEL_PERCENT_SCALE constexpr uint8 QDUEL_BURN_FEE_PERCENT_BPS = 30; // 0.3% * QDUEL_PERCENT_SCALE @@ -82,6 +83,16 @@ struct QDUEL : public ContractBase sint64 maxStake; }; + struct WinnerData + { + id player1; + id player2; + id winner; + uint64 revenue; + + bool isValid() { return !isZero(player1) && !isZero(player2) && !isZero(winner) && revenue > 0; } + }; + struct AddUserData_input { id userId; @@ -103,6 +114,15 @@ struct QDUEL : public ContractBase UserData newUserData; }; + struct AddWinnerData_input + { + WinnerData winnerData; + }; + + struct AddWinnerData_output + { + }; + struct CreateRoom_input { id allowedPlayer; // If zero, anyone can join @@ -258,6 +278,8 @@ struct QDUEL : public ContractBase TransferToShareholders_output transferToShareholders_output; FinalizeRoom_input finalizeInput; FinalizeRoom_output finalizeOutput; + AddWinnerData_input addWinnerDataInput; + AddWinnerData_output addWinnerDataOutput; id winner; uint64 returnAmount; uint64 amount; @@ -406,6 +428,15 @@ struct QDUEL : public ContractBase sint64 freeAmount; }; + struct GetLastWinners_input + { + }; + struct GetLastWinners_output + { + Array winners; + uint8 returnCode; + }; + struct END_TICK_locals { UserData userData; @@ -434,6 +465,7 @@ struct QDUEL : public ContractBase REGISTER_USER_FUNCTION(GetTTLHours, 3); REGISTER_USER_FUNCTION(GetUserProfile, 4); REGISTER_USER_FUNCTION(CalculateRevenue, 5); + REGISTER_USER_FUNCTION(GetLastWinners, 6); } INITIALIZE() @@ -456,13 +488,6 @@ struct QDUEL : public ContractBase { state.firstTick = true; state.currentState = EState::LOCKED; - - if (qpi.epoch() == 201) - { - state.teamAddress = ID(_O, _C, _Z, _W, _N, _J, _S, _N, _R, _U, _Q, _J, _U, _A, _H, _Z, _C, _T, _R, _P, _N, _Y, _W, _G, _G, _E, _F, _C, _X, - _B, _A, _V, _F, _O, _P, _R, _S, _N, _U, _L, _U, _E, _B, _S, _P, _U, _T, _R, _Z, _N, _T, _G, _F, _B, _I, _E); - state.percentScale = QDUEL_PERCENT_SCALE; - } } END_EPOCH() @@ -727,6 +752,13 @@ struct QDUEL : public ContractBase CALL(FinalizeRoom, locals.finalizeInput, locals.finalizeOutput); output.returnCode = locals.finalizeOutput.returnCode; output.winner = locals.winner; + + locals.addWinnerDataInput.winnerData.player1 = locals.room.owner; + locals.addWinnerDataInput.winnerData.player2 = qpi.invocator(); + locals.addWinnerDataInput.winnerData.winner = locals.winner; + locals.addWinnerDataInput.winnerData.revenue = locals.calculateRevenue_output.winner; + + CALL(AddWinnerData, locals.addWinnerDataInput, locals.addWinnerDataOutput); } PUBLIC_PROCEDURE_WITH_LOCALS(SetPercentFees) @@ -848,10 +880,16 @@ struct QDUEL : public ContractBase output.devFee = div(smul(input.amount, static_cast(state.devFeePercentBps)), state.percentScale); output.burnFee = div(smul(input.amount, static_cast(state.burnFeePercentBps)), state.percentScale); output.shareholdersFee = - smul(div(div(smul(input.amount, static_cast(state.shareholdersFeePercentBps)), state.percentScale), 676ULL), 676ULL); + smul(div(div(smul(input.amount, static_cast(state.shareholdersFeePercentBps)), state.percentScale), 676ULL), 676ULL); output.winner = input.amount - (output.devFee + output.burnFee + output.shareholdersFee); } + PUBLIC_FUNCTION(GetLastWinners) + { + output.winners = state.lastWinners; + output.returnCode = toReturnCode(EReturnCode::SUCCESS); + } + PUBLIC_PROCEDURE_WITH_LOCALS(Deposit) { if (qpi.invocationReward() == 0) @@ -956,6 +994,8 @@ struct QDUEL : public ContractBase uint8 firstTick; EState currentState; uint16 percentScale; + Array lastWinners; + uint64 winnerCounter; protected: template static constexpr const T& min(const T& a, const T& b) { return (a < b) ? a : b; } @@ -1189,4 +1229,18 @@ struct QDUEL : public ContractBase } output.returnCode = toReturnCode(EReturnCode::SUCCESS); } + + PRIVATE_PROCEDURE(AddWinnerData) + { + if (!input.winnerData.isValid()) + { + return; + } + + // Adds winner + state.lastWinners.set(state.winnerCounter, input.winnerData); + + // Increment winnerCounter + state.winnerCounter = mod(++state.winnerCounter, state.lastWinners.capacity()); + } }; diff --git a/src/contracts/QReservePool.h b/src/contracts/QReservePool.h index 7dfedec44..30a1971ca 100644 --- a/src/contracts/QReservePool.h +++ b/src/contracts/QReservePool.h @@ -133,16 +133,6 @@ struct QRP : ContractBase REGISTER_USER_FUNCTION(GetAllowedSC, 2); } - BEGIN_EPOCH() - { - if (qpi.epoch() == 201) - { - state.teamAddress = ID(_O, _C, _Z, _W, _N, _J, _S, _N, _R, _U, _Q, _J, _U, _A, _H, _Z, _C, _T, _R, _P, _N, _Y, _W, _G, _G, _E, _F, _C, _X, - _B, _A, _V, _F, _O, _P, _R, _S, _N, _U, _L, _U, _E, _B, _S, _P, _U, _T, _R, _Z, _N, _T, _G, _F, _B, _I, _E); - state.ownerAddress = state.teamAddress; - } - } - END_EPOCH() { state.allowedSmartContracts.cleanup(); } PUBLIC_PROCEDURE_WITH_LOCALS(WithdrawReserve) diff --git a/src/contracts/QThirtyFour.h b/src/contracts/QThirtyFour.h index 75598431d..393edc746 100644 --- a/src/contracts/QThirtyFour.h +++ b/src/contracts/QThirtyFour.h @@ -5,6 +5,7 @@ constexpr uint64 QTF_MAX_NUMBER_OF_PLAYERS = 1024; constexpr uint64 QTF_RANDOM_VALUES_COUNT = 4; constexpr uint64 QTF_MAX_RANDOM_VALUE = 30; constexpr uint64 QTF_TICKET_PRICE = 1000000; +constexpr uint64 QTF_WINNING_COMBINATIONS_HISTORY_SIZE = 128; // Baseline split for k2/k3 when FR is OFF (per spec: k3=40%, k2=28% of Winners block). // Initial 32% of Winners block is unallocated; overflow will also include unawarded k2/k3 funds. @@ -94,11 +95,28 @@ struct QTF : ContractBase { id player; Array randomValues; + + bool isValid() const { return !isZero(player); } + }; + + struct WinnerPlayerData + { + id player; + Array randomValues; + uint32 wonAmount; + + void addPlayerData(const PlayerData& data) + { + player = data.player; + randomValues = data.randomValues; + } + + bool isValid() const { return !isZero(player); } }; struct WinnerData { - Array winners; + Array winners; Array winnerValues; uint64 winnerCounter; uint16 epoch; @@ -664,6 +682,7 @@ struct QTF : ContractBase uint64 rlShares; // Cache for countMatches results to avoid redundant calculations Array cachedMatches; + WinnerPlayerData winnerPlayerData; }; struct END_EPOCH_locals @@ -684,6 +703,45 @@ struct QTF : ContractBase bit isScheduledToday; }; + struct GetPlayers_input + { + }; + + struct GetPlayers_output + { + Array players; + uint8 returnCode; + }; + + struct GetWinningCombinationsHistory_input + { + }; + + struct GetWinningCombinationsHistory_output + { + struct WinningCombination + { + Array values; + }; + + Array history; + uint8 returnCode; + }; + + struct SyncJackpot_input + { + }; + + struct SyncJackpot_output + { + uint8 returnCode; + }; + + struct SyncJackpot_locals + { + Entity entity; + }; + // Contract lifecycle methods INITIALIZE() { @@ -706,6 +764,8 @@ struct QTF : ContractBase REGISTER_USER_PROCEDURE(SetSchedule, 3); REGISTER_USER_PROCEDURE(SetTargetJackpot, 4); REGISTER_USER_PROCEDURE(SetDrawHour, 5); + REGISTER_USER_PROCEDURE(SyncJackpot, 6); + REGISTER_USER_FUNCTION(GetTicketPrice, 1); REGISTER_USER_FUNCTION(GetNextEpochData, 2); REGISTER_USER_FUNCTION(GetWinnerData, 3); @@ -715,6 +775,8 @@ struct QTF : ContractBase REGISTER_USER_FUNCTION(GetState, 7); REGISTER_USER_FUNCTION(GetFees, 8); REGISTER_USER_FUNCTION(EstimatePrizePayouts, 9); + REGISTER_USER_FUNCTION(GetPlayers, 10); + REGISTER_USER_FUNCTION(GetWinningCombinationsHistory, 11); } BEGIN_EPOCH() @@ -732,13 +794,6 @@ struct QTF : ContractBase RL::makeDateStamp(qpi.year(), qpi.month(), qpi.day(), state.lastDrawDateStamp); clearEpochState(state); enableBuyTicket(state, state.lastDrawDateStamp != RL_DEFAULT_INIT_TIME); - - if (qpi.epoch() == 201) - { - state.teamAddress = ID(_O, _C, _Z, _W, _N, _J, _S, _N, _R, _U, _Q, _J, _U, _A, _H, _Z, _C, _T, _R, _P, _N, _Y, _W, _G, _G, _E, _F, _C, _X, - _B, _A, _V, _F, _O, _P, _R, _S, _N, _U, _L, _U, _E, _B, _S, _P, _U, _T, _R, _Z, _N, _T, _G, _F, _B, _I, _E); - state.ownerAddress = state.teamAddress; - } } // Settle and reset at epoch end (uses locals buffer) @@ -967,6 +1022,20 @@ struct QTF : ContractBase output.returnCode = toReturnCode(EReturnCode::SUCCESS); } + PUBLIC_PROCEDURE_WITH_LOCALS(SyncJackpot) + { + if (qpi.invocator() != state.ownerAddress) + { + output.returnCode = toReturnCode(EReturnCode::ACCESS_DENIED); + return; + } + + qpi.getEntity(SELF, locals.entity); + state.jackpot = locals.entity.incomingAmount - locals.entity.outgoingAmount; + + output.returnCode = toReturnCode(EReturnCode::SUCCESS); + } + // Functions PUBLIC_FUNCTION(GetTicketPrice) { output.ticketPrice = state.ticketPrice; } PUBLIC_FUNCTION(GetNextEpochData) { output.nextEpochData = state.nextEpochData; } @@ -1080,6 +1149,18 @@ struct QTF : ContractBase } } + PUBLIC_FUNCTION(GetPlayers) + { + output.players = state.players; + output.returnCode = toReturnCode(EReturnCode::SUCCESS); + } + + PUBLIC_FUNCTION(GetWinningCombinationsHistory) + { + copyMemory(output.history, state.winningCombinationsHistory); + output.returnCode = toReturnCode(EReturnCode::SUCCESS); + } + protected: static void clearEpochState(QTF& state) { clearPlayerData(state); } @@ -1141,14 +1222,14 @@ struct QTF : ContractBase static void clearWinerData(QTF& state) { setMemory(state.lastWinnerData, 0); } - static void fillWinnerData(QTF& state, const PlayerData& playerData, const Array& winnerValues, + static void fillWinnerData(QTF& state, const WinnerPlayerData& winnerPlayerData, const Array& winnerValues, const uint16& epoch) { - if (!isZero(playerData.player)) + if (winnerPlayerData.isValid()) { if (state.lastWinnerData.winnerCounter < state.lastWinnerData.winners.capacity()) { - state.lastWinnerData.winners.set(state.lastWinnerData.winnerCounter++, playerData); + state.lastWinnerData.winners.set(state.lastWinnerData.winnerCounter++, winnerPlayerData); } } @@ -1156,39 +1237,32 @@ struct QTF : ContractBase state.lastWinnerData.epoch = epoch; } - WinnerData lastWinnerData; // last winners snapshot - - NextEpochData nextEpochData; // queued config (ticket price) + static void addWinningCombinationToHistory(QTF& state, const Array& winnerValues) + { + state.winningCombinationsHistory.set(state.winningCombinationsCount, winnerValues); + state.winningCombinationsCount = mod(++state.winningCombinationsCount, state.winningCombinationsHistory.capacity()); + } + WinnerData lastWinnerData; // last winners snapshot + NextEpochData nextEpochData; // queued config (ticket price) Array players; // current epoch tickets - - id teamAddress; // Dev/team payout address - - id ownerAddress; // config authority - - uint64 numberOfPlayers; // tickets count in epoch - - uint64 ticketPrice; // active ticket price - - uint64 jackpot; // jackpot balance - - uint64 targetJackpot; // FR target jackpot - - uint64 overflowAlphaBP; // baseline reserve share of overflow (bp) - - uint8 schedule; // bitmask of draw days - - uint8 drawHour; // draw hour UTC - - uint32 lastDrawDateStamp; // guard to avoid multiple draws per day - - bit frActive; // FR flag - - uint16 frRoundsSinceK4; // rounds since last jackpot hit - - uint16 frRoundsAtOrAboveTarget; // hysteresis counter for FR off - - uint8 currentState; // bitmask of STATE_* flags (e.g., STATE_SELLING) + id teamAddress; // Dev/team payout address + id ownerAddress; // config authority + uint64 numberOfPlayers; // tickets count in epoch + uint64 ticketPrice; // active ticket price + uint64 jackpot; // jackpot balance + uint64 targetJackpot; // FR target jackpot + uint64 overflowAlphaBP; // baseline reserve share of overflow (bp) + uint8 schedule; // bitmask of draw days + uint8 drawHour; // draw hour UTC + uint32 lastDrawDateStamp; // guard to avoid multiple draws per day + bit frActive; // FR flag + uint16 frRoundsSinceK4; // rounds since last jackpot hit + uint16 frRoundsAtOrAboveTarget; // hysteresis counter for FR off + uint8 currentState; // bitmask of STATE_* flags (e.g., STATE_SELLING) + Array, QTF_WINNING_COMBINATIONS_HISTORY_SIZE> + winningCombinationsHistory; // ring buffer of winning combinations + uint64 winningCombinationsCount; // next write position in ring buffer private: // Core settlement pipeline for one epoch: fees, FR redirects, payouts, jackpot/reserve updates. @@ -1425,13 +1499,17 @@ struct QTF : ContractBase if (locals.matches == 2 && locals.countK2 > 0 && locals.k2PerWinner > 0) { qpi.transfer(state.players.get(locals.i).player, locals.k2PerWinner); - fillWinnerData(state, state.players.get(locals.i), locals.winningValues, locals.currentEpoch); + locals.winnerPlayerData.addPlayerData(state.players.get(locals.i)); + locals.winnerPlayerData.wonAmount = static_cast(locals.k2PerWinner); + fillWinnerData(state, locals.winnerPlayerData, locals.winningValues, locals.currentEpoch); } // k3 payout if (locals.matches == 3 && locals.countK3 > 0 && locals.k3PerWinner > 0) { qpi.transfer(state.players.get(locals.i).player, locals.k3PerWinner); - fillWinnerData(state, state.players.get(locals.i), locals.winningValues, locals.currentEpoch); + locals.winnerPlayerData.addPlayerData(state.players.get(locals.i)); + locals.winnerPlayerData.wonAmount = static_cast(locals.k3PerWinner); + fillWinnerData(state, locals.winnerPlayerData, locals.winningValues, locals.currentEpoch); } // k4 payout (jackpot) if (locals.matches == 4 && locals.countK4 > 0) @@ -1440,7 +1518,9 @@ struct QTF : ContractBase { qpi.transfer(state.players.get(locals.i).player, locals.jackpotPerK4Winner); } - fillWinnerData(state, state.players.get(locals.i), locals.winningValues, locals.currentEpoch); + locals.winnerPlayerData.addPlayerData(state.players.get(locals.i)); + locals.winnerPlayerData.wonAmount = static_cast(locals.jackpotPerK4Winner); + fillWinnerData(state, locals.winnerPlayerData, locals.winningValues, locals.currentEpoch); } ++locals.i; @@ -1449,6 +1529,7 @@ struct QTF : ContractBase // Always save winning values and epoch, even if no winners state.lastWinnerData.winnerValues = locals.winningValues; state.lastWinnerData.epoch = locals.currentEpoch; + addWinningCombinationToHistory(state, locals.winningValues); // Post-jackpot (k4) logic: reset counters and reseed if jackpot was hit if (locals.countK4 > 0) @@ -1604,7 +1685,7 @@ struct QTF : ContractBase PRIVATE_FUNCTION_WITH_LOCALS(CheckContractBalance) { qpi.getEntity(SELF, locals.entity); - output.actualBalance = RL::max(locals.entity.incomingAmount - locals.entity.outgoingAmount, 0i64); + output.actualBalance = RL::max(locals.entity.incomingAmount - locals.entity.outgoingAmount, 0LL); output.hasEnough = (output.actualBalance >= input.expectedRevenue); } diff --git a/src/contracts/README_QRWA_TEST.md b/src/contracts/README_QRWA_TEST.md new file mode 100644 index 000000000..999b4dc43 --- /dev/null +++ b/src/contracts/README_QRWA_TEST.md @@ -0,0 +1,341 @@ +# Qubic Smart Contract – Entwicklungs-Checkliste & Testreferenz + +## Schnellübersicht: Was den Node crashen kann + +| Problem | Symptom | Lösung | +|---------|---------|--------| +| `struct XXXX2 {}` fehlt | Node startet nicht / Crash | Leere Struct vor der Haupt-Struct definieren | +| State > 1 GB | Node startet nicht | HashMap-Kapazitäten reduzieren | +| Verbotene C++ Features | Kompiliert evtl., crasht zur Laufzeit | Siehe Verbotsliste unten | +| Falsche Adress-Länge in `ID()` | Kompiliert nicht (Parameteranzahl) | Genau 56 Zeichen (ohne 4-Char Checksum) | + +--- + +## 1. Dateistruktur + +``` +src/contracts/YourContract.h ← Der Contract (eine einzige .h Datei) +src/contract_core/contract_def.h ← Registrierung (3 Stellen) +test/contract_yourcontract.cpp ← GoogleTest Tests +``` + +### Contract-Datei Grundgerüst + +```cpp +using namespace QPI; + +// Globale Konstanten – MÜSSEN mit Contract-Name prefixed sein +constexpr uint64 MYCONTRACT_SOME_VALUE = 42; + +// WICHTIG: Sekundäre State-Struct (für zukünftige EXPAND Events) +struct MYCONTRACT2 +{ +}; + +// Haupt-State-Struct +struct MYCONTRACT : public ContractBase +{ + // ... State Members, Procedures, Functions ... +}; +``` + +### Registrierung in `contract_def.h` (3 Stellen!) + +**Stelle 1** – Contract Index & Include: +```cpp +#undef CONTRACT_INDEX +#undef CONTRACT_STATE_TYPE +#undef CONTRACT_STATE2_TYPE + +#define MYCONTRACT_CONTRACT_INDEX 20 +#define CONTRACT_INDEX MYCONTRACT_CONTRACT_INDEX +#define CONTRACT_STATE_TYPE MYCONTRACT +#define CONTRACT_STATE2_TYPE MYCONTRACT2 // ← MUSS existieren! +#include "contracts/MyContract.h" +``` + +**Stelle 2** – Contract Description: +```cpp +{"MYCON", 197, 10000, sizeof(MYCONTRACT)}, +// Format: {"ASSET_NAME", CONSTRUCTION_EPOCH, DESTRUCTION_EPOCH, sizeof(STATE)} +// Asset-Name: max 7 Zeichen, erster Buchstabe A-Z, danach A-Z oder 0-9 +``` + +**Stelle 3** – Registrierung: +```cpp +REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(MYCONTRACT); +``` + +--- + +## 2. Verbotene C++ Features (Crashverursacher!) + +### Absolut verboten – kein einziges Vorkommen erlaubt + +| Verboten | Grund | Alternative | +|----------|-------|-------------| +| `*` (Pointer) | Sicherheitsrisiko | `*` nur für Multiplikation erlaubt | +| `[` und `]` | Low-Level Arrays ohne Bounds-Check | `Array` aus QPI verwenden | +| `#` (Preprocessor) | `#include`, `#define`, `#ifdef` etc. | Keine – nur `constexpr` für Konstanten | +| `"string"` | Sprung zu zufälligem Speicher | `STATIC_ASSERT` Macro statt `static_assert("msg")` | +| `'c'` (Char-Literal) | Wie Strings | Nicht verwenden | +| `float` / `double` | Nicht deterministische Arithmetik | `uint64`, `sint64`, `uint128` | +| `/` (Division) | Inkonsistentes Verhalten bei /0 | `div(a, b)` – gibt 0 bei /0 | +| `%` (Modulo) | Inkonsistentes Verhalten bei %0 | `mod(a, b)` – gibt 0 bei %0 | +| `...` (Variadic) | Verboten | Keine | +| `__` (Doppel-Underscore) | Compiler-Interna | Einfachen Underscore verwenden | +| `union` | Täuschung bei Code-Audit | `struct` verwenden | +| `typedef` (global) | Nur lokal erlaubt | `using` nur in Structs/Funktionen | +| `const_cast` | Sicherheitsrisiko | Nicht verwenden | +| `QpiContext` | Interne Klasse | Nicht referenzieren | +| `::` (Scope Resolution) | Nur für Structs/Enums aus Contract & qpi.h | Kein Zugriff auf fremde Namespaces | + +### Einzige erlaubte Ausnahme bei `using`: +```cpp +using namespace QPI; // ← OK am Dateianfang +``` + +--- + +## 3. Variablen-Regeln + +### ❌ VERBOTEN: Lokale Variablen auf dem Stack +```cpp +PUBLIC_PROCEDURE(Bad) +{ + uint64 counter = 0; // ❌ VERBOTEN - Stack-Variable + for (uint64 i = 0; ...) // ❌ VERBOTEN - Loop-Variable auf Stack +} +``` + +### ✅ RICHTIG: Alles in `_locals` Struct +```cpp +struct MyProc_locals +{ + uint64 counter; + uint64 i; +}; +PUBLIC_PROCEDURE_WITH_LOCALS(MyProc) +{ + locals.counter = 0; // ✅ OK + for (locals.i = 0; locals.i < 10; locals.i++) // ✅ OK + { + // ... + } +} +``` + +### Verfügbare Variablen in Procedures/Functions + +| Variable | Verfügbar in | Beschreibung | +|----------|-------------|--------------| +| `state` | Procedures: read/write, Functions: read-only | Contract State | +| `input` | Beide | Eingabedaten | +| `output` | Beide | Ausgabedaten (mit 0 initialisiert) | +| `locals` | `_WITH_LOCALS` Varianten | Lokale Variablen (mit 0 initialisiert) | +| `qpi` | Beide | QPI-Funktionen (`qpi.transfer()`, `qpi.tick()`, etc.) | + +--- + +## 4. ID() Macro – Adressformat + +### ❌ FALSCH: 60 Zeichen (mit Checksum) +``` +JHUIZPGZNMTHPCZBAMSZQGBJCAAOGDAQVFHWYKLEGGRM JEJSXTREUVCPYHS +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ +56 Zeichen Basis-Adresse 4 Checksum +``` + +### ✅ RICHTIG: Genau 56 Zeichen (ohne Checksum) +```cpp +state.mAdminAddress = ID( + _J, _H, _U, _I, _Z, _P, _G, _Z, _N, _M, _T, _H, _P, _C, _Z, _I, + _B, _A, _M, _S, _Z, _Q, _G, _B, _J, _C, _O, _A, _O, _G, _D, _A, + _Q, _V, _F, _H, _W, _Y, _K, _L, _E, _G, _G, _R, _M, _J, _E, _J, + _S, _X, _T, _R, _E, _U, _V, _C // ← 56 Parameter, KEINE Checksum +); +``` + +**Tipp:** Qubic-Adressen sind immer 60 Zeichen lang (56 Basis + 4 Checksum). Im Code die **letzten 4 Zeichen abschneiden**. + +--- + +## 5. State-Größenlimit + +**Maximum: 1 GB (1.073.741.824 Bytes)** + +### HashMap Speicherverbrauch berechnen + +``` +HashMap ≈ L × (sizeof(KeyT) + sizeof(ValueT)) + L/32 × 8 + 16 +``` + +| Beispiel | Größe | +|----------|-------| +| `HashMap` | ~80.5 MB | +| `HashMap` | ~40.3 MB | +| `HashMap` | ~2.5 MB | + +### qRWA State-Berechnung (aktuell) + +| Komponente | Anzahl | Größe | +|-----------|--------|-------| +| HashMap | 5× | ~403 MB | +| HashMap | 2× | ~161 MB | +| Sonstige (Arrays, Skalare) | - | ~0.1 MB | +| **Gesamt** | | **~564 MB (55% vom Limit)** | + +--- + +## 6. Procedure & Function Typen + +### System Procedures (automatisch vom Core aufgerufen) + +| Macro | Wann | Zweck | +|-------|------|-------| +| `INITIALIZE()` | Einmal nach IPO | State initialisieren | +| `BEGIN_EPOCH()` | Jede Epoch | Snapshots, Resets | +| `END_EPOCH()` | Jede Epoch | Voting auswerten, Daten kopieren | +| `BEGIN_TICK()` | Jeder Tick | Vor Transaktionen | +| `END_TICK()` | Jeder Tick | **Vorsicht: läuft JEDEN Tick!** | +| `POST_INCOMING_TRANSFER()` | Bei QU-Eingang | Revenue-Tracking | +| `PRE_ACQUIRE_SHARES()` | Asset-Transfer | Erlaubnis prüfen | +| `POST_ACQUIRE_SHARES()` | Nach Asset-Transfer | Buchführung | + +### User Procedures (durch Transaktionen aufgerufen) + +```cpp +struct MyProc_input { uint64 value; }; +struct MyProc_output { uint64 status; }; +PUBLIC_PROCEDURE(MyProc) { /* kann state ändern */ } +PRIVATE_PROCEDURE(MyProc) { /* nicht von anderen Contracts aufrufbar */ } +``` + +### User Functions (read-only Abfragen) + +```cpp +struct MyFunc_input {}; +struct MyFunc_output { uint64 result; }; +PUBLIC_FUNCTION(MyFunc) { /* state ist const, nur lesen */ } +``` + +### Input/Output Struct Regeln + +Erlaubte Typen in `_input` und `_output`: +- `uint8`, `uint16`, `uint32`, `uint64`, `sint8`, `sint16`, `sint32`, `sint64` +- `bit`, `id` +- `Array`, `BitArray` +- Eigene Structs die nur erlaubte Typen enthalten + +**Verboten** in Input/Output: `Collection`, `HashMap`, `HashSet` (inkonsistenter interner State) + +--- + +## 7. Verfügbare Container-Typen + +| Typ | Beschreibung | Hinweis | +|-----|-------------|---------| +| `Array` | Feste Größe, L muss 2^N sein | Bounds-geprüft | +| `BitArray` | Bit-Array, L muss 2^N sein | Min. 8 Bytes | +| `HashMap` | Key-Value Store | `cleanup()` am Epochenende! | +| `HashSet` | Nur Keys | `cleanup()` am Epochenende! | +| `Collection` | Priority Queues pro ID | `cleanup()` am Epochenende! | + +**Wichtig:** Nach Entfernen von Elementen aus HashMap/HashSet/Collection → `cleanupIfNeeded()` oder `cleanup()` am `END_EPOCH` aufrufen! + +--- + +## 8. QPI Wichtige Funktionen + +```cpp +// Informationen +qpi.tick() // Aktueller Tick +qpi.epoch() // Aktuelle Epoch +qpi.now() // Aktuelle Zeit (DateAndTime) +qpi.invocator() // Wer hat aufgerufen (User-ID oder Contract-ID) +qpi.invocationReward() // Wie viel QU wurde gesendet +qpi.originator() // Ursprünglicher Transaktions-Ersteller + +// Transfers +qpi.transfer(dest, amount) // QU senden +qpi.burn(amount) // QU verbrennen (füllt Fee-Reserve) + +// Assets +qpi.issueAsset(name, issuer, numberOfShares, unitOfMeasurement, ...) +qpi.numberOfShares(asset, ownershipSelect, possessionSelect) +qpi.transferShareOwnershipAndPossession(name, issuer, owner, possessor, amount, newOwnerAndPossessor) +qpi.releaseShares(asset, owner, possessor, amount, destOwnership, destPossession, fee) +qpi.distributeDividends(amountPerShare) // Dividende an Contract-Shareholder + +// Mathematik (statt / und %) +div(a, b) // Division (gibt 0 bei b==0) +mod(a, b) // Modulo (gibt 0 bei b==0) +sadd(a, b) // Saturating Add (kein Overflow) +smul(a, b) // Saturating Multiply +``` + +--- + +## 9. Verifikations-Tool + +### Automatisch in GitHub Actions (für PRs zu develop/main): +Das [Qubic Contract Verification Tool](https://github.com/Franziska-Mueller/qubic-contract-verify) prüft automatisch alle Regeln. + +### Manuell ausführen: +```bash +# Als GitHub Action in eigenem Repo: +- uses: Franziska-Mueller/qubic-contract-verify@v1.0.4 + with: + filepaths: 'src/contracts/qRWA.h' + +# Oder lokal bauen: +git clone https://github.com/Franziska-Mueller/qubic-contract-verify +cd qubic-contract-verify +cd deps/CppParser && mkdir builds && cd builds && cmake .. && make && cd ../../.. +mkdir build && cd build && cmake .. && make +./src/Release/contractverify /path/to/qRWA.h +``` + +--- + +## 10. Test-Checkliste vor Deployment + +- [ ] `struct XXXX2 {}` vorhanden (für `CONTRACT_STATE2_TYPE`) +- [ ] Keine verbotenen C++ Features (siehe Abschnitt 2) +- [ ] Alle Variablen in `_locals` Structs (keine Stack-Variablen) +- [ ] `ID()` Macro: genau 56 Zeichen pro Adresse +- [ ] State-Größe < 1 GB +- [ ] `REGISTER_USER_FUNCTIONS_AND_PROCEDURES()` implementiert +- [ ] Registrierung in `contract_def.h` an allen 3 Stellen +- [ ] `div<>()` und `mod()` statt `/` und `%` +- [ ] Keine `#include` im finalen Code +- [ ] Input/Output Structs nur erlaubte Typen +- [ ] HashMap/HashSet `cleanup()` am Epochenende +- [ ] Globale Konstanten mit Contract-Name prefixed (`QRWA_...`) +- [ ] Tests in GoogleTest Framework geschrieben +- [ ] Kompiliert ohne Warnings +- [ ] Contract Verification Tool bestanden +- [ ] Testnet mit mehreren Nodes stabil + +--- + +## 11. qRWA-spezifische Notizen + +### Aktuelle Test-Konfiguration +- Branch: `qrwa-dedicated-pool` +- Contract Index: 20 +- Construction Epoch: 197 +- Payout-Trigger: `qpi.tick() >= 44602200` (Test-Modus) +- 6 Governance-Adressen in `INITIALIZE()` (alle 56 Chars) + +### Bekannte Fixes (bereits angewendet) +1. **`struct QRWA2 {}`** hinzugefügt – fehlte komplett, crashte den Node +2. **Adressen auf 56 Zeichen** getrimmt – `ID()` Macro erwartet exakt 56 Parameter + +### Für Production zurücksetzen +```cpp +// In END_TICK: Test-Bedingungen ersetzen durch: +if (qpi.dayOfWeek(...) == QRWA_PAYOUT_DAY && locals.now.getHour() == QRWA_PAYOUT_HOUR) +// und: +if (locals.msSinceLastPayout >= QRWA_MIN_PAYOUT_INTERVAL_MS) +``` diff --git a/src/contracts/RandomLottery.h b/src/contracts/RandomLottery.h index 93fbdcbdf..8b946479a 100644 --- a/src/contracts/RandomLottery.h +++ b/src/contracts/RandomLottery.h @@ -405,13 +405,6 @@ struct RL : public ContractBase // Open selling for the new epoch enableBuyTicket(state, state.lastDrawDateStamp != RL_DEFAULT_INIT_TIME); - - if (qpi.epoch() == 201) - { - state.teamAddress = ID(_O, _C, _Z, _W, _N, _J, _S, _N, _R, _U, _Q, _J, _U, _A, _H, _Z, _C, _T, _R, _P, _N, _Y, _W, _G, _G, _E, _F, _C, _X, - _B, _A, _V, _F, _O, _P, _R, _S, _N, _U, _L, _U, _E, _B, _S, _P, _U, _T, _R, _Z, _N, _T, _G, _F, _B, _I, _E); - state.ownerAddress = state.teamAddress; - } } END_EPOCH() diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index cdd8c66c1..5fe8a3597 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -65,6 +65,10 @@ constexpr uint64 QRWA_LOG_TYPE_INCOMING_REVENUE_DEDICATED = 11; /**************** CONTRACT STATE *******************/ /***************************************************/ +struct QRWA2 +{ +}; + struct QRWA : public ContractBase { friend class ContractTestingQRWA; @@ -1119,25 +1123,25 @@ struct QRWA : public ContractBase _J, _H, _U, _I, _Z, _P, _G, _Z, _N, _M, _T, _H, _P, _C, _Z, _I, _B, _A, _M, _S, _Z, _Q, _G, _B, _J, _C, _O, _A, _O, _G, _D, _A, _Q, _V, _F, _H, _W, _Y, _K, _L, _E, _G, _G, _R, _M, _J, _E, _J, - _S, _X, _T, _R, _E, _U, _V, _C, _F, _G, _X, _L + _S, _X, _T, _R, _E, _U, _V, _C ); state.mCurrentGovParams.electricityAddress = ID( _J, _T, _I, _D, _B, _A, _Q, _S, _M, _H, _F, _S, _F, _D, _P, _C, _M, _Q, _G, _B, _Z, _V, _R, _N, _N, _T, _K, _A, _J, _N, _Z, _G, _E, _O, _L, _Y, _O, _U, _F, _N, _U, _E, _S, _M, _Q, _L, _N, _G, - _W, _J, _B, _A, _R, _G, _Q, _B, _Z, _L, _N, _H + _W, _J, _B, _A, _R, _G, _Q, _B ); state.mCurrentGovParams.maintenanceAddress = ID( _J, _N, _R, _Z, _M, _D, _C, _B, _Q, _Y, _F, _C, _F, _A, _T, _G, _L, _O, _Z, _V, _E, _W, _K, _F, _W, _E, _P, _D, _H, _S, _I, _G, _R, _F, _O, _F, _C, _G, _P, _J, _F, _E, _Z, _Q, _Y, _Q, _Z, _P, - _B, _K, _M, _S, _S, _V, _J, _B, _Y, _U, _M, _E + _B, _K, _M, _S, _S, _V, _J, _B ); state.mCurrentGovParams.reinvestmentAddress = ID( _C, _Q, _V, _D, _N, _N, _G, _N, _I, _R, _L, _T, _B, _G, _D, _J, _F, _P, _W, _U, _J, _A, _Y, _O, _D, _J, _E, _C, _L, _N, _W, _W, _U, _T, _V, _U, _N, _W, _T, _A, _M, _D, _S, _Y, _F, _B, _N, _K, - _S, _N, _D, _C, _A, _Y, _U, _D, _C, _T, _M, _M + _S, _N, _D, _C, _A, _Y, _U, _D ); // QMINE DEV's Address for receiving rewards from moved QMINE tokens @@ -1146,7 +1150,7 @@ struct QRWA : public ContractBase _R, _U, _J, _G, _S, _W, _E, _E, _E, _U, _C, _O, _O, _C, _D, _P, _A, _M, _U, _U, _Z, _S, _H, _I, _R, _Y, _N, _D, _A, _F, _D, _O, _W, _X, _F, _W, _A, _Q, _L, _Z, _R, _B, _N, _X, _G, _E, _X, _Q, - _W, _B, _D, _C, _V, _U, _Z, _G, _T, _J, _A, _E + _W, _B, _D, _C, _V, _U, _Z, _G ); state.mCurrentGovParams.electricityPercent = 350; state.mCurrentGovParams.maintenancePercent = 50; @@ -1169,7 +1173,7 @@ struct QRWA : public ContractBase _P, _D, _Q, _T, _K, _K, _I, _R, _S, _I, _G, _A, _G, _A, _O, _L, _J, _W, _Z, _W, _T, _C, _B, _S, _F, _C, _Y, _A, _I, _Z, _I, _R, _Y, _C, _H, _E, _B, _K, _H, _B, _J, _H, _H, _B, _J, _N, _J, _H, - _W, _L, _Y, _G, _X, _S, _V, _E, _Q, _E, _F, _C + _W, _L, _Y, _G, _X, _S, _V, _E ); // Initialize total distributed diff --git a/src/network_core/peers.h b/src/network_core/peers.h index c10dee23d..52fa95428 100644 --- a/src/network_core/peers.h +++ b/src/network_core/peers.h @@ -572,6 +572,7 @@ static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char typ */ static bool isBogonAddress(const IPv4Address& address) { + return false; return (!address.u8[0]) || (address.u8[0] == 127) || (address.u8[0] == 10) diff --git a/src/oracle_core/net_msg_impl.h b/src/oracle_core/net_msg_impl.h index f29a2ddff..00653d366 100644 --- a/src/oracle_core/net_msg_impl.h +++ b/src/oracle_core/net_msg_impl.h @@ -4,8 +4,7 @@ #include "network_messages/oracles.h" #include "network_core/peers.h" -template -void OracleEngine::processRequestOracleData(Peer* peer, RequestResponseHeader* header) const +void OracleEngine::processRequestOracleData(Peer* peer, RequestResponseHeader* header) const { // check input ASSERT(header && peer); @@ -151,7 +150,7 @@ void OracleEngine::processRequestOracleData(Peer* peer, R } if (oqm.status == ORACLE_QUERY_STATUS_PENDING || oqm.status == ORACLE_QUERY_STATUS_COMMITTED) { - const ReplyState& replyState = replyStates[oqm.statusVar.pending.replyStateIndex]; + const OracleReplyState& replyState = replyStates[oqm.statusVar.pending.replyStateIndex]; payloadOqm->agreeingCommits = replyState.replyCommitHistogramCount[replyState.mostCommitsHistIdx]; payloadOqm->totalCommits = replyState.totalCommits; } diff --git a/src/oracle_core/oracle_engine.h b/src/oracle_core/oracle_engine.h index f4e5ebef9..a3e4efc55 100644 --- a/src/oracle_core/oracle_engine.h +++ b/src/oracle_core/oracle_engine.h @@ -120,32 +120,31 @@ struct OracleSubscriptionMetadata }; // State of received OM reply and computor commits for a single oracle query -template struct OracleReplyState { int64_t queryId; + // reply data provided by own OM node m256i ownReplyDigest; uint16_t ownReplySize; uint8_t ownReplyData[MAX_ORACLE_REPLY_SIZE + 2]; - // track state of own reply commits (when they are scheduled and when actually got executed) - uint16_t ownReplyCommitExecCount; - uint32_t ownReplyCommitComputorTxTick[ownComputorSeedsCount]; - uint32_t ownReplyCommitComputorTxExecuted[ownComputorSeedsCount]; + // track when own reply commits tx are scheduled (per own computorIdx) + uint32_t replyCommitScheduleTick[NUMBER_OF_COMPUTORS]; + // track execution of reply commit tx of each computor m256i replyCommitDigests[NUMBER_OF_COMPUTORS]; m256i replyCommitKnowledgeProofs[NUMBER_OF_COMPUTORS]; uint32_t replyCommitTicks[NUMBER_OF_COMPUTORS]; + // aggregation of reply commits (groups that share the same digest are counted in the same histogram bin) uint16_t replyCommitHistogramIdx[NUMBER_OF_COMPUTORS]; uint16_t replyCommitHistogramCount[NUMBER_OF_COMPUTORS]; uint16_t mostCommitsHistIdx; uint16_t totalCommits; + // tick for which reveal tx is scheduled or has been announced (used to reduce the number of reveal tx sent) uint32_t expectedRevealTxTick; - uint32_t revealTick; - uint32_t revealTxIndex; }; struct OracleRevenuePoints @@ -251,7 +250,6 @@ struct OracleEngineStatistics }; -template class OracleEngine { protected: @@ -274,11 +272,8 @@ class OracleEngine uint32_t queryIndexInTick; } contractQueryIdState; - // data type of state of received OM reply and computor commits for single oracle query (used before reveal) - typedef OracleReplyState ReplyState; - // state of received OM reply and computor commits for each oracle query (used before reveal) - ReplyState* replyStates; + OracleReplyState* replyStates; // index in replyStates to check next for empty slot (cyclic buffer) int32_t replyStatesIndex; @@ -316,8 +311,8 @@ class OracleEngine /// fast lookup of oracle query index (sequential position in queries array) from oracle query ID (composed of query tick and other info) QPI::HashMap* queryIdToIndex; - /// array of ownComputorSeedsCount public keys (mainly for testing, in EFI core this points to computorPublicKeys from special_entities.h) - const m256i* ownComputorPublicKeys; + /// pointer to global array of 676 computor public keys (in EFI core this points to broadcastedComputors.computors.publicKeys) + const m256i* computorPublicKeys; /// buffer used to store output of getNotification() OracleNotificationData notificationOutputBuffer; @@ -386,10 +381,10 @@ class OracleEngine } public: - /// Initialize object, passing array of own computor public keys (with number of elements given by template param ownComputorSeedsCount). - bool init(const m256i* ownComputorPublicKeys) + /// Initialize object, passing array of computor public keys + bool init(const m256i* computorPublicKeys) { - this->ownComputorPublicKeys = ownComputorPublicKeys; + this->computorPublicKeys = computorPublicKeys; lock = 0; // alloc arrays and set to 0 @@ -560,7 +555,7 @@ class OracleEngine queryMetadata.statusVar.pending.replyStateIndex = replyStateSlotIdx; // init reply state (temporary until reply is revealed) - ReplyState& replyState = replyStates[replyStateSlotIdx]; + OracleReplyState& replyState = replyStates[replyStateSlotIdx]; setMem(&replyState, sizeof(replyState), 0); replyState.queryId = queryId; @@ -696,7 +691,7 @@ class OracleEngine queryMetadata.statusVar.pending.replyStateIndex = replyStateSlotIdx; // init reply state (temporary until reply is revealed) - ReplyState& replyState = replyStates[replyStateSlotIdx]; + OracleReplyState& replyState = replyStates[replyStateSlotIdx]; setMem(&replyState, sizeof(replyState), 0); replyState.queryId = queryId; @@ -822,8 +817,10 @@ class OracleEngine // get reply state const auto replyStateIdx = oqm.statusVar.pending.replyStateIndex; ASSERT(replyStateIdx < MAX_SIMULTANEOUS_ORACLE_QUERIES); - ReplyState& replyState = replyStates[replyStateIdx]; + OracleReplyState& replyState = replyStates[replyStateIdx]; ASSERT(replyState.queryId == replyMessage->oracleQueryId); + if (replyState.queryId != replyMessage->oracleQueryId) + return; // return if we already got a reply if (replyState.ownReplySize) @@ -901,7 +898,6 @@ class OracleEngine * * @param txBuffer Buffer for constructing the transaction. Size must be at least MAX_TRANSACTION_SIZE bytes. * @param computorIdx Index of computor list of computors broadcasted by arbitrator. - * @param ownComputorIdx Index of computor in local array computorSeeds. * @param txScheduleTick Tick, in which the transaction is supposed to be scheduled. * @param startIdx Index returned by the previous call of this function if more than one tx is required. * @return 0 if no tx needs to be sent; UINT32_MAX if all pending commits are included in the created tx; @@ -914,12 +910,12 @@ class OracleEngine * the return value/startIdx. */ uint32_t getReplyCommitTransaction( - void* txBuffer, uint16_t computorIdx, uint16_t ownComputorIdx, + void* txBuffer, uint16_t computorIdx, uint32_t txScheduleTick, uint32_t startIdx = 0) { // check inputs ASSERT(txBuffer); - if (ownComputorIdx >= ownComputorSeedsCount || computorIdx >= NUMBER_OF_COMPUTORS || txScheduleTick <= system.tick) + if (computorIdx >= NUMBER_OF_COMPUTORS || txScheduleTick <= system.tick) return 0; // init data pointers and reply commit counter @@ -941,13 +937,13 @@ class OracleEngine const unsigned int replyIdx = replyIndices[idx]; if (replyIdx >= MAX_SIMULTANEOUS_ORACLE_QUERIES) continue; - ReplyState& replyState = replyStates[replyIdx]; + OracleReplyState& replyState = replyStates[replyIdx]; if (replyState.queryId <= 0 || replyState.ownReplySize == 0) continue; // tx already executed or scheduled? - if (replyState.ownReplyCommitComputorTxExecuted[ownComputorIdx] || - replyState.ownReplyCommitComputorTxTick[ownComputorIdx] >= system.tick) // TODO: > or >= ? + if (replyState.replyCommitTicks[computorIdx] || + replyState.replyCommitScheduleTick[computorIdx] >= system.tick) // TODO: > or >= ? continue; // additional commit required -> leave loop early to finish tx @@ -964,7 +960,7 @@ class OracleEngine KangarooTwelve(replyState.ownReplyData, replyState.ownReplySize + 2, &commits[commitsCount].replyKnowledgeProof, 32); // signal to schedule tx for given tick - replyState.ownReplyCommitComputorTxTick[ownComputorIdx] = txScheduleTick; + replyState.replyCommitScheduleTick[computorIdx] = txScheduleTick; // we have completed adding this commit ++commitsCount; @@ -975,7 +971,7 @@ class OracleEngine return 0; // finish all of tx except for signature - tx->sourcePublicKey = ownComputorPublicKeys[ownComputorIdx]; + tx->sourcePublicKey = computorPublicKeys[computorIdx]; tx->destinationPublicKey = m256i::zero(); tx->amount = 0; tx->tick = txScheduleTick; @@ -1067,8 +1063,10 @@ class OracleEngine // get reply state const auto replyStateIdx = oqm.statusVar.pending.replyStateIndex; ASSERT(replyStateIdx < MAX_SIMULTANEOUS_ORACLE_QUERIES); - ReplyState& replyState = replyStates[replyStateIdx]; + OracleReplyState& replyState = replyStates[replyStateIdx]; ASSERT(replyState.queryId == item->queryId); + if (replyState.queryId != item->queryId) + continue; // ignore commit if we already have processed a commit by this computor if (replyState.replyCommitTicks[compIdx] != 0) @@ -1079,17 +1077,6 @@ class OracleEngine replyState.replyCommitKnowledgeProofs[compIdx] = item->replyKnowledgeProof; replyState.replyCommitTicks[compIdx] = transaction->tick; - // if tx is from own computor, prevent rescheduling of commit tx - for (auto i = 0ull; replyState.ownReplyCommitExecCount < ownComputorSeedsCount && i < ownComputorSeedsCount; ++i) - { - if (!replyState.ownReplyCommitComputorTxExecuted[i] && ownComputorPublicKeys[i] == transaction->sourcePublicKey) - { - replyState.ownReplyCommitComputorTxExecuted[i] = transaction->tick; - ++replyState.ownReplyCommitExecCount; - break; - } - } - // update reply commit histogram // 1. search existing or free slot of digest in histogram array uint16_t histIdx = 0; @@ -1200,7 +1187,7 @@ class OracleEngine * Prepare OracleReplyRevealTransaction in txBuffer, setting all except signature. * * @param txBuffer Buffer for constructing the transaction. Size must be at least MAX_TRANSACTION_SIZE bytes. - * @param ownComputorIdx Index of computor in local array computorSeeds. + * @param computorIdx Index of own computor in array broadcasted by arbitrator. * @param txScheduleTick Tick, in which the transaction is supposed to be scheduled. * @param startIdx Index returned by the previous call of this function if more than one tx is required. * @return 0 if no tx needs to be sent; any other value indicates that another tx needs to be created and it @@ -1211,11 +1198,11 @@ class OracleEngine * Calling processOracleReplyRevealTransaction() between two calls of getReplyRevealTransaction() invalidates * the return value/startIdx. */ - uint32_t getReplyRevealTransaction(void* txBuffer, uint16_t ownComputorIdx, uint32_t txScheduleTick, uint32_t startIdx = 0) + uint32_t getReplyRevealTransaction(void* txBuffer, uint16_t computorIdx, uint32_t txScheduleTick, uint32_t startIdx = 0) { // check inputs ASSERT(txBuffer); - if (ownComputorIdx >= ownComputorSeedsCount || txScheduleTick <= system.tick) + if (computorIdx >= NUMBER_OF_COMPUTORS || txScheduleTick <= system.tick) return 0; // init data pointer @@ -1235,7 +1222,7 @@ class OracleEngine const unsigned int replyIdx = replyIndices[idx]; if (replyIdx >= MAX_SIMULTANEOUS_ORACLE_QUERIES) continue; - ReplyState& replyState = replyStates[replyIdx]; + OracleReplyState& replyState = replyStates[replyIdx]; if (replyState.queryId <= 0 || replyState.ownReplySize == 0) continue; const uint16_t mostCommitsCount = replyState.replyCommitHistogramCount[replyState.mostCommitsHistIdx]; @@ -1254,7 +1241,7 @@ class OracleEngine continue; // set all of tx except for signature - tx->sourcePublicKey = ownComputorPublicKeys[ownComputorIdx]; + tx->sourcePublicKey = computorPublicKeys[computorIdx]; tx->destinationPublicKey = m256i::zero(); tx->amount = 0; tx->tick = txScheduleTick; @@ -1275,7 +1262,7 @@ class OracleEngine appendText(dbgMsg, ", queryId "); appendNumber(dbgMsg, replyState.queryId, FALSE); appendText(dbgMsg, ", computorIdx "); - appendNumber(dbgMsg, computorIndex(tx->sourcePublicKey), FALSE); + appendNumber(dbgMsg, computorIdx, FALSE); addDebugMessage(dbgMsg); #endif @@ -1290,7 +1277,7 @@ class OracleEngine protected: // Check oracle reply reveal transaction. Returns reply state if okay or NULL otherwise. Also sets output param queryIndexOutput. // Caller is responsible for locking. - ReplyState* checkReplyRevealTransaction(const OracleReplyRevealTransactionPrefix* transaction, uint32_t* queryIndexOutput = nullptr) const + OracleReplyState* checkReplyRevealTransaction(const OracleReplyRevealTransactionPrefix* transaction, uint32_t* queryIndexOutput = nullptr) const { // check precondition for calling with ASSERTs ASSERT(transaction != nullptr); @@ -1328,8 +1315,10 @@ class OracleEngine // get reply state const auto replyStateIdx = oqm.statusVar.pending.replyStateIndex; ASSERT(replyStateIdx < MAX_SIMULTANEOUS_ORACLE_QUERIES); - ReplyState& replyState = replyStates[replyStateIdx]; + OracleReplyState& replyState = replyStates[replyStateIdx]; ASSERT(replyState.queryId == transaction->queryId); + if (replyState.queryId != transaction->queryId) + return nullptr; // compute digest of reply in reveal tx const void* replyData = transaction + 1; @@ -1384,7 +1373,7 @@ class OracleEngine LockGuard lockGuard(lock); // check tx and get reply state - ReplyState* replyState = checkReplyRevealTransaction(transaction); + OracleReplyState* replyState = checkReplyRevealTransaction(transaction); if (!replyState) return; @@ -1428,7 +1417,7 @@ class OracleEngine // check tx and get reply state + query metadata uint32_t queryIndex; - ReplyState* replyState = checkReplyRevealTransaction(transaction, &queryIndex); + OracleReplyState* replyState = checkReplyRevealTransaction(transaction, &queryIndex); if (!replyState) return false; OracleQueryMetadata& oqm = queries[queryIndex]; @@ -1590,7 +1579,7 @@ class OracleEngine // get reply state const auto replyStateIdx = oqm.statusVar.pending.replyStateIndex; ASSERT(replyStateIdx < MAX_SIMULTANEOUS_ORACLE_QUERIES); - ReplyState& replyState = replyStates[replyStateIdx]; + OracleReplyState& replyState = replyStates[replyStateIdx]; ASSERT(replyState.queryId == oqm.queryId); const uint16_t mostCommitsCount = replyState.replyCommitHistogramCount[replyState.mostCommitsHistIdx]; ASSERT(replyState.mostCommitsHistIdx < NUMBER_OF_COMPUTORS && mostCommitsCount <= NUMBER_OF_COMPUTORS); @@ -1889,7 +1878,7 @@ class OracleEngine } // shared status checks - const ReplyState* replyState = nullptr; + const OracleReplyState* replyState = nullptr; uint16_t agreeingCommits = 0; switch (oqm.status) { @@ -1980,7 +1969,7 @@ class OracleEngine { const uint32_t replyIdx = replyIndices[idx]; ASSERT(replyIdx < MAX_SIMULTANEOUS_ORACLE_QUERIES); - const ReplyState& replyState = replyStates[replyIdx]; + const OracleReplyState& replyState = replyStates[replyIdx]; ASSERT(replyState.queryId); uint32_t queryIndex = 0; ASSERT(queryIdToIndex->get(replyState.queryId, queryIndex) && queryIndex < oracleQueryCount); @@ -1995,7 +1984,7 @@ class OracleEngine { const uint32_t replyIdx = replyIndices[idx]; ASSERT(replyIdx < MAX_SIMULTANEOUS_ORACLE_QUERIES); - const ReplyState& replyState = replyStates[replyIdx]; + const OracleReplyState& replyState = replyStates[replyIdx]; ASSERT(replyState.queryId); uint32_t queryIndex = 0; ASSERT(queryIdToIndex->get(replyState.queryId, queryIndex) && queryIndex < oracleQueryCount); @@ -2110,7 +2099,7 @@ class OracleEngine void processRequestOracleData(Peer* peer, RequestResponseHeader* header) const; }; -GLOBAL_VAR_DECL OracleEngine oracleEngine; +GLOBAL_VAR_DECL OracleEngine oracleEngine; /* - Handle seamless transitions? Keep state? diff --git a/src/oracle_core/snapshot_files.h b/src/oracle_core/snapshot_files.h index 6b245706a..98b420526 100644 --- a/src/oracle_core/snapshot_files.h +++ b/src/oracle_core/snapshot_files.h @@ -30,8 +30,7 @@ struct OracleEngineSnapshotData // save state (excluding queryIdToIndex and unused parts of large buffers) -template -bool OracleEngine::saveSnapshot(unsigned short epoch, CHAR16* directory) const +bool OracleEngine::saveSnapshot(unsigned short epoch, CHAR16* directory) const { addEpochToFileName(ORACLE_SNAPSHOT_ENGINE_FILENAME, sizeof(ORACLE_SNAPSHOT_ENGINE_FILENAME) / sizeof(ORACLE_SNAPSHOT_ENGINE_FILENAME[0]), epoch); addEpochToFileName(ORACLE_SNAPSHOT_QUERY_METADATA_FILENAME, sizeof(ORACLE_SNAPSHOT_QUERY_METADATA_FILENAME) / sizeof(ORACLE_SNAPSHOT_QUERY_METADATA_FILENAME[0]), epoch); @@ -82,7 +81,7 @@ bool OracleEngine::saveSnapshot(unsigned short epoch, CHA } logToConsole(L"Saving oracle reply states ..."); - sizeToSave = sizeof(ReplyState) * MAX_SIMULTANEOUS_ORACLE_QUERIES; + sizeToSave = sizeof(OracleReplyState) * MAX_SIMULTANEOUS_ORACLE_QUERIES; sz = saveLargeFile(ORACLE_SNAPSHOT_REPLY_STATES_FILENAME, sizeToSave, (unsigned char*)replyStates, directory); if (sz != sizeToSave) { @@ -95,8 +94,7 @@ bool OracleEngine::saveSnapshot(unsigned short epoch, CHA return true; } -template -bool OracleEngine::loadSnapshot(unsigned short epoch, CHAR16* directory) +bool OracleEngine::loadSnapshot(unsigned short epoch, CHAR16* directory) { addEpochToFileName(ORACLE_SNAPSHOT_ENGINE_FILENAME, sizeof(ORACLE_SNAPSHOT_ENGINE_FILENAME) / sizeof(ORACLE_SNAPSHOT_ENGINE_FILENAME[0]), epoch); addEpochToFileName(ORACLE_SNAPSHOT_QUERY_METADATA_FILENAME, sizeof(ORACLE_SNAPSHOT_QUERY_METADATA_FILENAME) / sizeof(ORACLE_SNAPSHOT_QUERY_METADATA_FILENAME[0]), epoch); @@ -163,7 +161,7 @@ bool OracleEngine::loadSnapshot(unsigned short epoch, CHA } logToConsole(L"Loading oracle reply states ..."); - sizeToLoad = sizeof(ReplyState) * MAX_SIMULTANEOUS_ORACLE_QUERIES; + sizeToLoad = sizeof(OracleReplyState) * MAX_SIMULTANEOUS_ORACLE_QUERIES; sz = loadLargeFile(ORACLE_SNAPSHOT_REPLY_STATES_FILENAME, sizeToLoad, (unsigned char*)replyStates, directory); if (sz != sizeToLoad) { @@ -182,14 +180,14 @@ bool OracleEngine::loadSnapshot(unsigned short epoch, CHA #else -template -bool OracleEngine::saveSnapshot(unsigned short epoch, CHAR16* directory) const +bool OracleEngine::saveSnapshot(unsigned short epoch, CHAR16* directory) const { + return false; } -template -bool OracleEngine::loadSnapshot(unsigned short epoch, CHAR16* directory) +bool OracleEngine::loadSnapshot(unsigned short epoch, CHAR16* directory) { + return false; } #endif diff --git a/src/private_settings.h b/src/private_settings.h index b8737a8d3..0fe7d040b 100644 --- a/src/private_settings.h +++ b/src/private_settings.h @@ -4,7 +4,7 @@ // Do NOT share the data of "Private Settings" section with anybody!!! -#define OPERATOR "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +#define OPERATOR "MEFKYFCDXDUILCAJKOIKWQAPENJDUHSSYPBRWFOTLALILAYWQFDSITJELLHG" static unsigned char computorSeeds[][55 + 1] = { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -61,7 +61,7 @@ static const unsigned char oracleMachineIPs[][4] = { #endif static unsigned long long logReaderPasscodes[4] = { - 0, 0, 0, 0 // REMOVE THIS ENTRY AND REPLACE IT WITH YOUR OWN RANDOM NUMBERS IN [0..18446744073709551615] RANGE IF LOGGING IS ENABLED + 1, 2, 3, 4 // REMOVE THIS ENTRY AND REPLACE IT WITH YOUR OWN RANDOM NUMBERS IN [0..18446744073709551615] RANGE IF LOGGING IS ENABLED }; // Mode for auto save ticks: @@ -74,4 +74,4 @@ static unsigned long long logReaderPasscodes[4] = { // Perform state persisting when your node is misaligned will also make your node misaligned after resuming. // Thus, picking various TICK_STORAGE_AUTOSAVE_TICK_PERIOD numbers across AUX nodes is recommended. // some suggested prime numbers you can try: 971 977 983 991 997 -#define TICK_STORAGE_AUTOSAVE_TICK_PERIOD 1000 \ No newline at end of file +#define TICK_STORAGE_AUTOSAVE_TICK_PERIOD 1337 diff --git a/src/public_settings.h b/src/public_settings.h index 33996bab0..6b4f2752a 100644 --- a/src/public_settings.h +++ b/src/public_settings.h @@ -9,9 +9,9 @@ // no need to define AVX512 here anymore, just change the project settings to use the AVX512 version // random seed is now obtained from spectrumDigests - +#define TESTNET #define MAX_NUMBER_OF_PROCESSORS 32 -#define NUMBER_OF_SOLUTION_PROCESSORS 12 +#define NUMBER_OF_SOLUTION_PROCESSORS 2 // do not increase this, because there may be issues due to too fast ticking // Number of buffers available for executing contract functions in parallel; having more means reserving a bit more RAM (+1 = +32 MB) // and less waiting in request processors if there are more parallel contract function requests. The maximum value that may make sense @@ -26,18 +26,18 @@ #define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 100 // The tick duration used for timing and scheduling logic. -#define TARGET_TICK_DURATION 1000 +#define TARGET_TICK_DURATION 7000 // The tick duration used to calculate the size of memory buffers. // This determines the memory footprint of the application. -#define TICK_DURATION_FOR_ALLOCATION_MS 750 -#define TRANSACTION_SPARSENESS 1 +#define TICK_DURATION_FOR_ALLOCATION_MS 7000 +#define TRANSACTION_SPARSENESS 4 // Number of ticks that are stored in the pending txs pool. This also defines how many ticks in advance a tx can be registered. #define PENDING_TXS_POOL_NUM_TICKS (1000 * 60 * 10ULL / TICK_DURATION_FOR_ALLOCATION_MS) // 10 minutes // Below are 2 variables that are used for auto-F5 feature: -#define AUTO_FORCE_NEXT_TICK_THRESHOLD 0ULL // Multiplier of TARGET_TICK_DURATION for the system to detect "F5 case" | set to 0 to disable +#define AUTO_FORCE_NEXT_TICK_THRESHOLD 20ULL // Multiplier of TARGET_TICK_DURATION for the system to detect "F5 case" | set to 0 to disable // to prevent bad actor causing misalignment. // depends on actual tick time of the network, operators should set this number randomly in this range [12, 26] // eg: If AUTO_FORCE_NEXT_TICK_THRESHOLD is 8 and TARGET_TICK_DURATION is 2, then the system will start "auto F5 procedure" after 16 seconds after receveing 451+ votes @@ -60,22 +60,22 @@ static_assert(AUTO_FORCE_NEXT_TICK_THRESHOLD* TARGET_TICK_DURATION >= PEER_REFRE #define START_NETWORK_FROM_SCRATCH 1 // Addons: If you don't know it, leave it 0. -#define ADDON_TX_STATUS_REQUEST 0 +#define ADDON_TX_STATUS_REQUEST 1 ////////////////////////////////////////////////////////////////////////// // Config options that should NOT be changed by operators #define VERSION_A 1 -#define VERSION_B 279 +#define VERSION_B 280 #define VERSION_C 0 // Epoch and initial tick for node startup -#define EPOCH 201 -#define TICK 44301000 +#define EPOCH 202 +#define TICK 44700000 #define TICK_IS_FIRST_TICK_OF_EPOCH 1 // Set to 0 if the network is restarted during the EPOCH with a new initial TICK -#define ARBITRATOR "AFZPUAIYVPNUYGJRQVLUKOPPVLHAZQTGLYAAUUNBXFTVTAMSBKQBLEIEPCVJ" -#define DISPATCHER "XPXYKFLGSWRHRGAUKWFWVXCDVEYAPCPCNUTMUDWFGDYQCWZNJMWFZEEGCFFO" +#define ARBITRATOR "MEFKYFCDXDUILCAJKOIKWQAPENJDUHSSYPBRWFOTLALILAYWQFDSITJELLHG" +#define DISPATCHER "DISPAPLNOYSWXCJMZEMFUNCCMMJANGQPYJDSEXZTTBFSUEPYPEKCICADBUCJ" static unsigned short SYSTEM_FILE_NAME[] = L"system"; static unsigned short SYSTEM_END_OF_EPOCH_FILE_NAME[] = L"system.eoe"; @@ -94,7 +94,7 @@ static constexpr unsigned long long HYPERIDENTITY_NUMBER_OF_TICKS = 1000; static constexpr unsigned long long HYPERIDENTITY_NUMBER_OF_NEIGHBORS = 728; // 2M. Must be divided by 2 static constexpr unsigned long long HYPERIDENTITY_NUMBER_OF_MUTATIONS = 150; static constexpr unsigned long long HYPERIDENTITY_POPULATION_THRESHOLD = HYPERIDENTITY_NUMBER_OF_INPUT_NEURONS + HYPERIDENTITY_NUMBER_OF_OUTPUT_NEURONS + HYPERIDENTITY_NUMBER_OF_MUTATIONS; // P -static constexpr unsigned int HYPERIDENTITY_SOLUTION_THRESHOLD_DEFAULT = 321; +static constexpr unsigned int HYPERIDENTITY_SOLUTION_THRESHOLD_DEFAULT = HYPERIDENTITY_NUMBER_OF_OUTPUT_NEURONS / 2 + HYPERIDENTITY_NUMBER_OF_OUTPUT_NEURONS / 2 * 5 / 100; // special value for testnet static constexpr unsigned long long ADDITION_NUMBER_OF_INPUT_NEURONS = 14; // K static constexpr unsigned long long ADDITION_NUMBER_OF_OUTPUT_NEURONS = 8; // L @@ -102,7 +102,7 @@ static constexpr unsigned long long ADDITION_NUMBER_OF_TICKS = 1000; static constexpr unsigned long long ADDITION_NUMBER_OF_NEIGHBORS = 728; // 2M. Must be divided by 2 static constexpr unsigned long long ADDITION_NUMBER_OF_MUTATIONS = 150; static constexpr unsigned long long ADDITION_POPULATION_THRESHOLD = ADDITION_NUMBER_OF_INPUT_NEURONS + ADDITION_NUMBER_OF_OUTPUT_NEURONS + ADDITION_NUMBER_OF_MUTATIONS; // P -static constexpr unsigned int ADDITION_SOLUTION_THRESHOLD_DEFAULT = 74300; +static constexpr unsigned int ADDITION_SOLUTION_THRESHOLD_DEFAULT = 68812; // special value for testnet // Multipler of score static constexpr unsigned int HYPERIDENTITY_SOLUTION_MULTIPLER = 1; @@ -110,16 +110,17 @@ static constexpr unsigned int ADDITION_SOLUTION_MULTIPLER = 1; static constexpr long long NEURON_VALUE_LIMIT = 1LL; - #define SOLUTION_SECURITY_DEPOSIT 1000000 // Signing difficulty -#define TARGET_TICK_VOTE_SIGNATURE 0x00095CBEU // around 7000 signing operations per ID +#define TARGET_TICK_VOTE_SIGNATURE 0x07FFFFFFU // around 32 signing operations per ID // include commonly needed definitions #include "network_messages/common_def.h" -#define MAX_NUMBER_OF_TICKS_PER_EPOCH (((((60ULL * 60 * 24 * 7 * 1000) / TICK_DURATION_FOR_ALLOCATION_MS) + NUMBER_OF_COMPUTORS - 1) / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS) +#define TESTNET_EPOCH_DURATION 3000 + +#define MAX_NUMBER_OF_TICKS_PER_EPOCH (TESTNET_EPOCH_DURATION + 5) #define FIRST_TICK_TRANSACTION_OFFSET sizeof(unsigned long long) #define MAX_TRANSACTION_SIZE (MAX_INPUT_SIZE + sizeof(Transaction) + SIGNATURE_SIZE) @@ -132,9 +133,10 @@ static_assert(INTERNAL_COMPUTATIONS_INTERVAL >= NUMBER_OF_COMPUTORS, "Internal c // DoW: Day of the week 0: Sunday, 1 = Monday ... static unsigned int gFullExternalComputationTimes[][2] = { - {0x040C0000U, 0x050C0000U}, // Thu 12:00:00 - Fri 12:00:00 - {0x060C0000U, 0x000C0000U}, // Sat 12:00:00 - Sun 12:00:00 - {0x010C0000U, 0x020C0000U}, // Mon 12:00:00 - Tue 12:00:00 + // only do 30 min intervals in the testnet + {0x040C0000U, 0x040C1E00U}, // Thu 12:00:00 - Thu 12:30:00 + {0x060C0000U, 0x060C1E00U}, // Sat 12:00:00 - Sat 12:30:00 + {0x010C0000U, 0x010C1E00U}, // Mon 12:00:00 - Mon 12:30:00 }; #define STACK_SIZE 4194304 diff --git a/src/qubic.cpp b/src/qubic.cpp index 3a15c06c7..aa63cfbfe 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -1,7 +1,5 @@ #define SINGLE_COMPILE_UNIT -// #define NO_PULSE - //#define INCLUDE_CONTRACT_TEST_EXAMPLES // contract_def.h needs to be included first to make sure that contracts have minimal access @@ -94,7 +92,7 @@ #define MAX_MESSAGE_PAYLOAD_SIZE MAX_TRANSACTION_SIZE #define MAX_UNIVERSE_SIZE 1073741824 #define MESSAGE_DISSEMINATION_THRESHOLD 1000000000 -#define PORT 21841 +#define PORT 31841 #define SYSTEM_DATA_SAVING_PERIOD 300000ULL #define TICK_TRANSACTIONS_PUBLICATION_OFFSET 2 // Must be only 2 #define MIN_MINING_SOLUTIONS_PUBLICATION_OFFSET 3 // Must be 3+ @@ -3647,7 +3645,7 @@ static void processTick(unsigned long long processorNumber) // - 0 if no tx was created (no need to send reply commits) // - UINT32_MAX if we all pending reply commits fitted into this one tx // - otherwise, an index value that has to be passed to the next call for building another tx - retCode = oracleEngine.getReplyCommitTransaction(tx, overallCompIdx, ownCompIdx, txTick, retCode); + retCode = oracleEngine.getReplyCommitTransaction(tx, overallCompIdx, txTick, retCode); if (!retCode) break; @@ -3694,15 +3692,17 @@ static void processTick(unsigned long long processorNumber) PROFILE_NAMED_SCOPE("processTick(): broadcast oracle reveal transactions"); auto* tx = (OracleReplyRevealTransactionPrefix*)txBuffer; const auto txTick = system.tick + ORACLE_REPLY_REVEAL_PUBLICATION_OFFSET; + const auto ownCompIdx = ownComputorIndicesMapping[0]; + const auto overallCompIdx = ownComputorIndices[0]; // create reply reveal transaction in tx (without signature), returning: // - 0 if no tx was created (no need to send reply commits) // - otherwise, an index value that has to be passed to the next call for building another tx unsigned int retCode = 0; - while ((retCode = oracleEngine.getReplyRevealTransaction(tx, 0, txTick, retCode)) != 0) + while ((retCode = oracleEngine.getReplyRevealTransaction(tx, overallCompIdx, txTick, retCode)) != 0) { // sign and broadcast tx KangarooTwelve(tx, sizeof(Transaction) + tx->inputSize, digest, sizeof(digest)); - sign(computorSubseeds[0].m256i_u8, computorPublicKeys[0].m256i_u8, digest, tx->signaturePtr()); + sign(computorSubseeds[ownCompIdx].m256i_u8, computorPublicKeys[ownCompIdx].m256i_u8, digest, tx->signaturePtr()); enqueueResponse(NULL, tx->totalSize(), BROADCAST_TRANSACTION, 0, tx); } } @@ -5456,8 +5456,9 @@ static void tickProcessor(void*) if (tickDataSuits) { const int dayIndex = ::dayIndex(etalonTick.year, etalonTick.month, etalonTick.day); - if ((dayIndex == 738570 + system.epoch * 7 && etalonTick.hour >= 12) - || dayIndex > 738570 + system.epoch * 7) + // if ((dayIndex == 738570 + system.epoch * 7 && etalonTick.hour >= 12) + // || dayIndex > 738570 + system.epoch * 7) + if (system.tick - system.initialTick >= TESTNET_EPOCH_DURATION) { // start seamless epoch transition epochTransitionState = 1; @@ -5912,7 +5913,7 @@ static bool initialize() logToConsole(L"initOracleInterfaces() failed! Not all interfaces are properly defined!"); return false; } - if (!oracleEngine.init(computorPublicKeys)) + if (!oracleEngine.init(broadcastedComputors.computors.publicKeys)) return false; #if ADDON_TX_STATUS_REQUEST @@ -6415,20 +6416,23 @@ static void logInfo() } else { - const CHAR16 alphabet[26][2] = { L"A", L"B", L"C", L"D", L"E", L"F", L"G", L"H", L"I", L"J", L"K", L"L", L"M", L"N", L"O", L"P", L"Q", L"R", L"S", L"T", L"U", L"V", L"W", L"X", L"Y", L"Z" }; - for (unsigned int i = 0; i < numberOfOwnComputorIndices; i++) - { - appendText(message, alphabet[ownComputorIndices[i] / 26]); - appendText(message, alphabet[ownComputorIndices[i] % 26]); - if (i < (unsigned int)(numberOfOwnComputorIndices - 1)) - { - appendText(message, L"+"); - } - else - { - appendText(message, L"."); - } - } + appendText(message, L"[Owning "); + appendNumber(message, numberOfOwnComputorIndices, false); + appendText(message, L" indices]"); + // const CHAR16 alphabet[26][2] = { L"A", L"B", L"C", L"D", L"E", L"F", L"G", L"H", L"I", L"J", L"K", L"L", L"M", L"N", L"O", L"P", L"Q", L"R", L"S", L"T", L"U", L"V", L"W", L"X", L"Y", L"Z" }; + // for (unsigned int i = 0; i < numberOfOwnComputorIndices; i++) + // { + // appendText(message, alphabet[ownComputorIndices[i] / 26]); + // appendText(message, alphabet[ownComputorIndices[i] % 26]); + // if (i < (unsigned int)(numberOfOwnComputorIndices - 1)) + // { + // appendText(message, L"+"); + // } + // else + // { + // appendText(message, L"."); + // } + // } } logToConsole(message); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 000000000..6723ec0a1 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.15) +project(qubic_core_tests CXX C) + +# GoogleTest requires at least C++20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG v1.16.0 + ) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + + +get_filename_component(PROJECT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.." ABSOLUTE) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/platform_common) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/platform_os) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../lib/platform_efi) # Currently still needed due to various imports +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../src) +include_directories(${PROJECT_ROOT_DIR}) + + +add_executable( + qubic_core_tests + # assets.cpp + # common_def.cpp + # contract_core.cpp + # contract_qearn.cpp + # contract_qvault.cpp + # contract_qx.cpp + # kangaroo_twelve.cpp + m256.cpp + math_lib.cpp + network_messages.cpp + # platform.cpp + # qpi_collection.cpp + # qpi.cpp + # qpi_hash_map.cpp + # score_cache.cpp + # score.cpp + # spectrum.cpp + # stdlib_impl.cpp + # tick_storage.cpp + # tx_status_request.cpp + # vote_counter.cpp +) + +# Apply test-specific compiler flags from the centralized detection module +apply_test_compiler_flags(qubic_core_tests) + +# Add additional test-specific flags if needed +if(IS_CLANG OR IS_GCC) + target_compile_options(qubic_core_tests PRIVATE -mrdrnd) +endif() + + +target_link_libraries( + qubic_core_tests PRIVATE + GTest::gtest_main + platform_common + platform_os +) + +include(GoogleTest) +gtest_discover_tests(qubic_core_tests) \ No newline at end of file diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..85a46fba7 --- /dev/null +++ b/test/README.md @@ -0,0 +1,113 @@ +# Qubic Core Testing + +## Testnet operation + +New features and releases of the Qubic Core must be tested in the target environment (EFI bare metal) in a testnet with multiple nodes. +In this task, developer are supported by the test team, which is led by kavatak and can be initially contacted via the [public test-net channel on Discord](https://discord.com/channels/768887649540243497/1182262429174992937). + +### Testing checklist +Importance Levels: +- **High**: The network and nodes can't function properly, need to fix asap or we should not release. + +- **Medium**: Funds are cryptographically secured but extra services don't work as expected (moneyFlew, state save/load features). These services can be turned off temporarily. + +- **Low**: Not really harmful, eg: display error. Can be fixed later. + + +5 main components inside qubic core: +``` +[Consensus algo (tick processors)] +[Solution scoring system] +[Request processors] + [Networking] +[Smart contract processor] +[Extra services] +``` + +Consensus algo (tick processors): +- [High] No misalignment +- [High] Files that are generated at the end of each epoch must be matched between nodes (universe, spectrum, contractXXXX, `.futureComputor` part of `system`). Use `md5sum` to check if the files match. To compare the list of future computers, use the analyzeSystem tool. +- [High] No stuck for more than 10 mins (not misalignment) +- [Medium] Successful epoch transition. After the transition, fresh node can join with the EFI file with new tick and epoch (ensure `#define START_NETWORK_FROM_SCRATCH 0` is set). +- [Low] All displayed stats are reset after epoch transition. + + +Extra services: +- [High] The tx add on (moneyflew). Set `#define ADDON_TX_STATUS_REQUEST 1` to enable it and verify transaction processing. +- [Medium] The node is able to save tick data and reload when resetting. To do this, set `#define TICK_STORAGE_AUTOSAVE_MODE 1` during compilation. Wait for the state to be saved, or press F8. Restart the node. +- [Medium] All logging works as expected. Including: qu transfer, share transfer, contract message, burning loggings. Use the [qlogging tool](https://github.com/qubic/qlogging) to check the log. + + +Solution scoring system: +- [High] No misalignment on resourceTestingDigest +- [High] Submit 30 valid solutions from different addresses, node shows +30 solutions, deposited coins are returned. Press F2 to see X/Y/Z/W solutions (X: Number of recorded solutions, Y: Number of published solutions, Z: Number of obsolete solutions, W: Total solutions received by the node). +- [High] Submit 30 invalid solutions from different addresses, node doesn't show anything, M burn logs appear. +- [High] Submit 4 valid solutions with 2 invalid solutions from the same address, node shows +4 solutions, 4 deposits are returned, 2 get burned. +- [High] Mining seed changes between mining & idle phases. Use [qubic-cli](https://github.com/qubic/qubic-cli) and the -getsysteminfo command to verify that the RandomMiningSeed value is updated properly between the mining and idle phases. +- [High] Submit 451+ valid solutions for custom mining. Check results using F3 on the node. +- [Medium] Repeatedly submitting 30 valid solutions. Press F2, expect the "Score cache" increases. +- [Medium] Solution processing time is under 1000ms (only on BM avx512). +- [Medium] Can set mining threshold remotely via qubic-cli -setsolutionthreshold. + +Smart contract processor: +- [High] No misalignment on computerDigest/spectrumDigest/universeDigest +- [High] Good stack memory usage (can only know by pressing F2 for now) + +Request processors and networking: +- [High] All threads are healthy. +- [Medium] Success rate of connection to the node: >90% Good. >70% OK. Acceptable >50%. Potentially bug <50% + + +## Testing with Test Example SCs + +To enable testing of components that cannot be covered by unit tests, the Test Example SCs provide interfaces to run tests in a ticking testnet via qubic-cli's testing commands. +At the moment, only the `TESTEXA` SC is used for this purpose. + +### Setup +- Enable the testing SCs in the core code by adding `#define INCLUDE_CONTRACT_TEST_EXAMPLES` before including contract_def.h in qubic.cpp. +- In qubic-cli testUtils.cpp file, make sure that `#define TESTEXA_CONTRACT_INDEX ` and `#define TESTEXA_ADDRESS ""` still match the index and address of the `TESTEXA` SC (it will change when new SCs are added). +- Run a test network with the adapted core code. + +### Tests to perform via qubic-cli +- [High] Run the command `-testqpifunctionsoutput` with a computor seed. The computor seed is required because other entities have a limit of max. 1 pending tx and the command will send 15 txs for future ticks. The command will wait and print whether the txs were included in the respective ticks. Ideally, they should all be included but sometimes there might be missing ones which did not pass the consensus. Afterwards, the command will check if the output of QPI functions saved in the `TESTEXA` state match the TickData and quorum tick votes. +- [High] The command `-testqpifunctionsoutputpast` will perform the same check as the previous test but only for past ticks (no seed required). +A special case is testing the QPI functions output after loading the state from a snapshot. For this, wait until a snapshot is saved, re-start the node from the snapshot, then run the command quickly. Ideally, the ticks tested by the command should include ticks from before and after loading the snapshot. + + +## Google Tests + +For simplified, automated, and isolated testing of components, we use the "test" project. +It is based on the Google Test framework and runs in your OS, facilitating easy debugging within your dev environment. + +### Score test + +The Score Test will compare the generated results with the ground-truth files located in test/data. + +#### Ground-truth files +The ground-truth files will be read by the score test, typically consist of two files: +1. **samples_xxx.csv** contains the input data. + - Each column is hex presentation of mining seeds, public keys and nonces. + - Each row is a sample +2. **scores_xxx.csv** the score running on the sample file + - Column presents score setting + - Row will corresponds to a row in the sample file. For example, the 10th row in scores_xxx.csv is the result of the data in the 10th row of the sample file. + +#### Reading ground-truth files + +The ground-truth files are in CSV format and can be read using various applications such as OpenOffice, MS Excel, and Google Sheets. + +For reference on how to read these files, please see **core/test/utils.h**. + +#### Generate ground-truth files +Ground-truth files can be generated using the tools available in the **core/tools** directory. You can pass the -h argument to get detailed instructions. + +For example, + +- Generate a sample file *samples_1234.csv* with 32 samples and run the reference score computation, saving the result into *score_1234.csv* +``` +score_test_generator.exe -m generator -s samples_1234.csv -n 32 -o score_1234.csv +``` + +- Use an existing sample file *samples_1234.csv*, run the reference score computation, and save the result into *score_1234.csv* +``` +score_test_generator.exe -m generator -s samples_1234.csv -o score_1234.csv +``` diff --git a/test/assets.cpp b/test/assets.cpp new file mode 100644 index 000000000..fc15c4e50 --- /dev/null +++ b/test/assets.cpp @@ -0,0 +1,757 @@ +#define NO_UEFI + +#define PRINT_TEST_INFO 0 + +#include "gtest/gtest.h" +#include "test_util.h" + +#include "logging_test.h" + +#include "assets/assets.h" +#include "contract_core/contract_exec.h" +#include "contract_core/qpi_spectrum_impl.h" +#include "contract_core/qpi_asset_impl.h" + + + + +class AssetsTest : public AssetStorage, LoggingTest +{ +public: + AssetsTest() + { + initAssets(); + commonBuffers.init(1, universeSizeInBytes); + } + + ~AssetsTest() + { + commonBuffers.deinit(); + deinitAssets(); + } + + static void clearUniverse() + { + memset(assets, 0, ASSETS_CAPACITY * sizeof(assets[0])); + as.indexLists.reset(); + } + + static void checkAssetsConsistency(bool printInfo = false) + { + // check lists + std::map listElementCount; + unsigned int issuanceIdx = indexLists.issuancesFirstIdx; + while (issuanceIdx != NO_ASSET_INDEX) + { + // check issuance + EXPECT_LT(issuanceIdx, ASSETS_CAPACITY); + EXPECT_EQ(assets[issuanceIdx].varStruct.issuance.type, ISSUANCE); + + // check all ownerships of issuance + unsigned int ownershipIdx = indexLists.ownershipsPossessionsFirstIdx[issuanceIdx]; + while (ownershipIdx != NO_ASSET_INDEX) + { + // check ownership + EXPECT_LT(ownershipIdx, ASSETS_CAPACITY); + EXPECT_EQ(assets[ownershipIdx].varStruct.issuance.type, OWNERSHIP); + + // check all possessions of ownership + unsigned int possessionIdx = indexLists.ownershipsPossessionsFirstIdx[ownershipIdx]; + while (possessionIdx != NO_ASSET_INDEX) + { + // check possession + EXPECT_LT(possessionIdx, ASSETS_CAPACITY); + EXPECT_EQ(assets[possessionIdx].varStruct.issuance.type, POSSESSION); + + // count possession of this ownership + ++listElementCount[ownershipIdx]; + + // get next ownership + possessionIdx = indexLists.nextIdx[possessionIdx]; + } + + // count ownerships of this issuance + ++listElementCount[issuanceIdx]; + + // get next ownership + ownershipIdx = indexLists.nextIdx[ownershipIdx]; + } + + // count issuances (use noAssetIndex to identify list) + ++listElementCount[NO_ASSET_INDEX]; + + // get next issuance + issuanceIdx = indexLists.nextIdx[issuanceIdx]; + } + + // count based on assets array + std::map arrayElementCount; + for (int index = 0; index < ASSETS_CAPACITY; index++) + { + switch (assets[index].varStruct.issuance.type) + { + case ISSUANCE: + if (printInfo) + { + char assetName[8]; + memcpy(assetName, assets[index].varStruct.issuance.name, 7); + assetName[7] = 0; + std::cout << "asset " << assetName << " by " << assets[index].varStruct.issuance.publicKey << ": index " << index << std::endl; + } + ++arrayElementCount[NO_ASSET_INDEX]; + break; + case OWNERSHIP: + ++arrayElementCount[assets[index].varStruct.ownership.issuanceIndex]; + break; + case POSSESSION: + ++arrayElementCount[assets[index].varStruct.possession.ownershipIndex]; + break; + } + } + + // check that counts match + EXPECT_EQ(listElementCount.size(), arrayElementCount.size()); + for (auto it1 = listElementCount.begin(), it2 = arrayElementCount.begin(); + it1 != listElementCount.end() && it2 != arrayElementCount.end(); + ++it1, ++it2) + { + EXPECT_EQ(it1->first, it2->first); + EXPECT_EQ(it1->second, it2->second); + } + + // check that number of owned and possessed shares are equal for each issuance + issuanceIdx = indexLists.issuancesFirstIdx; + while (issuanceIdx != NO_ASSET_INDEX) + { + Asset asset(assets[issuanceIdx].varStruct.issuance.publicKey, assetNameFromString(assets[issuanceIdx].varStruct.issuance.name)); + long long numOfSharesOwned = 0, numOfSharesPossessed = 0; + for (AssetOwnershipIterator iter(asset); !iter.reachedEnd(); iter.next()) + { + numOfSharesOwned += iter.numberOfOwnedShares(); + } + for (AssetPossessionIterator iter(asset); !iter.reachedEnd(); iter.next()) + { + numOfSharesPossessed += iter.numberOfPossessedShares(); + } + EXPECT_EQ(numOfSharesPossessed, numOfSharesOwned); + + issuanceIdx = indexLists.nextIdx[issuanceIdx]; + } + } +}; + + +TEST(TestCoreAssets, CheckLoadFile) +{ + AssetsTest test; + if (loadUniverse(L"universe.136")) + { + test.checkAssetsConsistency(true); + } + else + { + std::cout << "Universe file not found. Skipping file test..." << std::endl; + } +} + +struct AssetSharesKey +{ + m256i publicKey; + unsigned int managingContract; + + bool operator < (const AssetSharesKey& rhs) const + { + if (publicKey < rhs.publicKey) + return true; + else if (rhs.publicKey < publicKey) + return false; + else + return managingContract < rhs.managingContract; + } +}; + +struct AssetKey : public Asset +{ + bool operator < (const Asset& rhs) const + { + if (issuer < rhs.issuer) + return true; + else if (rhs.issuer < issuer) + return false; + else + return assetName < rhs.assetName; + } +}; + +struct IssuanceTestData +{ + Asset id; + unsigned short managingContract; + unsigned int universeIdx; + long long numOfShares; + int numOfOwners; + int transferDivisor; + + std::map shares; + std::map ownershipIdx; + std::map possessionIdx; + + void checkIssuance(const AssetIssuanceIterator& iter) const + { + unsigned int idxI = iter.issuanceIndex(); + EXPECT_LT(idxI, ASSETS_CAPACITY); + EXPECT_EQ(idxI, universeIdx); + EXPECT_EQ(assets[idxI].varStruct.issuance.type, ISSUANCE); + EXPECT_EQ((*((unsigned long long*)assets[idxI].varStruct.issuance.name)) & 0xFFFFFFFFFFFFFF, id.assetName); + EXPECT_EQ(assets[idxI].varStruct.issuance.publicKey, id.issuer); + + EXPECT_EQ(iter.issuer(), id.issuer); + EXPECT_EQ(iter.assetName(), id.assetName); + } + + void checkOwnershipAndIssuance(const AssetOwnershipIterator& iter) const + { + unsigned int idxI = iter.issuanceIndex(); + EXPECT_LT(idxI, ASSETS_CAPACITY); + EXPECT_EQ(idxI, universeIdx); + EXPECT_EQ(assets[idxI].varStruct.issuance.type, ISSUANCE); + EXPECT_EQ((*((unsigned long long*)assets[idxI].varStruct.issuance.name)) & 0xFFFFFFFFFFFFFF, id.assetName); + EXPECT_EQ(assets[idxI].varStruct.issuance.publicKey, id.issuer); + + unsigned int idxO = iter.ownershipIndex(); + EXPECT_LT(idxO, ASSETS_CAPACITY); + EXPECT_EQ(assets[idxO].varStruct.ownership.type, OWNERSHIP); + EXPECT_EQ(assets[idxO].varStruct.ownership.issuanceIndex, universeIdx); + unsigned int managingContract = assets[idxO].varStruct.ownership.managingContractIndex; + + const QPI::id& owner = assets[idxO].varStruct.ownership.publicKey; + AssetSharesKey ownerKey = { owner, managingContract }; + const auto sharesIt = shares.find(ownerKey); + ASSERT_NE(sharesIt, shares.end()); + long long numOfShares = sharesIt->second; + EXPECT_EQ(assets[idxO].varStruct.ownership.numberOfShares, numOfShares); + const auto ownershipIdxIt = ownershipIdx.find(ownerKey); + ASSERT_NE(ownershipIdxIt, ownershipIdx.end()); + EXPECT_EQ(idxO, ownershipIdxIt->second); + + EXPECT_EQ(iter.numberOfOwnedShares(), numOfShares); + EXPECT_EQ(iter.issuer(), id.issuer); + EXPECT_EQ(iter.assetName(), id.assetName); + EXPECT_EQ(iter.owner(), owner); + EXPECT_EQ((uint32)iter.ownershipManagingContract(), managingContract); + } + + void checkPossessionAndOwnershipAndIssuance(const AssetPossessionIterator& iter) const + { + checkOwnershipAndIssuance(iter); + + unsigned int idxP = iter.possessionIndex(); + EXPECT_LT(idxP, ASSETS_CAPACITY); + EXPECT_EQ(assets[idxP].varStruct.possession.type, POSSESSION); + EXPECT_EQ(assets[idxP].varStruct.possession.ownershipIndex, iter.ownershipIndex()); + unsigned int managingContract = (int)assets[idxP].varStruct.possession.managingContractIndex; + + const QPI::id& possessor = assets[idxP].varStruct.possession.publicKey; + AssetSharesKey possessorKey = { possessor, managingContract }; + const auto sharesIt = shares.find(possessorKey); + ASSERT_NE(sharesIt, shares.end()); + long long numOfShares = sharesIt->second; + EXPECT_EQ(assets[idxP].varStruct.possession.numberOfShares, numOfShares); + const auto possessionIdxIt = possessionIdx.find(possessorKey); + ASSERT_NE(possessionIdxIt, possessionIdx.end()); + EXPECT_EQ(idxP, possessionIdxIt->second); + + EXPECT_EQ(iter.numberOfPossessedShares(), numOfShares); + EXPECT_EQ(iter.possessor(), possessor); + EXPECT_EQ((uint32)iter.possessionManagingContract(), managingContract); + } +}; + +TEST(TestCoreAssets, AssetIterators) +{ + AssetsTest test; + test.clearUniverse(); + + IssuanceTestData issuances[] = { + { { m256i(1, 2, 3, 4), assetNameFromString("BLUB") }, 1, NO_ASSET_INDEX, 10000, 10, 30 }, + { { m256i::zero(), assetNameFromString("QX") }, 2, NO_ASSET_INDEX, 676, 20, 30 }, + { { m256i(1, 2, 3, 4), assetNameFromString("BLA") }, 3, NO_ASSET_INDEX, 123456789, 200, 30 }, + { { m256i(2, 2, 3, 4), assetNameFromString("BLA") }, 4, NO_ASSET_INDEX, 987654321, 300, 30 }, + { { m256i(1234, 2, 3, 4), assetNameFromString("BLA") }, 5, NO_ASSET_INDEX, 9876543210123ll, 676, 30 }, + { { m256i(1234, 2, 3, 4), assetNameFromString("FOO") }, 6, NO_ASSET_INDEX, 1000000ll, 2, 1 }, + }; + constexpr int issuancesCount = sizeof(issuances) / sizeof(issuances[0]); + m256i unusedPublicKey(9876, 4321, 0, 13579); + + // With empty universe, all iterators should stop right after init + AssetIssuanceIterator iterI; + EXPECT_TRUE(iterI.reachedEnd()); + EXPECT_EQ(iterI.issuanceIndex(), NO_ASSET_INDEX); + for (int i = 0; i < issuancesCount; ++i) + { + AssetOwnershipIterator iterO(issuances[i].id); + EXPECT_TRUE(iterO.reachedEnd()); + EXPECT_EQ(iterO.issuanceIndex(), NO_ASSET_INDEX); + EXPECT_EQ(iterO.ownershipIndex(), NO_ASSET_INDEX); + AssetPossessionIterator iterP(issuances[i].id); + EXPECT_TRUE(iterP.reachedEnd()); + EXPECT_EQ(iterP.issuanceIndex(), NO_ASSET_INDEX); + EXPECT_EQ(iterP.ownershipIndex(), NO_ASSET_INDEX); + EXPECT_EQ(iterP.possessionIndex(), NO_ASSET_INDEX); + } + + // Build universe with multiple owners / possessor per issuance + for (int i = 0; i < issuancesCount; ++i) + { + int firstOwnershipIdx = -1, firstPossessionIdx = -1, issuanceIdx = -1; + EXPECT_EQ(issueAsset(issuances[i].id.issuer, assetNameFromInt64(issuances[i].id.assetName).c_str(), 0, CONTRACT_ASSET_UNIT_OF_MEASUREMENT, + issuances[i].numOfShares, issuances[i].managingContract, &issuanceIdx, &firstOwnershipIdx, &firstPossessionIdx), issuances[i].numOfShares); + issuances[i].universeIdx = issuanceIdx; + + test.checkAssetsConsistency(); + + long long remainingShares = issuances[i].numOfShares; + for (int j = 1; j < issuances[i].numOfOwners; ++j) + { + long long sharesToTransfer = remainingShares / issuances[i].transferDivisor; + id destId(j*10, 9, 8, 7); + int destOwnershipIdx = -1, destPossessionIdx = -1; + EXPECT_TRUE(transferShareOwnershipAndPossession(firstOwnershipIdx, firstPossessionIdx, destId, sharesToTransfer, &destOwnershipIdx, &destPossessionIdx, false)); + AssetSharesKey key{ destId, issuances[i].managingContract }; + issuances[i].shares[key] = sharesToTransfer; + issuances[i].ownershipIdx[key] = destOwnershipIdx; + issuances[i].possessionIdx[key] = destPossessionIdx; + remainingShares -= sharesToTransfer; + } + + AssetSharesKey key{ issuances[i].id.issuer, issuances[i].managingContract }; + issuances[i].shares[key] = remainingShares; + issuances[i].ownershipIdx[key] = firstOwnershipIdx; + issuances[i].possessionIdx[key] = firstPossessionIdx; + + test.checkAssetsConsistency(i == issuancesCount - 1); + } + + { + // Test iterating all issuances with AssetIssuanceIterator + std::map testIssuancesSet; + for (int i = 0; i < issuancesCount; ++i) + { + AssetKey key{ issuances[i].id.issuer, issuances[i].id.assetName }; +#if PRINT_TEST_INFO > 0 + std::cout << issuances[i].id.issuer << ", name " << issuances[i].id.assetName << ", idx " << issuances[i].universeIdx << std::endl; +#endif + testIssuancesSet[key] = &issuances[i]; + } + AssetIssuanceIterator iter; + while (!iter.reachedEnd()) + { + AssetKey key{ iter.issuer(), iter.assetName() }; +#if PRINT_TEST_INFO > 0 + std::cout << iter.issuer() << ", name " << iter.assetName() << ", idx " << iter.issuanceIndex() << std::endl; +#endif + auto testIssuancesSetIt = testIssuancesSet.find(key); + EXPECT_NE(testIssuancesSetIt, testIssuancesSet.end()); + testIssuancesSetIt->second->checkIssuance(iter); + testIssuancesSet.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + EXPECT_EQ(testIssuancesSet.size(), 0); + + // Iterate by issuer (also test reusing the iterator) + auto assetSelect = AssetIssuanceSelect::byIssuer(issuances[0].id.issuer); + for (int i = 0; i < issuancesCount; ++i) + { + if (issuances[i].id.issuer != assetSelect.issuer) + continue; + AssetKey key{ issuances[i].id.issuer, issuances[i].id.assetName }; +#if PRINT_TEST_INFO > 0 + std::cout << issuances[i].id.issuer << ", name " << issuances[i].id.assetName << ", idx " << issuances[i].universeIdx << std::endl; +#endif + testIssuancesSet[key] = &issuances[i]; + } + iter.begin(assetSelect); + while (!iter.reachedEnd()) + { + AssetKey key{ iter.issuer(), iter.assetName() }; +#if PRINT_TEST_INFO > 0 + std::cout << iter.issuer() << ", name " << iter.assetName() << ", idx " << iter.issuanceIndex() << std::endl; +#endif + auto testIssuancesSetIt = testIssuancesSet.find(key); + EXPECT_NE(testIssuancesSetIt, testIssuancesSet.end()); + testIssuancesSetIt->second->checkIssuance(iter); + testIssuancesSet.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + EXPECT_EQ(testIssuancesSet.size(), 0); + + // Iterate by name + assetSelect = AssetIssuanceSelect::byName(assetNameFromString("BLA")); + for (int i = 0; i < issuancesCount; ++i) + { + if (issuances[i].id.assetName != assetSelect.assetName) + continue; + AssetKey key{ issuances[i].id.issuer, issuances[i].id.assetName }; +#if PRINT_TEST_INFO > 0 + std::cout << issuances[i].id.issuer << ", name " << issuances[i].id.assetName << ", idx " << issuances[i].universeIdx << std::endl; +#endif + testIssuancesSet[key] = &issuances[i]; + } + iter.begin(assetSelect); + while (!iter.reachedEnd()) + { + AssetKey key{ iter.issuer(), iter.assetName() }; +#if PRINT_TEST_INFO > 0 + std::cout << iter.issuer() << ", name " << iter.assetName() << ", idx " << iter.issuanceIndex() << std::endl; +#endif + auto testIssuancesSetIt = testIssuancesSet.find(key); + EXPECT_NE(testIssuancesSetIt, testIssuancesSet.end()); + testIssuancesSetIt->second->checkIssuance(iter); + testIssuancesSet.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + EXPECT_EQ(testIssuancesSet.size(), 0); + + // Test iterator to return single issuance + for (int i = 0; i < issuancesCount; ++i) + { + iter.begin({ issuances[i].id.issuer, issuances[i].id.assetName }); + issuances[i].checkIssuance(iter); + EXPECT_FALSE(iter.next()); + } + + // Test issuance iterator with unused key + iter.begin({ unusedPublicKey, assetNameFromString("UNUSED") }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(AssetIssuanceSelect::byIssuer(unusedPublicKey)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(AssetIssuanceSelect::byName(assetNameFromString("UNUSED"))); + EXPECT_TRUE(iter.reachedEnd()); + } + + { + // Test iterating all ownerships with AssetOwnershipIterator (also tests reusing iter) + AssetOwnershipIterator iter(issuances[0].id); + for (int i = 0; i < issuancesCount; ++i) + { + std::map shares = issuances[i].shares; + std::map ownershipIdx = issuances[i].ownershipIdx; + + if (i > 0) + { + iter.begin(issuances[i].id); + } + + while (!iter.reachedEnd()) + { + issuances[i].checkOwnershipAndIssuance(iter); + AssetSharesKey key{ iter.owner(), iter.ownershipManagingContract() }; + shares.erase(key); + ownershipIdx.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + + EXPECT_EQ(shares.size(), 0); + EXPECT_EQ(ownershipIdx.size(), 0); + } + + // Test iterating ownerships with specific owner (only single iteration / record because managing contract hasn't been changed) + for (int i = 0; i < issuancesCount; ++i) + { + for (const auto& ownerOwnershipIdxPair : issuances[i].ownershipIdx) + { + iter.begin(issuances[i].id, AssetOwnershipSelect::byOwner(ownerOwnershipIdxPair.first.publicKey)); + EXPECT_FALSE(iter.reachedEnd()); + EXPECT_EQ(iter.ownershipIndex(), ownerOwnershipIdxPair.second); + issuances[i].checkOwnershipAndIssuance(iter); + EXPECT_FALSE(iter.next()); + EXPECT_TRUE(iter.reachedEnd()); + } + } + + // Test iterating all ownerships with specific managing contract (all at the moment) + for (int i = 0; i < issuancesCount; ++i) + { + std::map shares = issuances[i].shares; + std::map ownershipIdx = issuances[i].ownershipIdx; + + iter.begin(issuances[i].id, AssetOwnershipSelect::byManagingContract(issuances[i].managingContract)); + while (!iter.reachedEnd()) + { + issuances[i].checkOwnershipAndIssuance(iter); + AssetSharesKey key{ iter.owner(), iter.ownershipManagingContract() }; + shares.erase(key); + ownershipIdx.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + + EXPECT_EQ(shares.size(), 0); + EXPECT_EQ(ownershipIdx.size(), 0); + } + + // Test ownership iterator with unused key + iter.begin({ unusedPublicKey, assetNameFromString("UNUSED") }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::byOwner(unusedPublicKey)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::byManagingContract(12345)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect{ id(10, 9, 8, 7), 12345 }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect{ unusedPublicKey, 1 }); + EXPECT_TRUE(iter.reachedEnd()); + } + + { + // Test iterating all possessions with AssetPossessionIterator (also tests reusing iter) + AssetPossessionIterator iter(issuances[0].id); + for (int i = 0; i < issuancesCount; ++i) + { + std::map shares = issuances[i].shares; + std::map possessionIdx = issuances[i].possessionIdx; + + if (i > 0) + { + iter.begin(issuances[i].id); + } + + while (!iter.reachedEnd()) + { + issuances[i].checkPossessionAndOwnershipAndIssuance(iter); + AssetSharesKey key{ iter.possessor(), iter.possessionManagingContract() }; + shares.erase(key); + possessionIdx.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + + EXPECT_EQ(shares.size(), 0); + EXPECT_EQ(possessionIdx.size(), 0); + } + + // Test iterating possessions with specific possessor (only single iteration / record because managing contract hasn't been changed) + for (int i = 0; i < issuancesCount; ++i) + { + for (const auto& possessorPossessionIdxPair : issuances[i].possessionIdx) + { + iter.begin(issuances[i].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(possessorPossessionIdxPair.first.publicKey)); + EXPECT_FALSE(iter.reachedEnd()); + EXPECT_EQ(iter.possessionIndex(), possessorPossessionIdxPair.second); + issuances[i].checkPossessionAndOwnershipAndIssuance(iter); + EXPECT_FALSE(iter.next()); + EXPECT_TRUE(iter.reachedEnd()); + } + } + + // Test possession iterator with unused key + iter.begin({ unusedPublicKey, assetNameFromString("UNUSED") }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::byOwner(unusedPublicKey)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::byManagingContract(12345)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect{ id(10, 9, 8, 7), 12345 }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect{ unusedPublicKey, 1 }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(unusedPublicKey)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(12345)); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect{ id(10, 9, 8, 7), 12345 }); + EXPECT_TRUE(iter.reachedEnd()); + iter.begin(issuances[0].id, AssetOwnershipSelect::any(), AssetPossessionSelect{ unusedPublicKey, 1 }); + EXPECT_TRUE(iter.reachedEnd()); + } + + { + // Test numberOfShares() + for (int i = 0; i < issuancesCount; ++i) + { + // iterate all possession records, compare results of numberOfShares() and numberOfPossessedShares() + std::map ownedShares; + for (AssetPossessionIterator iter(issuances[i].id); !iter.reachedEnd(); iter.next()) + { + long long numOfShares = numberOfShares(issuances[i].id, { iter.owner(), issuances[i].managingContract }, { iter.possessor(), issuances[i].managingContract }); + EXPECT_EQ(numOfShares, iter.numberOfPossessedShares()); + + long long numOfPossessedShares = numberOfPossessedShares(issuances[i].id.assetName, issuances[i].id.issuer, iter.owner(), iter.possessor(), issuances[i].managingContract, issuances[i].managingContract); + EXPECT_EQ(numOfShares, numOfPossessedShares); + + AssetSharesKey key{ iter.owner(), issuances[i].managingContract }; + ownedShares[key] += numOfShares; + } + + // iterate all ownership records, compare results of numberOfShares() and numberOfOwnedShares() + long long totalShares = 0; + for (AssetOwnershipIterator iter(issuances[i].id); !iter.reachedEnd(); iter.next()) + { + long long numOfShares = numberOfShares(issuances[i].id, { iter.owner(), issuances[i].managingContract }); + EXPECT_EQ(numOfShares, iter.numberOfOwnedShares()); + + AssetSharesKey key{ iter.owner(), iter.ownershipManagingContract() }; + EXPECT_EQ(numOfShares, ownedShares[key]); + + totalShares += numOfShares; + } + + EXPECT_EQ(totalShares, numberOfShares(issuances[i].id)); + EXPECT_EQ(totalShares, issuances[i].numOfShares); + } + } + + // check consistency after rebuild/cleanup of hash map + assetsEndEpoch(); + test.checkAssetsConsistency(); + + { + // Test burning of shares + for (int i = 0; i < issuancesCount; ++i) + { + for (int j = 3; j >= 1; j--) + { + // iterate all possession records and burn 1/j part of the shares + long long expectedTotalShares = 0; + for (AssetPossessionIterator iter(issuances[i].id); !iter.reachedEnd(); iter.next()) + { + const long long numOfSharesPossessedInitially = iter.numberOfPossessedShares(); + const long long numOfSharesOwnedInitially = iter.numberOfOwnedShares(); + const long long numOfSharesToBurn = numOfSharesPossessedInitially / j; + const long long numOfSharesPossessedAfterwards = numOfSharesPossessedInitially - numOfSharesToBurn; + const long long numOfSharesOwnedAfterwards = numOfSharesOwnedInitially - numOfSharesToBurn; + + const bool success = transferShareOwnershipAndPossession(iter.ownershipIndex(), iter.possessionIndex(), NULL_ID, numOfSharesToBurn, nullptr, nullptr, false); + + if (isZero(issuances[i].id.issuer)) + { + // burning fails for contract shares + EXPECT_FALSE(success); + EXPECT_EQ(numOfSharesPossessedInitially, iter.numberOfPossessedShares()); + EXPECT_EQ(numOfSharesOwnedInitially, iter.numberOfOwnedShares()); + expectedTotalShares += numOfSharesPossessedInitially; + } + else + { + // burning succeeds for non-contract asset shares + EXPECT_TRUE(success); + EXPECT_EQ(numOfSharesPossessedAfterwards, iter.numberOfPossessedShares()); + EXPECT_EQ(numOfSharesOwnedAfterwards, iter.numberOfOwnedShares()); + expectedTotalShares += numOfSharesPossessedAfterwards; + } + } + EXPECT_EQ(expectedTotalShares, numberOfShares(issuances[i].id)); + } + } + } + + // check consistency after rebuild/cleanup of hash map + assetsEndEpoch(); + test.checkAssetsConsistency(); +} + +TEST(TestCoreAssets, AssetTransferShareManagementRights) +{ + AssetsTest test; + test.clearUniverse(); + + IssuanceTestData issuances[] = { + { { m256i(1, 2, 3, 4), assetNameFromString("BLUB") }, 1, NO_ASSET_INDEX, 10000, 10, 30 }, + { { m256i::zero(), assetNameFromString("QX") }, 1, NO_ASSET_INDEX, 676, 20, 30 }, + { { m256i(1, 2, 3, 4), assetNameFromString("BLA") }, 1, NO_ASSET_INDEX, 123456789, 200, 30 }, + { { m256i(2, 2, 3, 4), assetNameFromString("BLA") }, 1, NO_ASSET_INDEX, 987654321, 300, 30 }, + { { m256i(1234, 2, 3, 4), assetNameFromString("BLA") }, 1, NO_ASSET_INDEX, 9876543210123ll, 676, 30 }, + { { m256i(1234, 2, 3, 4), assetNameFromString("FOO") }, 1, NO_ASSET_INDEX, 1000000ll, 2, 1 }, + }; + constexpr int issuancesCount = sizeof(issuances) / sizeof(issuances[0]); + + // Build universe with multiple managing contracts per issuance + for (int i = 0; i < issuancesCount; ++i) + { + int firstOwnershipIdx = -1, firstPossessionIdx = -1, issuanceIdx = -1; + EXPECT_EQ(issueAsset(issuances[i].id.issuer, assetNameFromInt64(issuances[i].id.assetName).c_str(), 0, CONTRACT_ASSET_UNIT_OF_MEASUREMENT, + issuances[i].numOfShares, issuances[i].managingContract, &issuanceIdx, &firstOwnershipIdx, &firstPossessionIdx), issuances[i].numOfShares); + issuances[i].universeIdx = issuanceIdx; + + test.checkAssetsConsistency(); + + long long remainingShares = issuances[i].numOfShares; + for (int j = 1; j < issuances[i].numOfOwners; ++j) + { + long long sharesToTransfer = remainingShares / issuances[i].transferDivisor; + unsigned int destContractIdx = j % 15; + int destOwnershipIdx = -1, destPossessionIdx = -1; + EXPECT_TRUE(transferShareManagementRights(firstOwnershipIdx, firstPossessionIdx, + destContractIdx, destContractIdx, sharesToTransfer, &destOwnershipIdx, &destPossessionIdx, false)); + AssetSharesKey key{ issuances[i].id.issuer, destContractIdx }; + issuances[i].shares[key] += sharesToTransfer; + issuances[i].ownershipIdx[key] = destOwnershipIdx; + issuances[i].possessionIdx[key] = destPossessionIdx; + remainingShares -= sharesToTransfer; + } + + AssetSharesKey key{ issuances[i].id.issuer, issuances[i].managingContract }; + issuances[i].shares[key] += remainingShares; + issuances[i].ownershipIdx[key] = firstOwnershipIdx; + issuances[i].possessionIdx[key] = firstPossessionIdx; + + test.checkAssetsConsistency(i == issuancesCount - 1); + } + + { + // Test iterating all possessions with AssetPossessionIterator (also tests reusing iter) + AssetPossessionIterator iter(issuances[0].id); + for (int i = 0; i < issuancesCount; ++i) + { + std::map shares = issuances[i].shares; + std::map possessionIdx = issuances[i].possessionIdx; + + if (i > 0) + { + iter.begin(issuances[i].id); + } + + while (!iter.reachedEnd()) + { + issuances[i].checkPossessionAndOwnershipAndIssuance(iter); + AssetSharesKey key{ iter.possessor(), iter.possessionManagingContract() }; + shares.erase(key); + possessionIdx.erase(key); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + + EXPECT_EQ(shares.size(), 0); + EXPECT_EQ(possessionIdx.size(), 0); + } + + // Test iterating possessions with specific possessor (different managing contracts) + for (int i = 0; i < issuancesCount; ++i) + { + iter.begin(issuances[i].id, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(issuances[i].id.issuer)); + EXPECT_FALSE(iter.reachedEnd()); + while (!iter.reachedEnd()) + { + issuances[i].checkPossessionAndOwnershipAndIssuance(iter); + AssetSharesKey key{ iter.possessor(), iter.possessionManagingContract() }; + EXPECT_NE(issuances[i].ownershipIdx.find(key), issuances[i].ownershipIdx.end()); + EXPECT_EQ(iter.ownershipIndex(), issuances[i].ownershipIdx[key]); + EXPECT_EQ(iter.possessionIndex(), issuances[i].possessionIdx[key]); + EXPECT_EQ((int)iter.ownershipManagingContract(), (int)iter.possessionManagingContract()); + EXPECT_EQ(iter.numberOfOwnedShares(), iter.numberOfPossessedShares()); + EXPECT_EQ(iter.numberOfPossessedShares(), issuances[i].shares[key]); + bool hasNext = iter.next(); + EXPECT_EQ(hasNext, !iter.reachedEnd()); + } + } + } +} + + + + + diff --git a/test/common_def.cpp b/test/common_def.cpp new file mode 100644 index 000000000..4bc64cff0 --- /dev/null +++ b/test/common_def.cpp @@ -0,0 +1,27 @@ +#define NO_UEFI +#define DEFINE_VARIABLES_SHARED_BETWEEN_COMPILE_UNITS + +#include "contract_testing.h" +#include "logging_test.h" +#include "oracle_testing.h" +#include "platform/concurrency_impl.h" +#include "platform/profiling.h" + +// Implement non-QPI version of notification trigger function that is defined in qubic.cpp, where it hands over the +// notification to the contract processor for running the incomming transfer callback. +// We don't have separate tick and contract processors in testing, so we run the callback directly. +void notifyContractOfIncomingTransfer(const m256i& source, const m256i& dest, long long amount, unsigned char type) +{ + // Only notify if amount > 0 and dest is contract + if (amount <= 0 || !isPublicKeyOfContract(dest)) + return; + + unsigned int contractIndex = (unsigned int)dest.m256i_u64[0]; + ASSERT(system.epoch >= contractDescriptions[contractIndex].constructionEpoch); + ASSERT(system.epoch < contractDescriptions[contractIndex].destructionEpoch); + + // Run callback system procedure POST_INCOMING_TRANSFER + QpiContextSystemProcedureCall qpiContext(contractIndex, POST_INCOMING_TRANSFER); + QPI::PostIncomingTransfer_input input{ source, amount, type }; + qpiContext.call(input); +} diff --git a/test/contract_ccf.cpp b/test/contract_ccf.cpp new file mode 100644 index 000000000..655e613a7 --- /dev/null +++ b/test/contract_ccf.cpp @@ -0,0 +1,1215 @@ +#define NO_UEFI + +#include "contract_testing.h" + +#define PRINT_DETAILS 0 + +class CCFChecker : public CCF +{ +public: + void checkSubscriptions(bool printDetails = PRINT_DETAILS) + { + if (printDetails) + { + std::cout << "Active Subscriptions (total capacity: " << activeSubscriptions.capacity() << "):" << std::endl; + for (uint64 i = 0; i < activeSubscriptions.capacity(); ++i) + { + const SubscriptionData& sub = activeSubscriptions.get(i); + if (!isZero(sub.destination)) + { + std::cout << "- Index " << i << ": destination=" << sub.destination + << ", weeksPerPeriod=" << (int)sub.weeksPerPeriod + << ", numberOfPeriods=" << sub.numberOfPeriods + << ", amountPerPeriod=" << sub.amountPerPeriod + << ", startEpoch=" << sub.startEpoch + << ", currentPeriod=" << sub.currentPeriod << std::endl; + } + } + std::cout << "Subscription Proposals (total capacity: " << subscriptionProposals.capacity() << "):" << std::endl; + for (uint64 i = 0; i < subscriptionProposals.capacity(); ++i) + { + const SubscriptionProposalData& prop = subscriptionProposals.get(i); + if (!isZero(prop.proposerId)) + { + std::cout << "- Index " << i << ": proposerId=" << prop.proposerId + << ", destination=" << prop.destination + << ", weeksPerPeriod=" << (int)prop.weeksPerPeriod + << ", numberOfPeriods=" << prop.numberOfPeriods + << ", amountPerPeriod=" << prop.amountPerPeriod + << ", startEpoch=" << prop.startEpoch << std::endl; + } + } + } + } + + const SubscriptionData* getActiveSubscriptionByDestination(const id& destination) + { + for (uint64 i = 0; i < activeSubscriptions.capacity(); ++i) + { + const SubscriptionData& sub = activeSubscriptions.get(i); + if (sub.destination == destination && !isZero(sub.destination)) + return ⊂ + } + return nullptr; + } + + // Helper to find destination from a proposer's subscription proposal + id getDestinationByProposer(const id& proposerId) + { + // Use constant 128 which matches SubscriptionProposalsT capacity + for (uint64 i = 0; i < 128; ++i) + { + const SubscriptionProposalData& prop = subscriptionProposals.get(i); + if (prop.proposerId == proposerId && !isZero(prop.proposerId)) + return prop.destination; + } + return NULL_ID; + } + + bool hasActiveSubscription(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr; + } + + + sint32 getSubscriptionCurrentPeriod(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr ? sub->currentPeriod : -1; + } + + bool getSubscriptionIsActive(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr; + } + + // Overload for backward compatibility - use proposer ID + bool getSubscriptionIsActive(const id& proposerId, bool) + { + return getSubscriptionIsActiveByProposer(proposerId); + } + + uint8 getSubscriptionWeeksPerPeriod(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr ? sub->weeksPerPeriod : 0; + } + + uint32 getSubscriptionNumberOfPeriods(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr ? sub->numberOfPeriods : 0; + } + + sint64 getSubscriptionAmountPerPeriod(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr ? sub->amountPerPeriod : 0; + } + + uint32 getSubscriptionStartEpoch(const id& destination) + { + const SubscriptionData* sub = getActiveSubscriptionByDestination(destination); + return sub != nullptr ? sub->startEpoch : 0; + } + + uint32 countActiveSubscriptions() + { + uint32 count = 0; + for (uint64 i = 0; i < activeSubscriptions.capacity(); ++i) + { + if (!isZero(activeSubscriptions.get(i).destination)) + count++; + } + return count; + } + + // Helper function to check if proposer has a subscription proposal + bool hasSubscriptionProposal(const id& proposerId) + { + // Use constant 128 which matches SubscriptionProposalsT capacity + for (uint64 i = 0; i < 128; ++i) + { + const SubscriptionProposalData& prop = subscriptionProposals.get(i); + if (prop.proposerId == proposerId && !isZero(prop.proposerId)) + return true; + } + return false; + } + + // Helper function for backward compatibility - finds destination from proposer's proposal and checks active subscription + bool hasActiveSubscriptionByProposer(const id& proposerId) + { + id destination = getDestinationByProposer(proposerId); + if (isZero(destination)) + return false; + return hasActiveSubscription(destination); + } + + // Helper function that checks both subscription proposals and active subscriptions by proposer + bool hasSubscription(const id& proposerId) + { + return hasSubscriptionProposal(proposerId) || hasActiveSubscriptionByProposer(proposerId); + } + + // Helper functions that work with proposer ID (for backward compatibility with tests) + bool getSubscriptionIsActiveByProposer(const id& proposerId) + { + return hasActiveSubscriptionByProposer(proposerId); + } + + // Helper to get subscription proposal data by proposer ID + const SubscriptionProposalData* getSubscriptionProposalByProposer(const id& proposerId) + { + // Use constant 128 which matches SubscriptionProposalsT capacity + for (uint64 i = 0; i < 128; ++i) + { + const SubscriptionProposalData& prop = subscriptionProposals.get(i); + if (prop.proposerId == proposerId && !isZero(prop.proposerId)) + return ∝ + } + return nullptr; + } + + uint8 getSubscriptionWeeksPerPeriodByProposer(const id& proposerId) + { + // First check subscription proposal + const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); + if (prop != nullptr) + return prop->weeksPerPeriod; + + // Then check active subscription + id destination = getDestinationByProposer(proposerId); + if (!isZero(destination)) + return getSubscriptionWeeksPerPeriod(destination); + + return 0; + } + + uint32 getSubscriptionNumberOfPeriodsByProposer(const id& proposerId) + { + // First check subscription proposal + const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); + if (prop != nullptr) + return prop->numberOfPeriods; + + // Then check active subscription + id destination = getDestinationByProposer(proposerId); + if (!isZero(destination)) + return getSubscriptionNumberOfPeriods(destination); + + return 0; + } + + sint64 getSubscriptionAmountPerPeriodByProposer(const id& proposerId) + { + // First check subscription proposal + const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); + if (prop != nullptr) + return prop->amountPerPeriod; + + // Then check active subscription + id destination = getDestinationByProposer(proposerId); + if (!isZero(destination)) + return getSubscriptionAmountPerPeriod(destination); + + return 0; + } + + uint32 getSubscriptionStartEpochByProposer(const id& proposerId) + { + // First check subscription proposal + const SubscriptionProposalData* prop = getSubscriptionProposalByProposer(proposerId); + if (prop != nullptr) + return prop->startEpoch; + + // Then check active subscription + id destination = getDestinationByProposer(proposerId); + if (!isZero(destination)) + return getSubscriptionStartEpoch(destination); + + return 0; + } + + sint32 getSubscriptionCurrentPeriodByProposer(const id& proposerId) + { + // Only check active subscription (currentPeriod doesn't exist in proposals) + id destination = getDestinationByProposer(proposerId); + if (isZero(destination)) + return -1; // No active subscription yet + return getSubscriptionCurrentPeriod(destination); + } +}; + +class ContractTestingCCF : protected ContractTesting +{ +public: + ContractTestingCCF() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(CCF); + callSystemProcedure(CCF_CONTRACT_INDEX, INITIALIZE); + + // Setup computors + for (unsigned long long i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + broadcastedComputors.computors.publicKeys[i] = id(i, 1, 2, 3); + increaseEnergy(id(i, 1, 2, 3), 1000000); + } + } + + ~ContractTestingCCF() + { + checkContractExecCleanup(); + } + + CCFChecker* getState() + { + return (CCFChecker*)contractStates[CCF_CONTRACT_INDEX]; + } + + CCF::SetProposal_output setProposal(const id& originator, const CCF::SetProposal_input& input) + { + CCF::SetProposal_output output; + invokeUserProcedure(CCF_CONTRACT_INDEX, 1, input, output, originator, 1000000); + return output; + } + + CCF::GetProposal_output getProposal(uint32 proposalIndex, const id& subscriptionDestination = NULL_ID) + { + CCF::GetProposal_input input; + input.proposalIndex = (uint16)proposalIndex; + input.subscriptionDestination = subscriptionDestination; + CCF::GetProposal_output output; + callFunction(CCF_CONTRACT_INDEX, 2, input, output); + return output; + } + + CCF::GetVotingResults_output getVotingResults(uint32 proposalIndex) + { + CCF::GetVotingResults_input input; + CCF::GetVotingResults_output output; + + input.proposalIndex = (uint16)proposalIndex; + callFunction(CCF_CONTRACT_INDEX, 4, input, output); + return output; + } + + bool vote(const id& originator, const CCF::Vote_input& input) + { + CCF::Vote_output output; + invokeUserProcedure(CCF_CONTRACT_INDEX, 2, input, output, originator, 0); + return output.okay; + } + + CCF::GetLatestTransfers_output getLatestTransfers() + { + CCF::GetLatestTransfers_output output; + callFunction(CCF_CONTRACT_INDEX, 5, CCF::GetLatestTransfers_input(), output); + return output; + } + + CCF::GetRegularPayments_output getRegularPayments() + { + CCF::GetRegularPayments_output output; + callFunction(CCF_CONTRACT_INDEX, 7, CCF::GetRegularPayments_input(), output); + return output; + } + + CCF::GetProposalFee_output getProposalFee() + { + CCF::GetProposalFee_output output; + callFunction(CCF_CONTRACT_INDEX, 6, CCF::GetProposalFee_input(), output); + return output; + } + + CCF::GetProposalIndices_output getProposalIndices(bool activeProposals, sint32 prevProposalIndex = -1) + { + CCF::GetProposalIndices_input input; + input.activeProposals = activeProposals; + input.prevProposalIndex = prevProposalIndex; + CCF::GetProposalIndices_output output; + callFunction(CCF_CONTRACT_INDEX, 1, input, output); + return output; + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(CCF_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(CCF_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + uint32 setupRegularProposal(const id& proposer, const id& destination, sint64 amount, bool expectSuccess = true) + { + CCF::SetProposal_input input; + setMemory(input, 0); + input.proposal.epoch = system.epoch; + input.proposal.type = ProposalTypes::TransferYesNo; + input.proposal.data.transfer.destination = destination; + input.proposal.data.transfer.amount = amount; + input.isSubscription = false; + + auto output = setProposal(proposer, input); + if (expectSuccess) + EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + else + EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + return output.proposalIndex; + } + + uint32 setupSubscriptionProposal(const id& proposer, const id& destination, sint64 amountPerPeriod, + uint32 numberOfPeriods, uint8 weeksPerPeriod, uint32 startEpoch, bool expectSuccess = true) + { + CCF::SetProposal_input input; + setMemory(input, 0); + input.proposal.epoch = system.epoch; + input.proposal.type = ProposalTypes::TransferYesNo; + input.proposal.data.transfer.destination = destination; + input.proposal.data.transfer.amount = amountPerPeriod; + input.isSubscription = true; + input.weeksPerPeriod = weeksPerPeriod; + input.numberOfPeriods = numberOfPeriods; + input.startEpoch = startEpoch; + input.amountPerPeriod = amountPerPeriod; + + auto output = setProposal(proposer, input); + if (expectSuccess) + EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + else + EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + return output.proposalIndex; + } + + void voteMultipleComputors(uint32 proposalIndex, uint32 votesNo, uint32 votesYes) + { + EXPECT_LE((int)(votesNo + votesYes), (int)NUMBER_OF_COMPUTORS); + const auto proposal = getProposal(proposalIndex); + EXPECT_TRUE(proposal.okay); + + CCF::Vote_input voteInput; + voteInput.proposalIndex = (uint16)proposalIndex; + voteInput.proposalType = proposal.proposal.type; + voteInput.proposalTick = proposal.proposal.tick; + + uint32 compIdx = 0; + for (uint32 i = 0; i < votesNo; ++i, ++compIdx) + { + voteInput.voteValue = 0; // 0 = no vote + EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), voteInput)); + } + for (uint32 i = 0; i < votesYes; ++i, ++compIdx) + { + voteInput.voteValue = 1; // 1 = yes vote + EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), voteInput)); + } + + auto results = getVotingResults(proposalIndex); + EXPECT_TRUE(results.okay); + EXPECT_EQ(results.results.optionVoteCount.get(0), uint32(votesNo)); + EXPECT_EQ(results.results.optionVoteCount.get(1), uint32(votesYes)); + } +}; + +static id ENTITY0(7, 0, 0, 0); +static id ENTITY1(100, 0, 0, 0); +static id ENTITY2(123, 456, 789, 0); +static id ENTITY3(42, 69, 0, 13); +static id ENTITY4(3, 14, 2, 7); + +TEST(ContractCCF, BasicInitialization) +{ + ContractTestingCCF test; + + // Check initial state + auto fee = test.getProposalFee(); + EXPECT_EQ(fee.proposalFee, 1000000u); +} + +TEST(ContractCCF, RegularProposalAndVoting) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + + // Set a regular transfer proposal + increaseEnergy(PROPOSER1, 1000000); + uint32 proposalIndex = test.setupRegularProposal(PROPOSER1, ENTITY1, 10000); + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Get proposal + auto proposal = test.getProposal(proposalIndex); + EXPECT_TRUE(proposal.okay); + EXPECT_EQ(proposal.proposal.data.transfer.destination, ENTITY1); + + // Vote on proposal + test.voteMultipleComputors(proposalIndex, 200, 350); + + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + + // End epoch to process votes + test.endEpoch(); + + // Check that transfer was executed + auto transfers = test.getLatestTransfers(); + bool found = false; + for (uint64 i = 0; i < transfers.capacity(); ++i) + { + if (transfers.get(i).destination == ENTITY1 && transfers.get(i).amount == 10000) + { + found = true; + EXPECT_TRUE(transfers.get(i).success); + break; + } + } + EXPECT_TRUE(found); +} + +TEST(ContractCCF, SubscriptionProposalCreation) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Create a subscription proposal + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 12, 4, system.epoch + 1); // 4 weeks per period (monthly) + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Check that subscription proposal was stored + auto state = test.getState(); + EXPECT_TRUE(state->hasSubscription(PROPOSER1)); + EXPECT_FALSE(state->getSubscriptionIsActiveByProposer(PROPOSER1)); // Not active until accepted + EXPECT_EQ(state->getSubscriptionWeeksPerPeriodByProposer(PROPOSER1), 4u); + EXPECT_EQ(state->getSubscriptionNumberOfPeriodsByProposer(PROPOSER1), 12u); + EXPECT_EQ(state->getSubscriptionAmountPerPeriodByProposer(PROPOSER1), 1000); + EXPECT_EQ(state->getSubscriptionStartEpochByProposer(PROPOSER1), system.epoch + 1); + EXPECT_EQ(state->getSubscriptionCurrentPeriodByProposer(PROPOSER1), -1); + + // Get proposal with subscription data + auto proposal = test.getProposal(proposalIndex, NULL_ID); + EXPECT_TRUE(proposal.okay); + EXPECT_TRUE(proposal.hasSubscriptionProposal); + EXPECT_FALSE(isZero(proposal.subscriptionProposal.proposerId)); +} + +TEST(ContractCCF, SubscriptionProposalVotingAndActivation) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + // Create a subscription proposal starting next epoch + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, system.epoch + 1); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Vote to approve + test.voteMultipleComputors(proposalIndex, 200, 350); + + // End epoch + test.endEpoch(); + + auto state = test.getState(); + + // Check subscription is now active (identified by destination) + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + EXPECT_TRUE(state->getSubscriptionIsActive(ENTITY1)); +} + +TEST(ContractCCF, SubscriptionPaymentProcessing) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + // Create subscription starting in epoch 189, weekly payments + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 500, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Approve proposal + test.voteMultipleComputors(proposalIndex, 200, 350); + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + sint64 initialBalance = getBalance(ENTITY1); + + // Move to start epoch and activate + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // Move to next epoch - should trigger first payment + system.epoch = 190; + test.beginEpoch(); + test.endEpoch(); + + // Check payment was made + sint64 newBalance = getBalance(ENTITY1); + EXPECT_GE(newBalance, initialBalance + 500 + 500); + + // Check regular payments log + auto payments = test.getRegularPayments(); + bool foundPayment = false; + for (uint64 i = 0; i < payments.capacity(); ++i) + { + const auto& payment = payments.get(i); + if (payment.destination == ENTITY1 && payment.amount == 500 && payment.periodIndex == 0) + { + foundPayment = true; + EXPECT_TRUE(payment.success); + break; + } + } + EXPECT_TRUE(foundPayment); + + // Check subscription currentPeriod was updated + auto state = test.getState(); + EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), 1); +} + +TEST(ContractCCF, MultipleSubscriptionPayments) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + // Create monthly subscription (4 epochs per period) + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 3, 4, 189); // 4 weeks per period (monthly) + + test.voteMultipleComputors(proposalIndex, 200, 350); + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + sint64 initialBalance = getBalance(ENTITY1); + + // Activate subscription + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // Move through epochs - should trigger payments at epochs 189, 193, 197 + for (uint32 epoch = 189; epoch <= 197; ++epoch) + { + system.epoch = epoch; + test.beginEpoch(); + test.endEpoch(); + } + + // Should have made 3 payments (periods 0, 1, 2) + sint64 newBalance = getBalance(ENTITY1); + EXPECT_GE(newBalance, initialBalance + 1000 + 1000 + 1000); + + // Check subscription completed all periods + auto state = test.getState(); + EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), -1); +} + +TEST(ContractCCF, PreventMultipleActiveSubscriptions) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + // Create first subscription + uint32 proposalIndex1 = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); + + increaseEnergy(PROPOSER1, 1000000); + // Try to create second subscription for same proposer - should overwrite the previous one + uint32 proposalIndex2 = test.setupSubscriptionProposal( + PROPOSER1, ENTITY2, 2000, 4, 1, 189, true); // 1 week per period (weekly) + EXPECT_EQ((int)proposalIndex2, (int)proposalIndex1); +} + +TEST(ContractCCF, CancelSubscription) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + // Create subscription + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + // Cancel proposal (epoch = 0) + CCF::SetProposal_input cancelInput; + setMemory(cancelInput, 0); + cancelInput.proposal.epoch = 0; + cancelInput.proposal.type = ProposalTypes::TransferYesNo; + cancelInput.isSubscription = true; + cancelInput.weeksPerPeriod = 1; // 1 week per period (weekly) + cancelInput.numberOfPeriods = 4; + cancelInput.startEpoch = 189; + cancelInput.amountPerPeriod = 1000; + auto cancelOutput = test.setProposal(PROPOSER1, cancelInput); + EXPECT_NE((int)cancelOutput.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Check subscription was deactivated + auto state = test.getState(); + EXPECT_FALSE(state->hasSubscriptionProposal(PROPOSER1)); // proposal is canceled, so no subscription proposal +} + +TEST(ContractCCF, SubscriptionValidation) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Test invalid weeksPerPeriod (must be > 0) + CCF::SetProposal_input input; + setMemory(input, 0); + input.proposal.epoch = system.epoch; + input.proposal.type = ProposalTypes::TransferYesNo; + input.proposal.data.transfer.destination = ENTITY1; + input.proposal.data.transfer.amount = 1000; + input.isSubscription = true; + input.weeksPerPeriod = 0; + input.numberOfPeriods = 4; + input.startEpoch = system.epoch; + input.amountPerPeriod = 1000; + + // Test start epoch in past + increaseEnergy(PROPOSER1, 1000000); + input.weeksPerPeriod = 1; // 1 week per period (weekly) + input.startEpoch = system.epoch - 1; // Should be >= current epoch + auto output = test.setProposal(PROPOSER1, input); + EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Test that zero numberOfPeriods is allowed (will cancel subscription when accepted) + increaseEnergy(PROPOSER1, 1000000); + input.weeksPerPeriod = 1; + input.startEpoch = system.epoch; + input.numberOfPeriods = 0; // Allowed - will cancel subscription + input.amountPerPeriod = 1000; + output = test.setProposal(PROPOSER1, input); + EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Test that zero amountPerPeriod is allowed (will cancel subscription when accepted) + increaseEnergy(PROPOSER1, 1000000); + input.numberOfPeriods = 4; + input.amountPerPeriod = 0; // Allowed - will cancel subscription + output = test.setProposal(PROPOSER1, input); + EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); +} + +TEST(ContractCCF, MultipleProposers) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + id PROPOSER2 = broadcastedComputors.computors.publicKeys[1]; + increaseEnergy(PROPOSER2, 1000000); + + // Create subscriptions for different proposers + uint32 proposalIndex1 = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); + uint32 proposalIndex2 = test.setupSubscriptionProposal( + PROPOSER2, ENTITY2, 2000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex2, (int)INVALID_PROPOSAL_INDEX); + + // Both proposals need to first be voted in before the subscriptions become active. + auto state = test.getState(); + EXPECT_FALSE(state->hasActiveSubscription(ENTITY1)); + EXPECT_FALSE(state->hasActiveSubscription(ENTITY2)); + EXPECT_EQ(state->countActiveSubscriptions(), 0u); + + // Vote in both subscription proposals to activate them + test.voteMultipleComputors(proposalIndex1, 200, 400); + test.voteMultipleComputors(proposalIndex2, 200, 400); + + // Increase energy so contract can execute the subscriptions + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 10000000); + test.endEpoch(); + + // Now both should be active subscriptions (by destination) + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + EXPECT_TRUE(state->hasActiveSubscription(ENTITY2)); + EXPECT_EQ(state->countActiveSubscriptions(), 2u); +} + +TEST(ContractCCF, ProposalRejectedNoQuorum) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + uint32 proposalIndex = test.setupRegularProposal(PROPOSER1, ENTITY1, 10000); + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Vote but not enough for quorum + test.voteMultipleComputors(proposalIndex, 100, 200); + + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Transfer should not have been executed + auto transfers = test.getLatestTransfers(); + bool found = false; + for (uint64 i = 0; i < transfers.capacity(); ++i) + { + if (transfers.get(i).destination == ENTITY1 && transfers.get(i).amount == 10000) + { + found = true; + break; + } + } + EXPECT_FALSE(found); +} + +TEST(ContractCCF, ProposalRejectedMoreNoVotes) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + uint32 proposalIndex = test.setupRegularProposal(PROPOSER1, ENTITY1, 10000); + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // More "no" votes than "yes" votes + test.voteMultipleComputors(proposalIndex, 350, 200); + + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Transfer should not have been executed + auto transfers = test.getLatestTransfers(); + bool found = false; + for (uint64 i = 0; i < transfers.capacity(); ++i) + { + if (transfers.get(i).destination == ENTITY1 && transfers.get(i).amount == 10000) + { + found = true; + break; + } + } + EXPECT_FALSE(found); +} + +TEST(ContractCCF, SubscriptionMaxEpochsValidation) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Try to create subscription that exceeds max epochs (52) + // Monthly subscription with 14 periods = 14 * 4 = 56 epochs > 52 + CCF::SetProposal_input input; + setMemory(input, 0); + input.proposal.epoch = system.epoch; + input.proposal.type = ProposalTypes::TransferYesNo; + input.proposal.data.transfer.destination = ENTITY1; + input.proposal.data.transfer.amount = 1000; + input.isSubscription = true; + input.weeksPerPeriod = 4; // 4 weeks per period (monthly) + input.numberOfPeriods = 14; // 14 * 4 = 56 epochs > 52 max + input.startEpoch = system.epoch + 1; + input.amountPerPeriod = 1000; + + auto output = test.setProposal(PROPOSER1, input); + EXPECT_EQ((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Try with valid number (12 months = 48 epochs < 52) + input.numberOfPeriods = 12; + output = test.setProposal(PROPOSER1, input); + EXPECT_NE((int)output.proposalIndex, (int)INVALID_PROPOSAL_INDEX); +} + +TEST(ContractCCF, SubscriptionExpiration) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Create weekly subscription with 3 periods + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 500, 3, 1, 189); // 1 week per period (weekly) + + test.voteMultipleComputors(proposalIndex, 200, 350); + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + sint64 initialBalance = getBalance(ENTITY1); + + // Activate and process payments + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // Process first payment (epoch 190) + system.epoch = 190; + test.beginEpoch(); + test.endEpoch(); + + // Process second payment (epoch 191) + system.epoch = 191; + test.beginEpoch(); + test.endEpoch(); + + sint64 balanceAfter3Payments = getBalance(ENTITY1); + EXPECT_GE(balanceAfter3Payments, initialBalance + 500 + 500 + 500); + + // Move to epoch 192 - subscription should be expired, no more payments + system.epoch = 192; + test.beginEpoch(); + test.endEpoch(); + + sint64 balanceAfterExpiration = getBalance(ENTITY1); + EXPECT_EQ(balanceAfterExpiration, balanceAfter3Payments); // No new payment +} + +TEST(ContractCCF, GetProposalIndices) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + // Create multiple proposals + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + id PROPOSER2 = broadcastedComputors.computors.publicKeys[1]; + increaseEnergy(PROPOSER2, 1000000); + uint32 proposalIndex1 = test.setupRegularProposal(PROPOSER1, ENTITY1, 1000); + EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); + uint32 proposalIndex2 = test.setupRegularProposal(PROPOSER2, ENTITY2, 2000); + EXPECT_NE((int)proposalIndex2, (int)INVALID_PROPOSAL_INDEX); + + auto output = test.getProposalIndices(true, -1); + + EXPECT_GE((int)output.numOfIndices, 2); + bool found1 = false, found2 = false; + for (uint32 i = 0; i < output.numOfIndices; ++i) + { + if (output.indices.get(i) == proposalIndex1) + found1 = true; + if (output.indices.get(i) == proposalIndex2) + found2 = true; + } + EXPECT_TRUE(found1); + EXPECT_TRUE(found2); +} + +TEST(ContractCCF, SubscriptionSlotReuse) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Create and cancel a subscription + uint32 proposalIndex1 = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); + + // Cancel it + increaseEnergy(PROPOSER1, 1000000); + + CCF::SetProposal_input cancelInput; + setMemory(cancelInput, 0); + cancelInput.proposal.epoch = 0; + cancelInput.proposal.type = ProposalTypes::TransferYesNo; + cancelInput.proposal.data.transfer.destination = ENTITY1; + cancelInput.proposal.data.transfer.amount = 1000; + cancelInput.weeksPerPeriod = 1; // 1 week per period (weekly) + cancelInput.numberOfPeriods = 4; + cancelInput.startEpoch = 189; + cancelInput.amountPerPeriod = 1000; + cancelInput.isSubscription = true; + auto cancelOutput = test.setProposal(PROPOSER1, cancelInput); + EXPECT_NE((int)cancelOutput.proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Create a new subscription - should reuse the slot + increaseEnergy(PROPOSER1, 1000000); + + uint32 proposalIndex2 = test.setupSubscriptionProposal( + PROPOSER1, ENTITY2, 2000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_EQ((int)proposalIndex2, (int)proposalIndex1); + + // Vote in the new subscription proposal to activate it + test.voteMultipleComputors(proposalIndex2, 200, 400); + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Check that subscription was updated (identified by destination) + auto state = test.getState(); + EXPECT_EQ(state->countActiveSubscriptions(), 1u); + EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY2), 2000); // New subscription for ENTITY2 +} + +TEST(ContractCCF, CancelSubscriptionByZeroAmount) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Create and activate a subscription + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Vote to approve + test.voteMultipleComputors(proposalIndex, 200, 350); + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Move to start epoch to activate + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // Verify subscription is active + auto state = test.getState(); + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY1), 1000); + + // Propose cancellation by setting amountPerPeriod to 0 + increaseEnergy(PROPOSER1, 1000000); + CCF::SetProposal_input cancelInput; + setMemory(cancelInput, 0); + cancelInput.proposal.epoch = system.epoch; + cancelInput.proposal.type = ProposalTypes::TransferYesNo; + cancelInput.proposal.data.transfer.destination = ENTITY1; + cancelInput.proposal.data.transfer.amount = 0; + cancelInput.isSubscription = true; + cancelInput.weeksPerPeriod = 1; + cancelInput.numberOfPeriods = 4; + cancelInput.startEpoch = system.epoch + 1; + cancelInput.amountPerPeriod = 0; // Zero amount will cancel subscription + + uint32 cancelProposalIndex = test.setProposal(PROPOSER1, cancelInput).proposalIndex; + EXPECT_NE((int)cancelProposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Vote to approve cancellation + test.voteMultipleComputors(cancelProposalIndex, 200, 350); + // Increase energy for contract + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Verify subscription was deleted + state = test.getState(); + EXPECT_FALSE(state->hasActiveSubscription(ENTITY1)); + EXPECT_EQ(state->countActiveSubscriptions(), 0u); +} + +TEST(ContractCCF, CancelSubscriptionByZeroPeriods) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Create and activate a subscription + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); // 1 week per period (weekly) + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Vote to approve + test.voteMultipleComputors(proposalIndex, 200, 350); + // Increase energy for contract to pay for the proposal + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Move to start epoch to activate + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // Verify subscription is active + auto state = test.getState(); + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + + // Propose cancellation by setting numberOfPeriods to 0 + increaseEnergy(PROPOSER1, 1000000); + CCF::SetProposal_input cancelInput; + setMemory(cancelInput, 0); + cancelInput.proposal.epoch = system.epoch; + cancelInput.proposal.type = ProposalTypes::TransferYesNo; + cancelInput.proposal.data.transfer.destination = ENTITY1; + cancelInput.proposal.data.transfer.amount = 0; + cancelInput.isSubscription = true; + cancelInput.weeksPerPeriod = 1; + cancelInput.numberOfPeriods = 0; // Zero periods will cancel subscription + cancelInput.startEpoch = system.epoch + 1; + cancelInput.amountPerPeriod = 1000; + + uint32 cancelProposalIndex = test.setProposal(PROPOSER1, cancelInput).proposalIndex; + EXPECT_NE((int)cancelProposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Vote to approve cancellation + test.voteMultipleComputors(cancelProposalIndex, 200, 350); + // Increase energy for contract + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Verify subscription was deleted + state = test.getState(); + EXPECT_FALSE(state->hasActiveSubscription(ENTITY1)); + EXPECT_EQ(state->countActiveSubscriptions(), 0u); +} + +TEST(ContractCCF, SubscriptionWithDifferentWeeksPerPeriod) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + + // Create subscription with 2 weeks per period + uint32 proposalIndex = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 6, 2, 189); // 2 weeks per period, 6 periods + EXPECT_NE((int)proposalIndex, (int)INVALID_PROPOSAL_INDEX); + + // Verify proposal data + auto state = test.getState(); + EXPECT_EQ(state->getSubscriptionWeeksPerPeriodByProposer(PROPOSER1), 2u); + EXPECT_EQ(state->getSubscriptionNumberOfPeriodsByProposer(PROPOSER1), 6u); + + // Vote to approve + test.voteMultipleComputors(proposalIndex, 200, 350); + // Increase energy for contract + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + // Still period -1, no payment yet + EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), -1); + + // Move to start epoch + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // period 0, it is the first payment period. + EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), 0); + + // Verify subscription is active with correct weeksPerPeriod + state = test.getState(); + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + EXPECT_EQ(state->getSubscriptionWeeksPerPeriod(ENTITY1), 2u); + EXPECT_EQ(state->getSubscriptionNumberOfPeriods(ENTITY1), 6u); + + system.epoch = 190; + test.beginEpoch(); + test.endEpoch(); + + system.epoch = 191; + test.beginEpoch(); + test.endEpoch(); + + // period 1, it is the second payment period. + EXPECT_EQ(state->getSubscriptionCurrentPeriod(ENTITY1), 1); + + sint64 balance = getBalance(ENTITY1); + EXPECT_GE(balance, 1000); // Payment was made +} + +TEST(ContractCCF, SubscriptionOverwriteByDestination) +{ + ContractTestingCCF test; + system.epoch = 188; + test.beginEpoch(); + + id PROPOSER1 = broadcastedComputors.computors.publicKeys[0]; + increaseEnergy(PROPOSER1, 1000000); + id PROPOSER2 = broadcastedComputors.computors.publicKeys[1]; + increaseEnergy(PROPOSER2, 1000000); + + // PROPOSER1 creates subscription for ENTITY1 + uint32 proposalIndex1 = test.setupSubscriptionProposal( + PROPOSER1, ENTITY1, 1000, 4, 1, 189); + EXPECT_NE((int)proposalIndex1, (int)INVALID_PROPOSAL_INDEX); + + // Vote and activate + test.voteMultipleComputors(proposalIndex1, 200, 350); + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + system.epoch = 189; + test.beginEpoch(); + test.endEpoch(); + + // Verify first subscription is active + auto state = test.getState(); + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY1), 1000); + + // PROPOSER2 creates a new subscription proposal for the same destination + // This should overwrite the existing subscription when accepted + uint32 proposalIndex2 = test.setupSubscriptionProposal( + PROPOSER2, ENTITY1, 2000, 6, 2, system.epoch + 1); // Different amount and schedule + EXPECT_NE((int)proposalIndex2, (int)INVALID_PROPOSAL_INDEX); + + // Vote and activate the new subscription + test.voteMultipleComputors(proposalIndex2, 200, 350); + increaseEnergy(id(CCF_CONTRACT_INDEX, 0, 0, 0), 1000000); + test.endEpoch(); + + system.epoch = 190; + test.beginEpoch(); + test.endEpoch(); + + // Verify the subscription was overwritten + state = test.getState(); + EXPECT_TRUE(state->hasActiveSubscription(ENTITY1)); + EXPECT_EQ(state->getSubscriptionAmountPerPeriod(ENTITY1), 2000); // New amount + EXPECT_EQ(state->getSubscriptionWeeksPerPeriod(ENTITY1), 2u); // New schedule + EXPECT_EQ(state->getSubscriptionNumberOfPeriods(ENTITY1), 6u); // New number of periods + EXPECT_EQ(state->countActiveSubscriptions(), 1u); // Still only one subscription per destination +} diff --git a/test/contract_core.cpp b/test/contract_core.cpp new file mode 100644 index 000000000..5a4e217ac --- /dev/null +++ b/test/contract_core.cpp @@ -0,0 +1,167 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#define TRACK_MAX_STACK_BUFFER_SIZE +#include "../src/contract_core/stack_buffer.h" +#include "../src/contract_core/contract_action_tracker.h" + +TEST(TestCoreContractCore, StackBuffer) +{ + StackBuffer s1; + s1.init(); + EXPECT_EQ(s1.capacity(), 120); + EXPECT_EQ(s1.size(), 0); + EXPECT_EQ(s1.maxSizeObserved(), 0); + EXPECT_EQ(s1.failedAllocAttempts(), 0); + EXPECT_FALSE(s1.free()); + + EXPECT_NE(s1.allocate(70), nullptr); // success + EXPECT_EQ(s1.allocate(50), nullptr); // fail + EXPECT_EQ(s1.allocate(100), nullptr); // fail + EXPECT_EQ(s1.allocate(255), nullptr); // fail + EXPECT_EQ(s1.size(), 71); + EXPECT_TRUE(s1.free()); + EXPECT_EQ(s1.size(), 0); + EXPECT_EQ(s1.maxSizeObserved(), 71); + EXPECT_EQ(s1.failedAllocAttempts(), 3); + + EXPECT_NE(s1.allocate(10), nullptr); // success + EXPECT_NE(s1.allocate(20), nullptr); // success + EXPECT_NE(s1.allocate(30), nullptr); // success + EXPECT_NE(s1.allocate(40), nullptr); // success + EXPECT_EQ(s1.size(), 104); + EXPECT_TRUE(s1.free()); + EXPECT_EQ(s1.size(), 63); + EXPECT_NE(s1.allocate(20), nullptr); // success + EXPECT_EQ(s1.size(), 84); + EXPECT_EQ(s1.allocate(50), nullptr); // fail + EXPECT_EQ(s1.allocate(100), nullptr); // fail + EXPECT_EQ(s1.allocate(255), nullptr); // fail + EXPECT_TRUE(s1.free()); + EXPECT_EQ(s1.size(), 63); + EXPECT_TRUE(s1.free()); + EXPECT_EQ(s1.size(), 32); + EXPECT_TRUE(s1.free()); + EXPECT_EQ(s1.size(), 11); + EXPECT_TRUE(s1.free()); + EXPECT_EQ(s1.size(), 0); + EXPECT_EQ(s1.maxSizeObserved(), 104); + EXPECT_EQ(s1.failedAllocAttempts(), 6); + EXPECT_FALSE(s1.free()); + + char* p; + EXPECT_NE(p = s1.allocate(70), nullptr); // success + *p = 100; + EXPECT_EQ(*p, 100); + EXPECT_NE(p = s1.allocate(40), nullptr); // success + *p = 20; + EXPECT_EQ(*p, 20); + EXPECT_EQ(s1.size(), 112); + s1.freeAll(); + EXPECT_EQ(s1.size(), 0); + EXPECT_EQ(s1.maxSizeObserved(), 112); + EXPECT_EQ(s1.failedAllocAttempts(), 6); + + char* ptr; + unsigned char sz; + bool special; + EXPECT_FALSE(s1.unwind(ptr, sz, special)); + + EXPECT_NE(p = s1.allocate(112, false), nullptr); // success + EXPECT_EQ(s1.size(), 113); + EXPECT_EQ(s1.maxSizeObserved(), 113); + EXPECT_TRUE(s1.unwind(ptr, sz, special)); + EXPECT_EQ(sz, 112); + EXPECT_EQ(ptr, p); + EXPECT_EQ(special, false); + + EXPECT_EQ(s1.size(), 0); + EXPECT_EQ(s1.maxSizeObserved(), 113); + EXPECT_FALSE(s1.unwind(ptr, sz, special)); + + EXPECT_EQ(p = s1.allocate(120, true), nullptr); // fail + EXPECT_EQ(s1.failedAllocAttempts(), 7); + + EXPECT_NE(p = s1.allocate(119, true), nullptr); // success + EXPECT_EQ(s1.size(), 120); + EXPECT_EQ(s1.maxSizeObserved(), 120); + EXPECT_TRUE(s1.unwind(ptr, sz, special)); + EXPECT_EQ(sz, 119); + EXPECT_EQ(ptr, p); + EXPECT_EQ(special, true); + + StackBuffer s2; + s2.init(); + EXPECT_EQ(s2.capacity(), 128000); + EXPECT_EQ(s2.size(), 0); + EXPECT_EQ(s2.maxSizeObserved(), 0); + EXPECT_EQ(s2.failedAllocAttempts(), 0); + EXPECT_FALSE(s2.free()); + + constexpr int depth = 10; + unsigned int sz2; + char* ptrArray[depth]; + for (int i = 0; i < depth; ++i) + { + EXPECT_NE(ptrArray[i] = s2.allocate(i * 1000, i % 3 == 0), nullptr); // success + } + for (int i = depth - 1; i >= 0; --i) + { + EXPECT_TRUE(s2.unwind(ptr, sz2, special)); + EXPECT_EQ(sz2, i * 1000); + EXPECT_EQ(ptr, ptrArray[i]); + EXPECT_EQ(special, i % 3 == 0); + } +} + +TEST(TestCoreContractCore, ContractActionTracker) +{ + m256i id0(0, 1, 2, 3); + m256i id1(2, 3, 4, 5); + m256i id2(3, 4, 5, 6); + + ContractActionTracker<6> at; + EXPECT_TRUE(at.allocBuffer()); + at.init(); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), 0); + + EXPECT_TRUE(at.addQuTransfer(id0, id1, 100)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), -100); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 100); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 0); + + EXPECT_TRUE(at.addQuTransfer(id1, id0, 100)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), 0); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 0); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 0); + + EXPECT_TRUE(at.addQuTransfer(id0, id1, 1000)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), -1000); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 1000); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 0); + + EXPECT_TRUE(at.addQuTransfer(id1, id2, 800)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), -1000); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 800); + + EXPECT_TRUE(at.addQuTransfer(id2, id0, 500)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), -500); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 300); + + // Transfer to own address does not change anything + EXPECT_TRUE(at.addQuTransfer(id0, id0, 10000)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), -500); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 300); + + // Fails because action cannot be stored + EXPECT_FALSE(at.addQuTransfer(id2, id0, 500)); + EXPECT_EQ(at.getOverallQuTransferBalance(id0), -500); + EXPECT_EQ(at.getOverallQuTransferBalance(id1), 200); + EXPECT_EQ(at.getOverallQuTransferBalance(id2), 300); + + at.freeBuffer(); +} diff --git a/test/contract_gqmprop.cpp b/test/contract_gqmprop.cpp new file mode 100644 index 000000000..bf0854b3b --- /dev/null +++ b/test/contract_gqmprop.cpp @@ -0,0 +1,537 @@ +#define NO_UEFI + +#include "contract_testing.h" + +#define PRINT_DETAILS 0 + +class GQmPropChecker : public GQMPROP +{ +public: + void checkRevenueDonations( + std::vector* expectedOrder = nullptr, + std::vector* expectedEntries = nullptr, + bool printTable = PRINT_DETAILS) + { + const GQMPROP::RevenueDonationT& revDon = this->revenueDonation; + const GQMPROP::RevenueDonationEntry* cur = nullptr; + const GQMPROP::RevenueDonationEntry* prev = nullptr; + uint64 idxRD = 0; + + if (printTable) + { + std::cout << "Revenue donations table (epoch " << system.epoch << "):" << std::endl; + for (idxRD = 0; idxRD < revDon.capacity(); ++idxRD) + { + cur = &revDon.get(idxRD); + if (isZero(cur->destinationPublicKey)) + break; + std::cout << "- " << idxRD << ": ID " << cur->destinationPublicKey << ", epoch " << cur->firstEpoch << ", amount " << float(cur->millionthAmount) / 1000000.f << std::endl; + } + } + + // check the used part of the table + std::set prevIds; + uint64 idxO = 0; + for (idxRD = 0; idxRD < revDon.capacity(); ++idxRD) + { + cur = &revDon.get(idxRD); + if (isZero(cur->destinationPublicKey)) + { + // done with checking used part of the table + break; + } + if (idxRD > 0) + { + if (cur->destinationPublicKey == prev->destinationPublicKey) + { + // entries of same ID + // -> check order + EXPECT_LT((int)prev->firstEpoch, (int)cur->firstEpoch); + // -> check that we don't have two non-future entries + if (prev->firstEpoch < system.epoch) + { + EXPECT_GE((int)cur->firstEpoch, (int)system.epoch); + } + } + else + { + // next ID + // -> check that we haven't seen it before + EXPECT_EQ(prevIds.find(cur->destinationPublicKey), prevIds.end()); + ++idxO; + } + } + + EXPECT_GE(cur->millionthAmount, 0); + EXPECT_LE(cur->millionthAmount, 1000000); + + if (expectedOrder) + { + EXPECT_LT(idxO, expectedOrder->size()); + EXPECT_EQ(cur->destinationPublicKey, expectedOrder->at(idxO)); + } + if (expectedEntries) + { + EXPECT_LT(idxRD, expectedEntries->size()); + EXPECT_EQ(cur->destinationPublicKey, expectedEntries->at(idxRD).destinationPublicKey); + EXPECT_EQ((int)cur->firstEpoch, (int)expectedEntries->at(idxRD).firstEpoch); + EXPECT_EQ(cur->millionthAmount, expectedEntries->at(idxRD).millionthAmount); + } + + // update prev data for later checks + prev = cur; + prevIds.insert(cur->destinationPublicKey); + } + + // check that all remaining entries are 0 + for (; idxRD < revDon.capacity(); ++idxRD) + { + EXPECT_TRUE(isZero(revDon.get(idxRD).destinationPublicKey)); + EXPECT_EQ((int)revDon.get(idxRD).firstEpoch, 0); + EXPECT_EQ(revDon.get(idxRD).millionthAmount, 0); + } + } +}; + + +class ContractTestingGQmProp : protected ContractTesting +{ +public: + ContractTestingGQmProp() + { + initEmptySpectrum(); + INIT_CONTRACT(GQMPROP); + callSystemProcedure(GQMPROP_CONTRACT_INDEX, INITIALIZE); + + // Setup computors + for (unsigned long long i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + broadcastedComputors.computors.publicKeys[i] = id(i, 1, 2, 3); + increaseEnergy(id(i, 1, 2, 3), 1000000); + } + } + + GQmPropChecker* getState() + { + return (GQmPropChecker*)contractStates[GQMPROP_CONTRACT_INDEX]; + } + + GQMPROP::GetProposal_output getProposal(uint16 proposalIndex) + { + GQMPROP::GetProposal_input input{ proposalIndex }; + GQMPROP::GetProposal_output output; + callFunction(GQMPROP_CONTRACT_INDEX, 2, input, output); + return output; + } + + GQMPROP::GetVotingResults_output getResults(uint16 proposalIndex) + { + GQMPROP::GetVotingResults_input input{ proposalIndex }; + GQMPROP::GetVotingResults_output output; + callFunction(GQMPROP_CONTRACT_INDEX, 4, input, output); + return output; + } + + uint16 setProposal(const id& originator, const GQMPROP::ProposalDataT& proposalData) + { + GQMPROP::SetProposal_input input = proposalData; + GQMPROP::SetProposal_output output; + invokeUserProcedure(GQMPROP_CONTRACT_INDEX, 1, input, output, originator, 0); + return output.proposalIndex; + } + + bool vote(const id& originator, uint16 proposalIndex, const GQMPROP::ProposalDataT& proposalData, sint64 voteValue) + { + GQMPROP::Vote_input input{proposalIndex, proposalData.type, proposalData.tick, voteValue}; + GQMPROP::Vote_output output; + invokeUserProcedure(GQMPROP_CONTRACT_INDEX, 2, input, output, originator, 0); + return output.okay; + } + + // TODO: add other procedures + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(GQMPROP_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void voteMultipleComputors(uint16 proposalIndex, uint16 votesNo, uint16 votesYes1, uint16 votesYes2 = 0) + { + EXPECT_LE(int(votesNo + votesYes1 + votesYes2), NUMBER_OF_COMPUTORS); + const auto proposal = getProposal(proposalIndex); + EXPECT_TRUE(proposal.okay); + uint16 compIdx = 0; + for (uint16 i = 0; i < votesNo; ++i, ++compIdx) + EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), proposalIndex, proposal.proposal, 0)); + for (uint16 i = 0; i < votesYes1; ++i, ++compIdx) + EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), proposalIndex, proposal.proposal, 1)); + for (uint16 i = 0; i < votesYes2; ++i, ++compIdx) + EXPECT_TRUE(vote(id(compIdx, 1, 2, 3), proposalIndex, proposal.proposal, 2)); + auto results = getResults(proposalIndex); + EXPECT_TRUE(results.okay); + EXPECT_EQ(results.results.optionVoteCount.get(0), uint32(votesNo)); + EXPECT_EQ(results.results.optionVoteCount.get(1), uint32(votesYes1)); + EXPECT_EQ(results.results.optionVoteCount.get(2), uint32(votesYes2)); + } + + uint16 setupProposal(uint16 proposer, uint16 type, const id& dest, sint64 amount = 0, uint16 targetEpoch = 0, bool expectSuccess = true) + { + GQMPROP::ProposalDataT proposal; + setMemory(proposal, 0); + proposal.epoch = system.epoch; + proposal.type = type; + switch (ProposalTypes::cls(type)) + { + case ProposalTypes::Class::Transfer: + { + const auto amountCount = ProposalTypes::optionCount(type) - 1; + for (int i = 0; i < amountCount; ++i) + proposal.data.transfer.amounts.set(i, amount + i); + proposal.data.transfer.destination = dest; + break; + } + case ProposalTypes::Class::TransferInEpoch: + proposal.data.transferInEpoch.amount = amount; + proposal.data.transferInEpoch.destination = dest; + proposal.data.transferInEpoch.targetEpoch = targetEpoch; + break; + } + uint16 proposalIdx = setProposal(id(proposer, 1, 2, 3), proposal); + if (expectSuccess) + EXPECT_NE((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); + else + EXPECT_EQ((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); + return proposalIdx; + } +}; + +static id ENTITY0(7, 0, 0, 0); +static id ENTITY1(100, 0, 0, 0); +static id ENTITY2(123, 456, 789, 0); +static id ENTITY3(42, 69, 0, 13); +static id ENTITY4(3, 14, 2, 7); + +TEST(ContractGQmProp, RevDonation) +{ + ContractTestingGQmProp test; + uint16 proposalIndex; + std::vector revDonationOrder; + std::vector revDonationEntries; + + // rev donation from INITALIZE + revDonationOrder.push_back(ENTITY0); + revDonationEntries = { {ENTITY0, 150000, 123} }; + + //------------------------------------------------------------- + // epoch 200 + system.epoch = 200; + test.beginEpoch(); + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // one successful and several unsuccessful proposals (testing insert at end) + proposalIndex = test.setupProposal(0, ProposalTypes::TransferYesNo, ENTITY1, 1000); + test.voteMultipleComputors(proposalIndex, 200, 350); + revDonationOrder.push_back(ENTITY1); + + proposalIndex = test.setupProposal(1, ProposalTypes::TransferYesNo, ENTITY2, 10000); + test.voteMultipleComputors(proposalIndex, 100, 200); // total votes < QUORUM + + proposalIndex = test.setupProposal(2, ProposalTypes::TransferYesNo, ENTITY2, 20000); + test.voteMultipleComputors(proposalIndex, 379, 256); // most noted is "no" + + proposalIndex = test.setupProposal(3, ProposalTypes::TransferThreeAmounts, ENTITY2, 30000); + test.voteMultipleComputors(proposalIndex, 10, 20, 400); // total votes < QUORUM + + proposalIndex = test.setupProposal(4, ProposalTypes::TransferThreeAmounts, ENTITY2, 40000); + test.voteMultipleComputors(proposalIndex, 201, 202, 203); // max voted < QUORUM/2 + + //------------------------------------------------------------- + // epoch 201 + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { {ENTITY0, 150000, 123}, {ENTITY1, 1000, 201} }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // add future revenue donations (testing insert at end and in the middle) + proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY2, 2000, 205); + test.voteMultipleComputors(proposalIndex, 0, 600); + revDonationOrder.push_back(ENTITY2); + + proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY1, 3000, 204); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY1, 4000, 203); + test.voteMultipleComputors(proposalIndex, 300, 350); + + proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY1, 5000, 205); + test.voteMultipleComputors(proposalIndex, 300, 350); + + //------------------------------------------------------------- + // epoch 202 + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { {ENTITY0, 150000, 123}, {ENTITY1, 1000, 201}, {ENTITY1, 4000, 203}, {ENTITY1, 3000, 204}, {ENTITY1, 5000, 205}, {ENTITY2, 2000, 205} }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + //------------------------------------------------------------- + // epoch 203 -> {ENTITY1, 1000, 201} is cleaned up, because {ENTITY1, 4000, 203} is in action now + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 150000, 123}, + {ENTITY1, 4000, 203}, {ENTITY1, 3000, 204}, {ENTITY1, 5000, 205}, + {ENTITY2, 2000, 205} + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // add more dontaions + proposalIndex = test.setupProposal(1, ProposalTypes::TransferThreeAmounts, ENTITY4, 6000); + test.voteMultipleComputors(proposalIndex, 100, 100, 400); // vote for second amount (6001) + revDonationOrder.push_back(ENTITY3); + + proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY3, 7000, 205); + test.voteMultipleComputors(proposalIndex, 100, 500); + revDonationOrder.push_back(ENTITY4); + + // add at front of ENTITY3 + proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY3, 8000, 204); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY3, 9000, 210); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY3, 10000, 207); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(5, ProposalTypes::TransferInEpochYesNo, ENTITY4, 11000, 220); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(6, ProposalTypes::TransferInEpochYesNo, ENTITY4, 12000, 210); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(7, ProposalTypes::TransferInEpochYesNo, ENTITY4, 13000, 208); + test.voteMultipleComputors(proposalIndex, 100, 500); + + // rejected due to non-future epoch + test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY4, 7500, 203, false); + + //------------------------------------------------------------- + // epoch 204 -> {ENTITY1, 4000, 203} is cleaned up, because {ENTITY1, 3000, 204} is in action now + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 150000, 123}, + {ENTITY1, 3000, 204}, {ENTITY1, 5000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 8000, 204}, {ENTITY3, 7000, 205}, {ENTITY3, 10000, 207}, {ENTITY3, 9000, 210}, + {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 11000, 220}, + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // update amounts (feature to overwrite/cancel scheduled changes) + proposalIndex = test.setupProposal(0, ProposalTypes::TransferYesNo, ENTITY3, 14000); // epoch 205 + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY1, 15000, 205); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY4, 16000, 220); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY3, 17000, 207); + test.voteMultipleComputors(proposalIndex, 100, 500); + + // update with higher proposal index overwrites the one with lower index (both ENTITY3 epoch 205) + proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY3, 18000, 205); + test.voteMultipleComputors(proposalIndex, 100, 500); + + //------------------------------------------------------------- + // epoch 205 -> {ENTITY1, 3000, 204} and {ENTITY3, 8000, 204} are cleaned up + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 150000, 123}, + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 18000, 205}, {ENTITY3, 17000, 207}, {ENTITY3, 9000, 210}, + {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // update amount (feature to overwrite/cancel scheduled changes) + proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY3, 19000, 210); + test.voteMultipleComputors(proposalIndex, 100, 500); + + // schedule new rev. donation for ENTITY0 + proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY0, 20000, 210); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY0, 21000, 207); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY0, 22000, 215); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY0, 23000, 208); + test.voteMultipleComputors(proposalIndex, 100, 500); + + //------------------------------------------------------------- + // epoch 206 -> nothing cleaned up (not epoch 206 donation entry gets in action) + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 150000, 123}, {ENTITY0, 21000, 207}, {ENTITY0, 23000, 208}, {ENTITY0, 20000, 210}, {ENTITY0, 22000, 215}, + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 18000, 205}, {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, + {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + //------------------------------------------------------------- + // epoch 207 -> entries from ENTITY0 and ENTITY3 cleaned up + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 21000, 207}, {ENTITY0, 23000, 208}, {ENTITY0, 20000, 210}, {ENTITY0, 22000, 215}, + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, + {ENTITY4, 6001, 204}, {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + //------------------------------------------------------------- + // epoch 208 -> entries from ENTITY0 and ENTITY4 cleaned up + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 23000, 208}, {ENTITY0, 20000, 210}, {ENTITY0, 22000, 215}, + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, + {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220}, + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // fill up the revenue donation table storage completely (2 of 10 are updated, 118 are added, 10 cannot be added + // due to lack of storage) + revDonationEntries = { {ENTITY0, 23000, 208} }; + for (unsigned int i = 0; i < 130; ++i) + { + proposalIndex = test.setupProposal(i, ProposalTypes::TransferInEpochYesNo, ENTITY0, 30000 + i, 210 + i); + test.voteMultipleComputors(proposalIndex, 100, 500); + if (i < 120) + revDonationEntries.push_back({ ENTITY0, 30000 + i, uint16(210 + i) }); + } + revDonationEntries.insert(revDonationEntries.end(), { + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 17000, 207}, {ENTITY3, 19000, 210}, + {ENTITY4, 13000, 208}, {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220} }); + EXPECT_EQ(revDonationEntries.size(), 128); + + //------------------------------------------------------------- + // epoch 209 -> no entries cleaned up + ++system.epoch; + test.beginEpoch(); + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + //------------------------------------------------------------- + // epoch 210 -> 3 entries cleaned up (ENTITY0, ENTITY3, ENTITY4) + ++system.epoch; + test.beginEpoch(); + + revDonationEntries.clear(); + for (unsigned int i = 0; i < 120; ++i) + { + revDonationEntries.push_back({ ENTITY0, 30000 + i, uint16(210 + i) }); + } + revDonationEntries.insert(revDonationEntries.end(), { + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 19000, 210}, + {ENTITY4, 12000, 210}, {ENTITY4, 16000, 220} }); + + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + + //------------------------------------------------------------- + // epoch 211 -> entry removed from ENTITY0 (first entry) + ++system.epoch; + test.beginEpoch(); + revDonationEntries.erase(revDonationEntries.begin()); + EXPECT_EQ(revDonationEntries.size(), 124); + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + // fill up storage at the end + proposalIndex = test.setupProposal(0, ProposalTypes::TransferInEpochYesNo, ENTITY4, 40000, 213); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(1, ProposalTypes::TransferInEpochYesNo, ENTITY4, 41000, 225); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(2, ProposalTypes::TransferInEpochYesNo, ENTITY4, 42000, 215); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(3, ProposalTypes::TransferInEpochYesNo, ENTITY4, 43000, 214); + test.voteMultipleComputors(proposalIndex, 100, 500); + + // will fail due to storage limitation + proposalIndex = test.setupProposal(4, ProposalTypes::TransferInEpochYesNo, ENTITY4, 44000, 230); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(5, ProposalTypes::TransferInEpochYesNo, ENTITY4, 44000, 212); + test.voteMultipleComputors(proposalIndex, 100, 500); + + revDonationEntries.erase(revDonationEntries.end() - 1); + revDonationEntries.insert(revDonationEntries.end(), { + {ENTITY4, 40000, 213}, {ENTITY4, 43000, 214}, {ENTITY4, 42000, 215}, {ENTITY4, 16000, 220}, {ENTITY4, 41000, 225}, + }); + + //------------------------------------------------------------- + // epoch 212 -> entry removed from ENTITY0 (first entry) + ++system.epoch; + test.beginEpoch(); + revDonationEntries.erase(revDonationEntries.begin()); + EXPECT_EQ(revDonationEntries.size(), 127); + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); + + //------------------------------------------------------------- + // run 120 epochs reducing table back to one entry per entity + revDonationEntries = { + {ENTITY0, 30119, 329}, + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 19000, 210}, + {ENTITY4, 41000, 225}, + }; + for (unsigned int i = 0; i < 120; ++i) + { + ++system.epoch; + test.beginEpoch(); + if (i == 119) + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries, true); + else + test.getState()->checkRevenueDonations(&revDonationOrder, nullptr, false); + } + + //------------------------------------------------------------- + // test that IDs are not removed even if donation percentage is 0 + proposalIndex = test.setupProposal(0, ProposalTypes::TransferYesNo, ENTITY0, 0); + test.voteMultipleComputors(proposalIndex, 100, 500); + + proposalIndex = test.setupProposal(1, ProposalTypes::TransferYesNo, ENTITY3, 0); + test.voteMultipleComputors(proposalIndex, 100, 500); + + ++system.epoch; + test.beginEpoch(); + revDonationEntries = { + {ENTITY0, 0, 333}, + {ENTITY1, 15000, 205}, + {ENTITY2, 2000, 205}, + {ENTITY3, 0, 333}, + {ENTITY4, 41000, 225}, + }; + test.getState()->checkRevenueDonations(&revDonationOrder, &revDonationEntries); +} diff --git a/test/contract_msvault.cpp b/test/contract_msvault.cpp new file mode 100644 index 000000000..e37260230 --- /dev/null +++ b/test/contract_msvault.cpp @@ -0,0 +1,1258 @@ +#define NO_UEFI + +#include "contract_testing.h" +#include "test_util.h" + +static const id OWNER1 = ID(_T, _K, _U, _W, _W, _S, _N, _B, _A, _E, _G, _W, _J, _H, _Q, _J, _D, _F, _L, _G, _Q, _H, _J, _J, _C, _J, _B, _A, _X, _B, _S, _Q, _M, _Q, _A, _Z, _J, _J, _D, _Y, _X, _E, _P, _B, _V, _B, _B, _L, _I, _Q, _A, _N, _J, _T, _I, _D); +static const id OWNER2 = ID(_F, _X, _J, _F, _B, _T, _J, _M, _Y, _F, _J, _H, _P, _B, _X, _C, _D, _Q, _T, _L, _Y, _U, _K, _G, _M, _H, _B, _B, _Z, _A, _A, _F, _T, _I, _C, _W, _U, _K, _R, _B, _M, _E, _K, _Y, _N, _U, _P, _M, _R, _M, _B, _D, _N, _D, _R, _G); +static const id OWNER3 = ID(_K, _E, _F, _D, _Z, _T, _Y, _L, _F, _E, _R, _A, _H, _D, _V, _L, _N, _Q, _O, _R, _D, _H, _F, _Q, _I, _B, _S, _B, _Z, _C, _W, _S, _Z, _X, _Z, _F, _F, _A, _N, _O, _T, _F, _A, _H, _W, _M, _O, _V, _G, _T, _R, _Q, _J, _P, _X, _D); +static const id TEST_VAULT_NAME = ID(_M, _Y, _M, _S, _V, _A, _U, _L, _U, _S, _E, _D, _F, _O, _R, _U, _N, _I, _T, _T, _T, _E, _S, _T, _I, _N, _G, _P, _U, _R, _P, _O, _S, _E, _S, _O, _N, _L, _Y, _U, _N, _I, _T, _T, _E, _S, _C, _O, _R, _E, _S, _M, _A, _R, _T, _T); + +static constexpr uint64 TWO_OF_TWO = 2ULL; +static constexpr uint64 TWO_OF_THREE = 2ULL; + +static const id DESTINATION = id::randomValue(); +static constexpr uint64 QX_ISSUE_ASSET_FEE = 1000000000ull; +static constexpr uint64 QX_MANAGEMENT_TRANSFER_FEE = 100ull; + +class ContractTestingMsVault : protected ContractTesting +{ +public: + ContractTestingMsVault() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(MSVAULT); + callSystemProcedure(MSVAULT_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(MSVAULT_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(MSVAULT_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + MSVAULT::registerVault_output registerVault(uint64 requiredApprovals, id vaultName, const std::vector& owners, uint64 fee) + { + MSVAULT::registerVault_input input; + for (uint64 i = 0; i < MSVAULT_MAX_OWNERS; i++) + { + input.owners.set(i, (i < owners.size()) ? owners[i] : NULL_ID); + } + input.requiredApprovals = requiredApprovals; + input.vaultName = vaultName; + MSVAULT::registerVault_output regOut; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 1, input, regOut, owners[0], fee); + return regOut; + } + + MSVAULT::deposit_output deposit(uint64 vaultId, uint64 amount, const id& from) + { + MSVAULT::deposit_input input; + input.vaultId = vaultId; + increaseEnergy(from, amount); + MSVAULT::deposit_output depOut; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 2, input, depOut, from, amount); + return depOut; + } + + MSVAULT::releaseTo_output releaseTo(uint64 vaultId, uint64 amount, const id& destination, const id& owner, uint64 fee = MSVAULT_RELEASE_FEE) + { + MSVAULT::releaseTo_input input; + input.vaultId = vaultId; + input.amount = amount; + input.destination = destination; + + increaseEnergy(owner, fee); + MSVAULT::releaseTo_output relOut; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 3, input, relOut, owner, fee); + return relOut; + } + + MSVAULT::resetRelease_output resetRelease(uint64 vaultId, const id& owner, uint64 fee = MSVAULT_RELEASE_RESET_FEE) + { + MSVAULT::resetRelease_input input; + input.vaultId = vaultId; + + increaseEnergy(owner, fee); + MSVAULT::resetRelease_output rstOut; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 4, input, rstOut, owner, fee); + return rstOut; + } + + MSVAULT::getVaultName_output getVaultName(uint64 vaultId) const + { + MSVAULT::getVaultName_input input; + MSVAULT::getVaultName_output output; + input.vaultId = vaultId; + callFunction(MSVAULT_CONTRACT_INDEX, 8, input, output); + return output; + } + + MSVAULT::getVaults_output getVaults(const id& pubKey) const + { + MSVAULT::getVaults_input input; + MSVAULT::getVaults_output output; + input.publicKey = pubKey; + callFunction(MSVAULT_CONTRACT_INDEX, 5, input, output); + return output; + } + + MSVAULT::getBalanceOf_output getBalanceOf(uint64 vaultId) const + { + MSVAULT::getBalanceOf_input input; + MSVAULT::getBalanceOf_output output; + input.vaultId = vaultId; + callFunction(MSVAULT_CONTRACT_INDEX, 7, input, output); + return output; + } + + MSVAULT::getReleaseStatus_output getReleaseStatus(uint64 vaultId) const + { + MSVAULT::getReleaseStatus_input input; + MSVAULT::getReleaseStatus_output output; + input.vaultId = vaultId; + callFunction(MSVAULT_CONTRACT_INDEX, 6, input, output); + return output; + } + + MSVAULT::getRevenueInfo_output getRevenueInfo() const + { + MSVAULT::getRevenueInfo_input input; + MSVAULT::getRevenueInfo_output output; + callFunction(MSVAULT_CONTRACT_INDEX, 9, input, output); + return output; + } + + // Helper: find newly created vault by difference + uint64 findNewvaultIdAfterRegister(const id& owner, uint64 prevCount) + { + auto vaultsAfter = getVaults(owner); + if (vaultsAfter.numberOfVaults == prevCount + 1) + { + return (uint64)vaultsAfter.vaultIds.get(prevCount); + } + return -1; + } + + void issueAsset(const id& issuer, const std::string& assetNameStr, sint64 numberOfShares) + { + uint64 assetName = assetNameFromString(assetNameStr.c_str()); + QX::IssueAsset_input input{ assetName, numberOfShares, 0, 0 }; + QX::IssueAsset_output output; + increaseEnergy(issuer, QX_ISSUE_ASSET_FEE); + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QX_ISSUE_ASSET_FEE); + } + + QX::TransferShareOwnershipAndPossession_output transferAsset(const id& from, const id& to, const Asset& asset, uint64_t amount) { + QX::TransferShareOwnershipAndPossession_input input; + input.issuer = asset.issuer; + input.newOwnerAndPossessor = to; + input.assetName = asset.assetName; + input.numberOfShares = amount; + QX::TransferShareOwnershipAndPossession_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, from, 1000000); + return output; + } + + int64_t transferShareManagementRights(const id& from, const Asset& asset, sint64 numberOfShares, uint32 newManagingContractIndex) + { + QX::TransferShareManagementRights_input input; + input.asset = asset; + input.numberOfShares = numberOfShares; + input.newManagingContractIndex = newManagingContractIndex; + QX::TransferShareManagementRights_output output; + output.transferredNumberOfShares = 0; + invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, from, 0); + return output.transferredNumberOfShares; + } + + MSVAULT::revokeAssetManagementRights_output revokeAssetManagementRights(const id& from, const Asset& asset, sint64 numberOfShares) + { + MSVAULT::revokeAssetManagementRights_input input; + input.asset = asset; + input.numberOfShares = numberOfShares; + MSVAULT::revokeAssetManagementRights_output output; + output.transferredNumberOfShares = 0; + output.status = 0; + + // The fee required by QX is 100. Do this to ensure enough fee. + const uint64 fee = 100; + increaseEnergy(from, fee); + + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 25, input, output, from, fee); + return output; + } + + MSVAULT::depositAsset_output depositAsset(uint64 vaultId, const Asset& asset, uint64 amount, const id& from) + { + MSVAULT::depositAsset_input input; + input.vaultId = vaultId; + input.asset = asset; + input.amount = amount; + MSVAULT::depositAsset_output output; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 19, input, output, from, 0); + return output; + } + + MSVAULT::releaseAssetTo_output releaseAssetTo(uint64 vaultId, const Asset& asset, uint64 amount, const id& destination, const id& owner, uint64 fee = MSVAULT_RELEASE_FEE) + { + MSVAULT::releaseAssetTo_input input; + input.vaultId = vaultId; + input.asset = asset; + input.amount = amount; + input.destination = destination; + + increaseEnergy(owner, fee); + MSVAULT::releaseAssetTo_output output; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 20, input, output, owner, fee); + return output; + } + + MSVAULT::resetAssetRelease_output resetAssetRelease(uint64 vaultId, const id& owner, uint64 fee = MSVAULT_RELEASE_RESET_FEE) + { + MSVAULT::resetAssetRelease_input input; + input.vaultId = vaultId; + + increaseEnergy(owner, fee); + MSVAULT::resetAssetRelease_output output; + invokeUserProcedure(MSVAULT_CONTRACT_INDEX, 21, input, output, owner, fee); + return output; + } + + MSVAULT::getVaultAssetBalances_output getVaultAssetBalances(uint64 vaultId) const + { + MSVAULT::getVaultAssetBalances_input input; + input.vaultId = vaultId; + MSVAULT::getVaultAssetBalances_output output; + callFunction(MSVAULT_CONTRACT_INDEX, 22, input, output); + return output; + } + + MSVAULT::getAssetReleaseStatus_output getAssetReleaseStatus(uint64 vaultId) const + { + MSVAULT::getAssetReleaseStatus_input input; + input.vaultId = vaultId; + MSVAULT::getAssetReleaseStatus_output output; + callFunction(MSVAULT_CONTRACT_INDEX, 23, input, output); + return output; + } + + ~ContractTestingMsVault() + { + } +}; + + +TEST(ContractMsVault, RegisterVault_InsufficientFee) +{ + ContractTestingMsVault msVault; + + // Check how many vaults OWNER1 has initially + auto vaultsO1Before = msVault.getVaults(OWNER1); + + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + + // Attempt with insufficient fee + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, 5000ULL); + EXPECT_EQ(regOut.status, 2ULL); // FAILURE_INSUFFICIENT_FEE + + // No new vault should be created + auto vaultsO1After = msVault.getVaults(OWNER1); + EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), + static_cast(vaultsO1Before.numberOfVaults)); +} + +TEST(ContractMsVault, RegisterVault_OneOwner) +{ + ContractTestingMsVault msVault; + auto vaultsO1Before = msVault.getVaults(OWNER1); + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + + // Only one owner => should fail + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 5ULL); // FAILURE_INVALID_PARAMS + + // Should fail, no new vault + auto vaultsO1After = msVault.getVaults(OWNER1); + EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), + static_cast(vaultsO1Before.numberOfVaults)); +} + +TEST(ContractMsVault, RegisterVault_Success) +{ + ContractTestingMsVault msVault; + auto vaultsO1Before = msVault.getVaults(OWNER1); + + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); // SUCCESS + + auto vaultsO1After = msVault.getVaults(OWNER1); + EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), + static_cast(vaultsO1Before.numberOfVaults + 1)); + + // Extract the new vaultId + uint64 vaultId = vaultsO1After.vaultIds.get(vaultsO1Before.numberOfVaults); + // Check vault name + auto nameOut = msVault.getVaultName(vaultId); + EXPECT_EQ(nameOut.vaultName, TEST_VAULT_NAME); + + // Check revenue info + auto revenue = msVault.getRevenueInfo(); + // At least one vault active, revenue should have increased + EXPECT_GE(revenue.numberOfActiveVaults, 1U); + EXPECT_GE(revenue.totalRevenue, MSVAULT_REGISTERING_FEE); + + auto balance = msVault.getBalanceOf(vaultId); + EXPECT_EQ(balance.balance, 0U); +} + +TEST(ContractMsVault, GetVaultName) +{ + ContractTestingMsVault msVault; + + auto vaultsO1Before = msVault.getVaults(OWNER1); + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1After = msVault.getVaults(OWNER1); + EXPECT_EQ(static_cast(vaultsO1After.numberOfVaults), + static_cast(vaultsO1Before.numberOfVaults + 1)); + uint64 vaultId = vaultsO1After.vaultIds.get(vaultsO1Before.numberOfVaults); + + auto nameOut = msVault.getVaultName(vaultId); + EXPECT_EQ(nameOut.vaultName, TEST_VAULT_NAME); + + auto invalidNameOut = msVault.getVaultName(9999ULL); + EXPECT_EQ(invalidNameOut.vaultName, NULL_ID); +} + +TEST(ContractMsVault, Deposit_InvalidVault) +{ + ContractTestingMsVault msVault; + // deposit to a non-existent vault + auto beforeBalance = msVault.getBalanceOf(999ULL); + auto depOut = msVault.deposit(999ULL, 5000ULL, OWNER1); + EXPECT_EQ(depOut.status, 3ULL); // FAILURE_INVALID_VAULT + // no change in balance + auto afterBalance = msVault.getBalanceOf(999ULL); + EXPECT_EQ(afterBalance.balance, beforeBalance.balance); +} + +TEST(ContractMsVault, Deposit_Success) +{ + ContractTestingMsVault msVault; + + auto vaultsO1Before = msVault.getVaults(OWNER1); + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1After = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1After.vaultIds.get(vaultsO1Before.numberOfVaults); + + auto balBefore = msVault.getBalanceOf(vaultId); + auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + auto balAfter = msVault.getBalanceOf(vaultId); + EXPECT_EQ(balAfter.balance, balBefore.balance + 10000ULL); +} + +TEST(ContractMsVault, ReleaseTo_NonOwner) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + auto releaseStatusBefore = msVault.getReleaseStatus(vaultId); + + // Non-owner attempt release + auto relOut = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER3); + EXPECT_EQ(relOut.status, 4ULL); // FAILURE_NOT_AUTHORIZED + auto releaseStatusAfter = msVault.getReleaseStatus(vaultId); + + // No approvals should be set + EXPECT_EQ(releaseStatusAfter.amounts.get(0), releaseStatusBefore.amounts.get(0)); + EXPECT_EQ(releaseStatusAfter.destinations.get(0), releaseStatusBefore.destinations.get(0)); +} + +TEST(ContractMsVault, ReleaseTo_InvalidParams) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE); + // 2 out of 2 owners + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + auto releaseStatusBefore = msVault.getReleaseStatus(vaultId); + // amount=0 + auto relOut1 = msVault.releaseTo(vaultId, 0ULL, OWNER2, OWNER1); + EXPECT_EQ(relOut1.status, 5ULL); // FAILURE_INVALID_PARAMS + auto releaseStatusAfter1 = msVault.getReleaseStatus(vaultId); + EXPECT_EQ(releaseStatusAfter1.amounts.get(0), releaseStatusBefore.amounts.get(0)); + + // destination NULL_ID + auto relOut2 = msVault.releaseTo(vaultId, 5000ULL, NULL_ID, OWNER1); + EXPECT_EQ(relOut2.status, 5ULL); // FAILURE_INVALID_PARAMS + auto releaseStatusAfter2 = msVault.getReleaseStatus(vaultId); + EXPECT_EQ(releaseStatusAfter2.amounts.get(0), releaseStatusBefore.amounts.get(0)); +} + +TEST(ContractMsVault, ReleaseTo_PartialApproval) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, 100000000ULL); + increaseEnergy(OWNER3, 100000000ULL); + + // 2 out of 3 owners + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 15000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + auto relOut = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER1); + EXPECT_EQ(relOut.status, 9ULL); // PENDING_APPROVAL + + auto status = msVault.getReleaseStatus(vaultId); + // Partial approval means just first owner sets the request + EXPECT_EQ(status.amounts.get(0), 5000ULL); + EXPECT_EQ(status.destinations.get(0), OWNER3); +} + +TEST(ContractMsVault, ReleaseTo_FullApproval) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, 100000000ULL); + increaseEnergy(OWNER2, 100000000ULL); + increaseEnergy(OWNER3, 100000000ULL); + + // 2 out of 3 + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + // OWNER1 requests 5000 Qubics to OWNER3 + auto relOut1 = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER1); + EXPECT_EQ(relOut1.status, 9ULL); // PENDING_APPROVAL + // Not approved yet + + auto relOut2 = msVault.releaseTo(vaultId, 5000ULL, OWNER3, OWNER2); // second approval + EXPECT_EQ(relOut2.status, 1ULL); // SUCCESS + + // After full approval, amount should be released + auto bal = msVault.getBalanceOf(vaultId); + EXPECT_EQ(bal.balance, 10000ULL - 5000ULL); +} + +TEST(ContractMsVault, ReleaseTo_InsufficientBalance) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, 100000000ULL); + increaseEnergy(OWNER2, 100000000ULL); + increaseEnergy(OWNER3, 100000000ULL); + + // 2 out of 2 + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 10000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + auto balBefore = msVault.getBalanceOf(vaultId); + // Attempt to release more than balance + auto relOut = msVault.releaseTo(vaultId, 20000ULL, OWNER3, OWNER1); + EXPECT_EQ(relOut.status, 6ULL); // FAILURE_INSUFFICIENT_BALANCE + + // Should fail, balance no change + auto balAfter = msVault.getBalanceOf(vaultId); + EXPECT_EQ(balAfter.balance, balBefore.balance); +} + +TEST(ContractMsVault, ResetRelease_NonOwner) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, 100000000ULL); + increaseEnergy(OWNER2, 100000000ULL); + increaseEnergy(OWNER3, 100000000ULL); + + // 2 out of 2 + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 5000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + auto relOut = msVault.releaseTo(vaultId, 2000ULL, OWNER2, OWNER1); + EXPECT_EQ(relOut.status, 9ULL); // PENDING_APPROVAL + + auto statusBefore = msVault.getReleaseStatus(vaultId); + auto rstOut = msVault.resetRelease(vaultId, OWNER3); // Non owner tries to reset + EXPECT_EQ(rstOut.status, 4ULL); // FAILURE_NOT_AUTHORIZED + auto statusAfter = msVault.getReleaseStatus(vaultId); + + // No change in release requests + EXPECT_EQ(statusAfter.amounts.get(0), statusBefore.amounts.get(0)); + EXPECT_EQ(statusAfter.destinations.get(0), statusBefore.destinations.get(0)); +} + +TEST(ContractMsVault, ResetRelease_Success) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, 100000000ULL); + increaseEnergy(OWNER2, 100000000ULL); + increaseEnergy(OWNER3, 100000000ULL); + + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaultsO1 = msVault.getVaults(OWNER1); + uint64 vaultId = vaultsO1.vaultIds.get(vaultsO1.numberOfVaults - 1); + + auto depOut = msVault.deposit(vaultId, 5000ULL, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + // OWNER2 requests a releaseTo + auto relOut = msVault.releaseTo(vaultId, 2000ULL, OWNER1, OWNER2); + EXPECT_EQ(relOut.status, 9ULL); + + // Now reset by OWNER2 + auto rstOut = msVault.resetRelease(vaultId, OWNER2); + EXPECT_EQ(rstOut.status, 1ULL); + + auto status = msVault.getReleaseStatus(vaultId); + // All cleared + for (uint16 i = 0; i < 3; i++) + { + EXPECT_EQ(status.amounts.get(i), 0ULL); + EXPECT_EQ(status.destinations.get(i), NULL_ID); + } +} + +TEST(ContractMsVault, GetVaults_Multiple) +{ + ContractTestingMsVault msVault; + + increaseEnergy(OWNER1, 100000000ULL); + increaseEnergy(OWNER2, 100000000ULL); + increaseEnergy(OWNER3, 100000000ULL); + + auto vaultsForOwner2Before = msVault.getVaults(OWNER2); + + auto regOut1 = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut1.status, 1ULL); + auto regOut2 = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut2.status, 1ULL); + + auto vaultsForOwner2After = msVault.getVaults(OWNER2); + EXPECT_GE(static_cast(vaultsForOwner2After.numberOfVaults), + static_cast(vaultsForOwner2Before.numberOfVaults + 2U)); +} + +TEST(ContractMsVault, GetRevenue) +{ + ContractTestingMsVault msVault; + const Asset assetTest = { OWNER1, assetNameFromString("TESTREV") }; + + increaseEnergy(OWNER1, 1000000000ULL); + increaseEnergy(OWNER2, 1000000000ULL); + + uint64 expectedRevenue = 0; + auto revenueInfo = msVault.getRevenueInfo(); + EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); + EXPECT_EQ(revenueInfo.numberOfActiveVaults, 0U); + + // Register a vault, generating the first fee + auto regOut = msVault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + expectedRevenue += MSVAULT_REGISTERING_FEE; + + auto vaults = msVault.getVaults(OWNER1); + uint64 vaultId = vaults.vaultIds.get(0); + + // Deposit QUs to ensure the vault can pay holding fees + const uint64 depositAmount = 10000000; // 10M QUs + auto depOut = msVault.deposit(vaultId, depositAmount, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + // expectedRevenue += state.liveDepositFee; // Fee is currently 0 + + // Generate Qubic-based fees + auto relOut = msVault.releaseTo(vaultId, 1000ULL, DESTINATION, OWNER1); + EXPECT_EQ(relOut.status, 9ULL); // Pending approval + expectedRevenue += MSVAULT_RELEASE_FEE; + auto rstOut = msVault.resetRelease(vaultId, OWNER1); + EXPECT_EQ(rstOut.status, 1ULL); + expectedRevenue += MSVAULT_RELEASE_RESET_FEE; + + // Generate Asset-based fees + msVault.issueAsset(OWNER1, "TESTREV", 10000); + msVault.transferShareManagementRights(OWNER1, assetTest, 5000, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msVault.depositAsset(vaultId, assetTest, 1000, OWNER1); + EXPECT_EQ(depAssetOut.status, 1ULL); + // expectedRevenue += state.liveDepositFee; // Fee is currently 0 + auto relAssetOut = msVault.releaseAssetTo(vaultId, assetTest, 50, DESTINATION, OWNER2); + EXPECT_EQ(relAssetOut.status, 9ULL); // Pending approval + expectedRevenue += MSVAULT_RELEASE_FEE; + auto rstAssetOut = msVault.resetAssetRelease(vaultId, OWNER2); + EXPECT_EQ(rstAssetOut.status, 1ULL); + expectedRevenue += MSVAULT_RELEASE_RESET_FEE; + + // Verify revenue before the first epoch ends + revenueInfo = msVault.getRevenueInfo(); + EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); + + msVault.endEpoch(); + msVault.beginEpoch(); + + // Holding fee from the active vault is collected + expectedRevenue += MSVAULT_HOLDING_FEE; + + revenueInfo = msVault.getRevenueInfo(); + EXPECT_EQ(revenueInfo.numberOfActiveVaults, 1U); + EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); + + // Verify dividends were distributed correctly based on the total revenue so far + uint64 expectedDistribution = (expectedRevenue / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS; + EXPECT_EQ(revenueInfo.totalDistributedToShareholders, expectedDistribution); + + // Make more revenue generation actions in the new epoch + auto relOut2 = msVault.releaseTo(vaultId, 2000ULL, DESTINATION, OWNER2); + EXPECT_EQ(relOut2.status, 9ULL); + expectedRevenue += MSVAULT_RELEASE_FEE; + + auto rstOut2 = msVault.resetRelease(vaultId, OWNER2); + EXPECT_EQ(rstOut2.status, 1ULL); + expectedRevenue += MSVAULT_RELEASE_RESET_FEE; + + // Revoke some of the previously granted management rights. + // This one has a fee, but it is paid to QX, not kept by MsVault. + // Therefore, expectedRevenue should NOT be incremented. + auto revokeOut = msVault.revokeAssetManagementRights(OWNER1, assetTest, 2000); + EXPECT_EQ(revokeOut.status, 1ULL); + + // End the second epoch + msVault.endEpoch(); + msVault.beginEpoch(); + + // Another holding fee is collected + expectedRevenue += MSVAULT_HOLDING_FEE; + + revenueInfo = msVault.getRevenueInfo(); + EXPECT_EQ(revenueInfo.numberOfActiveVaults, 1U); + EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); + + // Verify the new cumulative dividend distribution + expectedDistribution = (expectedRevenue / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS; + EXPECT_EQ(revenueInfo.totalDistributedToShareholders, expectedDistribution); + + // No new transactions in this epoch + msVault.endEpoch(); + msVault.beginEpoch(); + + // A third holding fee is collected + expectedRevenue += MSVAULT_HOLDING_FEE; + + revenueInfo = msVault.getRevenueInfo(); + EXPECT_EQ(revenueInfo.numberOfActiveVaults, 1U); + EXPECT_EQ(revenueInfo.totalRevenue, expectedRevenue); + + // Verify the final cumulative dividend distribution + expectedDistribution = (expectedRevenue / NUMBER_OF_COMPUTORS) * NUMBER_OF_COMPUTORS; + EXPECT_EQ(revenueInfo.totalDistributedToShareholders, expectedDistribution); +} + +TEST(ContractMsVault, ManagementRightsVsDirectDeposit) +{ + ContractTestingMsVault msvault; + + // Create an issuer and two users. + const id ISSUER = id::randomValue(); + const id USER_WITH_RIGHTS = id::randomValue(); // This user will do it correctly + const id USER_WITHOUT_RIGHTS = id::randomValue(); // This user will attempt a direct deposit first + + Asset assetTest = { ISSUER, assetNameFromString("ASSET") }; + const sint64 initialDistribution = 50000; + + // Give everyone energy for fees + increaseEnergy(ISSUER, QX_ISSUE_ASSET_FEE + (1000000 * 2)); + increaseEnergy(USER_WITH_RIGHTS, MSVAULT_REGISTERING_FEE + (1000000 * 3)); + increaseEnergy(USER_WITHOUT_RIGHTS, 1000000 * 3); // More energy for the correct attempt later + + // Issue the asset and distribute it to the two users + msvault.issueAsset(ISSUER, "ASSET", initialDistribution * 2); + msvault.transferAsset(ISSUER, USER_WITH_RIGHTS, assetTest, initialDistribution); + msvault.transferAsset(ISSUER, USER_WITHOUT_RIGHTS, assetTest, initialDistribution); + + // Verify initial on-chain balances (both users' shares are managed by QX currently) + EXPECT_EQ(numberOfShares(assetTest, { USER_WITH_RIGHTS, QX_CONTRACT_INDEX }, + { USER_WITH_RIGHTS, QX_CONTRACT_INDEX }), initialDistribution); + EXPECT_EQ(numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }, + { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }), initialDistribution); + + // Create a simple vault owned by USER_WITH_RIGHTS + auto regOut = msvault.registerVault(2, TEST_VAULT_NAME, { USER_WITH_RIGHTS, OWNER1 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaults = msvault.getVaults(USER_WITH_RIGHTS); + uint64 vaultId = vaults.vaultIds.get(0); + + // User with Management Rights + const sint64 sharesToManage1 = 10000; + msvault.transferShareManagementRights(USER_WITH_RIGHTS, assetTest, sharesToManage1, MSVAULT_CONTRACT_INDEX); + + // verify that management rights were transferred successfully + EXPECT_EQ(numberOfShares(assetTest, { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }, + { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }), sharesToManage1); + EXPECT_EQ(numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }), 0); + + // This user now makes multiple deposits + const sint64 deposit1_U1 = 1000; + const sint64 deposit2_U1 = 2500; + auto depAssetOut1 = msvault.depositAsset(vaultId, assetTest, deposit1_U1, USER_WITH_RIGHTS); + EXPECT_EQ(depAssetOut1.status, 1ULL); + + // Verify balances after first deposit + sint64 sc_onchain_balance = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(sc_onchain_balance, deposit1_U1); + sint64 user_managed_balance = numberOfShares(assetTest, { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }, + { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(user_managed_balance, sharesToManage1 - deposit1_U1); + + auto depAssetOut2 = msvault.depositAsset(vaultId, assetTest, deposit2_U1, USER_WITH_RIGHTS); + EXPECT_EQ(depAssetOut2.status, 1ULL); + + // verify balances after second deposit + sc_onchain_balance = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(sc_onchain_balance, deposit1_U1 + deposit2_U1); + sint64 user1_managed_balance = numberOfShares(assetTest, { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }, + { USER_WITH_RIGHTS, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(user1_managed_balance, sharesToManage1 - deposit1_U1 - deposit2_U1); + + // user without management rights + sint64 sc_balance_before_direct_attempt = sc_onchain_balance; + sint64 user3_balance_before = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }, + { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }); + + // This user attempts to deposit directly + auto depAssetOut3 = msvault.depositAsset(vaultId, assetTest, 500, USER_WITHOUT_RIGHTS); + EXPECT_EQ(depAssetOut3.status, 6ULL); // FAILURE_INSUFFICIENT_BALANCE + + // Verify that no shares were transferred + sint64 sc_balance_after_direct_attempt = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(sc_balance_after_direct_attempt, sc_balance_before_direct_attempt); + + sint64 user3_balance_after = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }, + { USER_WITHOUT_RIGHTS, QX_CONTRACT_INDEX }); + EXPECT_EQ(user3_balance_after, user3_balance_before); // User's balance should be unchanged + + sint64 user3_balance_after_msvault = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }, + { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(user3_balance_after_msvault, 0); + + // the second user now does it the correct way + const sint64 sharesToManage2 = 8000; + msvault.transferShareManagementRights(USER_WITHOUT_RIGHTS, assetTest, sharesToManage2, MSVAULT_CONTRACT_INDEX); + + // Verify their management rights were transferred successfully + EXPECT_EQ(numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }, + { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }), sharesToManage2); + + const sint64 deposit1_U2 = 4000; + auto depAssetOut4 = msvault.depositAsset(vaultId, assetTest, deposit1_U2, USER_WITHOUT_RIGHTS); + EXPECT_EQ(depAssetOut4.status, 1ULL); + + // check the total balance in the smart contract + sint64 final_sc_balance = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + sint64 total_deposited = deposit1_U1 + deposit2_U1 + deposit1_U2; + EXPECT_EQ(final_sc_balance, total_deposited); + + // Also verify the second user's remaining managed balance + sint64 user2_managed_balance = numberOfShares(assetTest, { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }, + { USER_WITHOUT_RIGHTS, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(user2_managed_balance, sharesToManage2 - deposit1_U2); +} + +TEST(ContractMsVault, DepositAsset_Success) +{ + ContractTestingMsVault msvault; + Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; + + // Create a vault and issue an asset to OWNER1 + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + QX_ISSUE_ASSET_FEE); + auto regOut = msvault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + auto vaults = msvault.getVaults(OWNER1); + uint64 vaultId = vaults.vaultIds.get(0); + + msvault.issueAsset(OWNER1, "ASSET", 1000000); + + auto OWNER4 = id::randomValue(); + + auto transfered = msvault.transferShareManagementRights(OWNER1, assetTest, 5000, MSVAULT_CONTRACT_INDEX); + + // Deposit the asset into the vault + auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 500, OWNER1); + EXPECT_EQ(depAssetOut.status, 1ULL); + + // Check the vault's asset balance + auto assetBalances = msvault.getVaultAssetBalances(vaultId); + EXPECT_EQ(assetBalances.status, 1ULL); + EXPECT_EQ(assetBalances.numberOfAssetTypes, 1ULL); + + auto firstAssetBalance = assetBalances.assetBalances.get(0); + EXPECT_EQ(firstAssetBalance.asset.issuer, assetTest.issuer); + EXPECT_EQ(firstAssetBalance.asset.assetName, assetTest.assetName); + EXPECT_EQ(firstAssetBalance.balance, 500ULL); + + // Check SC's shares + sint64 scShares = numberOfShares(assetTest, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(scShares, 500LL); +} + +TEST(ContractMsVault, DepositAsset_MaxTypes) +{ + ContractTestingMsVault msvault; + + // Create a vault + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + QX_ISSUE_ASSET_FEE * (MSVAULT_MAX_ASSET_TYPES + 1)); // Extra energy for fees + auto regOut = msvault.registerVault(2ULL, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + auto vaults = msvault.getVaults(OWNER1); + uint64 vaultId = vaults.vaultIds.get(0); + + // Deposit the maximum number of different asset types + for (uint64 i = 0; i < MSVAULT_MAX_ASSET_TYPES; i++) + { + std::string assetName = "ASSET" + std::to_string(i); + Asset currentAsset = { OWNER1, assetNameFromString(assetName.c_str()) }; + msvault.issueAsset(OWNER1, assetName, 1000000); + msvault.transferShareManagementRights(OWNER1, currentAsset, 100000, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msvault.depositAsset(vaultId, currentAsset, 1000, OWNER1); + EXPECT_EQ(depAssetOut.status, 1ULL); + } + + // Check if max asset types reached + auto balances = msvault.getVaultAssetBalances(vaultId); + EXPECT_EQ(balances.numberOfAssetTypes, MSVAULT_MAX_ASSET_TYPES); + + // Try to deposit one more asset type + Asset extraAsset = { OWNER1, assetNameFromString("ASSETE") }; + msvault.issueAsset(OWNER1, "ASSETE", 100000); + msvault.transferShareManagementRights(OWNER1, extraAsset, 100000, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msvault.depositAsset(vaultId, extraAsset, 1000, OWNER1); + EXPECT_EQ(depAssetOut.status, 7ULL); // FAILURE_LIMIT_REACHED + + // The number of asset types should not have increased + auto balancesAfter = msvault.getVaultAssetBalances(vaultId); + EXPECT_EQ(balancesAfter.numberOfAssetTypes, MSVAULT_MAX_ASSET_TYPES); +} + +TEST(ContractMsVault, ReleaseAssetTo_FullApproval) +{ + ContractTestingMsVault msvault; + Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; + + // Create a 2-of-3 vault, issue and deposit an asset + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + MSVAULT_RELEASE_FEE + QX_ISSUE_ASSET_FEE + QX_MANAGEMENT_TRANSFER_FEE); + increaseEnergy(OWNER2, MSVAULT_RELEASE_FEE); + auto regOut = msvault.registerVault(TWO_OF_THREE, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + auto vaults = msvault.getVaults(OWNER1); + uint64 vaultId = vaults.vaultIds.get(0); + + msvault.issueAsset(OWNER1, "ASSET", 1000000); + msvault.transferShareManagementRights(OWNER1, assetTest, 800, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 800, OWNER1); + EXPECT_EQ(depAssetOut.status, 1ULL); + + // Deposit funds into the vault to cover the upcoming management transfer fee. + auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + // Check initial balances for the destination + EXPECT_EQ(numberOfShares(assetTest, { DESTINATION, QX_CONTRACT_INDEX }, { DESTINATION, QX_CONTRACT_INDEX }), 0LL); + EXPECT_EQ(numberOfShares(assetTest, { DESTINATION, MSVAULT_CONTRACT_INDEX }, { DESTINATION, MSVAULT_CONTRACT_INDEX }), 0LL); + auto vaultAssetBalanceBefore = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; + EXPECT_EQ(vaultAssetBalanceBefore, 800ULL); + + // Owners approve the release + auto relAssetOut1 = msvault.releaseAssetTo(vaultId, assetTest, 800, DESTINATION, OWNER1); + EXPECT_EQ(relAssetOut1.status, 9ULL); + auto relAssetOut2 = msvault.releaseAssetTo(vaultId, assetTest, 800, DESTINATION, OWNER2); + EXPECT_EQ(relAssetOut2.status, 1ULL); + + // Check final balances + sint64 destBalanceManagedByQx = numberOfShares(assetTest, { DESTINATION, QX_CONTRACT_INDEX }, { DESTINATION, QX_CONTRACT_INDEX }); + sint64 destBalanceManagedByMsVault = numberOfShares(assetTest, { DESTINATION, MSVAULT_CONTRACT_INDEX }, { DESTINATION, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(destBalanceManagedByQx, 800LL); + EXPECT_EQ(destBalanceManagedByMsVault, 0LL); + + auto vaultAssetBalanceAfter = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; + EXPECT_EQ(vaultAssetBalanceAfter, 0ULL); // 800 - 800 + + // The vault's qubic balance should be 0 after paying the management transfer fee + auto vaultQubicBalanceAfter = msvault.getBalanceOf(vaultId); + EXPECT_EQ(vaultQubicBalanceAfter.balance, 0LL); + + // Release status should be reset + auto releaseStatus = msvault.getAssetReleaseStatus(vaultId); + EXPECT_EQ(releaseStatus.amounts.get(0), 0ULL); + EXPECT_EQ(releaseStatus.destinations.get(0), NULL_ID); +} + +TEST(ContractMsVault, ReleaseAssetTo_PartialApproval) +{ + ContractTestingMsVault msvault; + Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; + + // Setup + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + MSVAULT_RELEASE_FEE + QX_MANAGEMENT_TRANSFER_FEE); + auto regOut = msvault.registerVault(TWO_OF_THREE, TEST_VAULT_NAME, { OWNER1, OWNER2, OWNER3 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaults = msvault.getVaults(OWNER1); + uint64 vaultId = vaults.vaultIds.get(0); + msvault.issueAsset(OWNER1, "ASSET", 1000000); + msvault.transferShareManagementRights(OWNER1, assetTest, 800, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 800, OWNER1); + EXPECT_EQ(depAssetOut.status, 1ULL); + + // Deposit the fee into the vault so it can process release requests. + auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + // Only one owner approves + auto relAssetOut = msvault.releaseAssetTo(vaultId, assetTest, 500, DESTINATION, OWNER1); + EXPECT_EQ(relAssetOut.status, 9ULL); // PENDING_APPROVAL + + // Check release status is pending + auto status = msvault.getAssetReleaseStatus(vaultId); + EXPECT_EQ(status.status, 1ULL); + // Owner 1 is at index 0 + EXPECT_EQ(status.assets.get(0).assetName, assetTest.assetName); + EXPECT_EQ(status.amounts.get(0), 500ULL); + EXPECT_EQ(status.destinations.get(0), DESTINATION); + // Other owner slots are empty + EXPECT_EQ(status.amounts.get(1), 0ULL); + + // Balances should be unchanged + sint64 destinationBalance = numberOfShares(assetTest, { DESTINATION, QX_CONTRACT_INDEX }, + { DESTINATION, QX_CONTRACT_INDEX }); + EXPECT_EQ(destinationBalance, 0LL); + auto vaultBalance = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; + EXPECT_EQ(vaultBalance, 800ULL); +} + +TEST(ContractMsVault, ResetAssetRelease_Success) +{ + ContractTestingMsVault msvault; + Asset assetTest = { OWNER1, assetNameFromString("ASSET") }; + + // Setup + increaseEnergy(OWNER1, MSVAULT_REGISTERING_FEE + MSVAULT_RELEASE_RESET_FEE + MSVAULT_RELEASE_FEE + QX_MANAGEMENT_TRANSFER_FEE); + auto regOut = msvault.registerVault(TWO_OF_TWO, TEST_VAULT_NAME, { OWNER1, OWNER2 }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + auto vaults = msvault.getVaults(OWNER1); + uint64 vaultId = vaults.vaultIds.get(0); + msvault.issueAsset(OWNER1, "ASSET", 1000000); + msvault.transferShareManagementRights(OWNER1, assetTest, 100, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msvault.depositAsset(vaultId, assetTest, 100, OWNER1); + EXPECT_EQ(depAssetOut.status, 1ULL); + + auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, OWNER1); + EXPECT_EQ(depOut.status, 1ULL); + + // Propose and then reset a release + auto relAssetOut = msvault.releaseAssetTo(vaultId, assetTest, 50, DESTINATION, OWNER1); + EXPECT_EQ(relAssetOut.status, 9ULL); + + // Check status is pending before reset + auto statusBefore = msvault.getAssetReleaseStatus(vaultId); + EXPECT_EQ(statusBefore.amounts.get(0), 50ULL); + + // Reset the release + auto rstAssetOut = msvault.resetAssetRelease(vaultId, OWNER1); + EXPECT_EQ(rstAssetOut.status, 1ULL); + + // Status should be cleared for that owner + auto statusAfter = msvault.getAssetReleaseStatus(vaultId); + EXPECT_EQ(statusAfter.amounts.get(0), 0ULL); + EXPECT_EQ(statusAfter.destinations.get(0), NULL_ID); + + // Vault balance should be unchanged + auto vaultBalance = msvault.getVaultAssetBalances(vaultId).assetBalances.get(0).balance; + EXPECT_EQ(vaultBalance, 100ULL); +} + +TEST(ContractMsVault, FullLifecycle_BalanceVerification) +{ + ContractTestingMsVault msvault; + const id USER = OWNER1; + const id PARTNER = OWNER2; + const id DESTINATION_ACC = OWNER3; + Asset assetTest = { USER, assetNameFromString("ASSET") }; + const sint64 initialShares = 10000; + const sint64 sharesToManage = 5000; + const sint64 sharesToDeposit = 4000; + const sint64 sharesToRelease = 1500; + + // Issue asset and create a type 2 vault + increaseEnergy(USER, MSVAULT_REGISTERING_FEE + QX_ISSUE_ASSET_FEE + QX_MANAGEMENT_TRANSFER_FEE); + increaseEnergy(PARTNER, MSVAULT_RELEASE_FEE); + + msvault.issueAsset(USER, "ASSET", initialShares); + auto regOut = msvault.registerVault(TWO_OF_TWO, TEST_VAULT_NAME, { USER, PARTNER }, MSVAULT_REGISTERING_FEE); + EXPECT_EQ(regOut.status, 1ULL); + + auto vaults = msvault.getVaults(USER); + uint64 vaultId = vaults.vaultIds.get(0); + + // Verify user has full on-chain balance under QX management + sint64 userShares_QX = numberOfShares(assetTest, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }); + EXPECT_EQ(userShares_QX, initialShares); + + // Fund the vault for the future management transfer fee + auto depOut = msvault.deposit(vaultId, QX_MANAGEMENT_TRANSFER_FEE, USER); + EXPECT_EQ(depOut.status, 1ULL); + + // User gives MsVault management rights over a portion of their shares + msvault.transferShareManagementRights(USER, assetTest, sharesToManage, MSVAULT_CONTRACT_INDEX); + + // Verify on-chain balances after management transfer + sint64 userShares_MSVAULT_Managed = numberOfShares(assetTest, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }); + userShares_QX = numberOfShares(assetTest, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }); + EXPECT_EQ(userShares_MSVAULT_Managed, sharesToManage); + EXPECT_EQ(userShares_QX, initialShares - sharesToManage); + + // User deposits the MsVault-managed shares into the vault + auto depAssetOut = msvault.depositAsset(vaultId, assetTest, sharesToDeposit, USER); + EXPECT_EQ(depAssetOut.status, 1ULL); + + // User's on-chain balance of MsVault-managed shares should decrease + userShares_MSVAULT_Managed = numberOfShares(assetTest, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(userShares_MSVAULT_Managed, sharesToManage - sharesToDeposit); // 5000 - 4000 = 1000 + + // MsVault contract's on-chain balance should increase + sint64 scShares_onchain = numberOfShares(assetTest, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(scShares_onchain, sharesToDeposit); + + // Vault's internal balance should match the on-chain balance + auto vaultBalances = msvault.getVaultAssetBalances(vaultId); + EXPECT_EQ(vaultBalances.status, 1ULL); + EXPECT_EQ(vaultBalances.numberOfAssetTypes, 1ULL); + EXPECT_EQ(vaultBalances.assetBalances.get(0).balance, sharesToDeposit); + + // Both owners approve a release to the destination + auto relAssetOut1 = msvault.releaseAssetTo(vaultId, assetTest, sharesToRelease, DESTINATION_ACC, USER); + EXPECT_EQ(relAssetOut1.status, 9ULL); + auto relAssetOut2 = msvault.releaseAssetTo(vaultId, assetTest, sharesToRelease, DESTINATION_ACC, PARTNER); + EXPECT_EQ(relAssetOut2.status, 1ULL); + + // MsVault contract's on-chain balance should decrease + scShares_onchain = numberOfShares(assetTest, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(scShares_onchain, sharesToDeposit - sharesToRelease); + + // Vault's internal balance should be updated correctly + vaultBalances = msvault.getVaultAssetBalances(vaultId); + EXPECT_EQ(vaultBalances.assetBalances.get(0).balance, sharesToDeposit - sharesToRelease); + + // Vault's internal qubic balance should decrease by the fee + EXPECT_EQ(msvault.getBalanceOf(vaultId).balance, 0); + + // Destination's on-chain balance should increase, and it should be managed by QX + sint64 destinationSharesManagedByQx = numberOfShares(assetTest, { DESTINATION_ACC, QX_CONTRACT_INDEX }, { DESTINATION_ACC, QX_CONTRACT_INDEX }); + sint64 destinationSharesManagedByMsVault = numberOfShares(assetTest, { DESTINATION_ACC, MSVAULT_CONTRACT_INDEX }, { DESTINATION_ACC, MSVAULT_CONTRACT_INDEX }); + + EXPECT_EQ(destinationSharesManagedByQx, sharesToRelease); + EXPECT_EQ(destinationSharesManagedByMsVault, 0); +} + +TEST(ContractMsVault, StressTest_MultiUser_MultiAsset) +{ + ContractTestingMsVault msvault; + + // Define users, assets, and vaults + const int USER_COUNT = 16; + const int ASSET_COUNT = 8; + const int VAULT_COUNT = 3; + std::vector users; + std::vector assets; + + for (int i = 0; i < USER_COUNT; ++i) + { + users.push_back(id::randomValue()); + increaseEnergy(users[i], 1000000000000ULL); + } + + for (int i = 0; i < ASSET_COUNT; ++i) + { + // Issue each asset from a different user for variety + id issuer = users[i]; + std::string assetName = "ASSET" + std::to_string(i); + assets.push_back({ issuer, assetNameFromString(assetName.c_str()) }); + msvault.issueAsset(issuer, assetName, 1000000); // Issue 1M of each token + } + + // Create 3 vaults with different sets of 4 owners each + EXPECT_EQ(msvault.registerVault(3, id::randomValue(), { users[0], users[1], users[2], users[3] }, MSVAULT_REGISTERING_FEE).status, 1ULL); + EXPECT_EQ(msvault.registerVault(2, id::randomValue(), { users[4], users[5], users[6], users[7] }, MSVAULT_REGISTERING_FEE).status, 1ULL); + EXPECT_EQ(msvault.registerVault(4, id::randomValue(), { users[8], users[9], users[10], users[11] }, MSVAULT_REGISTERING_FEE).status, 1ULL); + + // Each of the 8 assets is deposited twice by its owner + uint64 targetVaultId = 0; + const sint64 depositAmount = 100; + + for (int i = 0; i < USER_COUNT; ++i) + { + int assetIndex = i % ASSET_COUNT; + Asset assetToDeposit = assets[assetIndex]; + id owner_of_asset = users[assetIndex]; + + msvault.transferShareManagementRights(owner_of_asset, assetToDeposit, depositAmount, MSVAULT_CONTRACT_INDEX); + auto depAssetOut = msvault.depositAsset(targetVaultId, assetToDeposit, depositAmount, owner_of_asset); + EXPECT_EQ(depAssetOut.status, 1ULL); + } + + auto depOut = msvault.deposit(targetVaultId, QX_MANAGEMENT_TRANSFER_FEE, users[0]); + EXPECT_EQ(depOut.status, 1ULL); + + // Check the state of the target vault + auto vaultBalances = msvault.getVaultAssetBalances(targetVaultId); + EXPECT_EQ(vaultBalances.status, 1ULL); + EXPECT_EQ(vaultBalances.numberOfAssetTypes, (uint64_t)ASSET_COUNT); + for (uint64 i = 0; i < ASSET_COUNT; ++i) + { + // Verify each asset has a balance of 200 (deposited twice) + EXPECT_EQ(vaultBalances.assetBalances.get(i).balance, depositAmount * 2); + } + + // From Vault 0, owners 0, 1, and 2 approve a release + const id releaseDestination = users[15]; + const Asset assetToRelease = assets[0]; // Release ASSET_0 + const sint64 releaseAmount = 75; + + // A 3-of-4 vault, so we need 3 approvals + EXPECT_EQ(msvault.releaseAssetTo(targetVaultId, assetToRelease, releaseAmount, releaseDestination, users[0]).status, 9ULL); + EXPECT_EQ(msvault.releaseAssetTo(targetVaultId, assetToRelease, releaseAmount, releaseDestination, users[1]).status, 9ULL); + EXPECT_EQ(msvault.releaseAssetTo(targetVaultId, assetToRelease, releaseAmount, releaseDestination, users[2]).status, 1ULL); + + // Check destination on-chain balance + sint64 destBalance = numberOfShares(assetToRelease, { releaseDestination, QX_CONTRACT_INDEX }, + { releaseDestination, QX_CONTRACT_INDEX }); + EXPECT_EQ(destBalance, releaseAmount); + + // Check vault's internal accounting for the released asset + vaultBalances = msvault.getVaultAssetBalances(targetVaultId); + bool foundReleasedAsset = false; + for (uint64 i = 0; i < vaultBalances.numberOfAssetTypes; ++i) + { + auto bal = vaultBalances.assetBalances.get(i); + if (bal.asset.assetName == assetToRelease.assetName && bal.asset.issuer == assetToRelease.issuer) + { + // Expected balance is (100 * 2) - 75 = 125 + EXPECT_EQ(bal.balance, (depositAmount * 2) - releaseAmount); + foundReleasedAsset = true; + break; + } + } + EXPECT_TRUE(foundReleasedAsset); + + // Check MsVault's on-chain balance for the released asset + sint64 scOnChainBalance = numberOfShares(assetToRelease, { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }, + { id(MSVAULT_CONTRACT_INDEX, 0, 0, 0), MSVAULT_CONTRACT_INDEX }); + // The total on-chain balance should also be (100 * 2) - 75 = 125 + EXPECT_EQ(scOnChainBalance, (depositAmount * 2) - releaseAmount); +} + +TEST(ContractMsVault, RevokeAssetManagementRights_Success) +{ + ContractTestingMsVault msvault; + + const id USER = OWNER1; + const Asset asset = { USER, assetNameFromString("REVOKE") }; + const sint64 initialShares = 10000; + const sint64 sharesToManage = 4000; + const sint64 sharesToRevoke = 3000; + + // Issue asset and transfer management rights to MsVault + increaseEnergy(USER, QX_ISSUE_ASSET_FEE + 1000000 + 100); // Energy for all fees + msvault.issueAsset(USER, "REVOKE", initialShares); + + // Verify initial state: all shares managed by QX + EXPECT_EQ(numberOfShares(asset, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }), initialShares); + EXPECT_EQ(numberOfShares(asset, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }), 0); + + // User gives MsVault management rights over a portion of their shares + msvault.transferShareManagementRights(USER, asset, sharesToManage, MSVAULT_CONTRACT_INDEX); + + // Verify intermediate state: rights are split between QX and MsVault + EXPECT_EQ(numberOfShares(asset, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }), initialShares - sharesToManage); + EXPECT_EQ(numberOfShares(asset, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }), sharesToManage); + + // User revokes a portion of the managed rights from MsVault. The helper now handles the fee. + auto revokeOut = msvault.revokeAssetManagementRights(USER, asset, sharesToRevoke); + + // Verify the outcome + EXPECT_EQ(revokeOut.status, 1ULL); + EXPECT_EQ(revokeOut.transferredNumberOfShares, sharesToRevoke); + + // The amount managed by MsVault should decrease + sint64 finalManagedByMsVault = numberOfShares(asset, { USER, MSVAULT_CONTRACT_INDEX }, { USER, MSVAULT_CONTRACT_INDEX }); + EXPECT_EQ(finalManagedByMsVault, sharesToManage - sharesToRevoke); // 4000 - 3000 = 1000 + + // The amount managed by QX should increase accordingly + sint64 finalManagedByQx = numberOfShares(asset, { USER, QX_CONTRACT_INDEX }, { USER, QX_CONTRACT_INDEX }); + EXPECT_EQ(finalManagedByQx, (initialShares - sharesToManage) + sharesToRevoke); // 6000 + 3000 = 9000 +} diff --git a/test/contract_nostromo.cpp b/test/contract_nostromo.cpp new file mode 100644 index 000000000..262e5e2d8 --- /dev/null +++ b/test/contract_nostromo.cpp @@ -0,0 +1,1692 @@ +#define NO_UEFI + +#include +#include + +#include "contract_testing.h" + +static std::mt19937_64 rand64; + +static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) +{ + if(minValue > maxValue) + { + return 0; + } + return minValue + rand64() % (maxValue - minValue); +} + +static id getUser(unsigned long long i) +{ + return id(i, i / 2 + 4, i + 10, i * 3 + 8); +} + +static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) +{ + unsigned long long userCount = random(0, maxNum); + std::vector users; + users.reserve(userCount); + for (unsigned int i = 0; i < userCount; ++i) + { + unsigned long long userIdx = random(0, totalUsers - 1); + users.push_back(getUser(userIdx)); + } + return users; +} + +class NostromoChecker : public NOST +{ +public: + void registerChecker(id registerId, uint32 tierLevel, uint32 indexOfRegister) + { + EXPECT_EQ(users.contains(registerId), 1); + uint8 stateTierLevel; + users.get(registerId, stateTierLevel); + EXPECT_EQ(tierLevel, stateTierLevel); + } + void countOfRegisterChecker(uint32 totalUser) + { + EXPECT_EQ(totalUser, numberOfRegister); + } + void logoutFromTierChecker(id registerId) + { + EXPECT_EQ(users.contains(registerId), 0); + } + void numberOfCreatedProjectChecker(uint32 numberOfProjects) + { + EXPECT_EQ(numberOfProjects, numberOfCreatedProject); + } + void createdProjectChecker(uint32 indexOfProject, id creator, uint64 assetName, uint32 supply, uint32 startYear, uint32 startMonth, uint32 startDay, uint32 startHour, uint32 endYear, uint32 endMonth, uint32 endDay, uint32 endHour) + { + uint32 startDate, endDate; + NOST::packNostromoDate(startYear, startMonth, startDay, startHour, 0, 0, startDate); + NOST::packNostromoDate(endYear, endMonth, endDay, endHour, 0, 0, endDate); + + EXPECT_EQ(tokens.contains(assetName), 1); + EXPECT_EQ(projects.get(indexOfProject).creator, creator); + EXPECT_EQ(projects.get(indexOfProject).isCreatedFundarasing, 0); + EXPECT_EQ(projects.get(indexOfProject).numberOfNo, 0); + EXPECT_EQ(projects.get(indexOfProject).numberOfYes, 0); + EXPECT_EQ(projects.get(indexOfProject).supplyOfToken, supply); + EXPECT_EQ(projects.get(indexOfProject).tokenName, assetName); + EXPECT_EQ(projects.get(indexOfProject).startDate, startDate); + EXPECT_EQ(projects.get(indexOfProject).endDate, endDate); + } + void epochRevenueChecker(uint64 amountOfRevenue) + { + EXPECT_EQ(amountOfRevenue, epochRevenue); + } + void totalPoolWeightChecker(uint32 totalWeight) + { + EXPECT_EQ(totalWeight, totalPoolWeight); + } + void voteInProjectChecker(uint32 indexOfProject, uint32 numberOfYes, uint32 numberOfNo) + { + EXPECT_EQ(projects.get(indexOfProject).numberOfYes, numberOfYes); + EXPECT_EQ(projects.get(indexOfProject).numberOfNo, numberOfNo); + } + void numberOfVotedProjectAndVotedListChecker(id registerId, uint32 numberOfProject, Array votedList) + { + uint32 count; + numberOfVotedProject.get(registerId, count); + EXPECT_EQ(count, numberOfProject); + + Array vote; + voteStatus.get(registerId, vote); + for (uint32 i = 0; i < count; i++) + { + EXPECT_EQ(vote.get(i), votedList.get(i)); + } + } + void countOfFundraisingChecker(uint32 count) + { + EXPECT_EQ(count, numberOfFundraising); + } + void createFundraisingChecker(const id& registerId, + uint64 tokenPrice, + uint64 soldAmount, + uint64 requiredFunds, + + uint32 indexOfProject, + uint32 firstPhaseStartYear, + uint32 firstPhaseStartMonth, + uint32 firstPhaseStartDay, + uint32 firstPhaseStartHour, + uint32 firstPhaseEndYear, + uint32 firstPhaseEndMonth, + uint32 firstPhaseEndDay, + uint32 firstPhaseEndHour, + + uint32 secondPhaseStartYear, + uint32 secondPhaseStartMonth, + uint32 secondPhaseStartDay, + uint32 secondPhaseStartHour, + uint32 secondPhaseEndYear, + uint32 secondPhaseEndMonth, + uint32 secondPhaseEndDay, + uint32 secondPhaseEndHour, + + uint32 thirdPhaseStartYear, + uint32 thirdPhaseStartMonth, + uint32 thirdPhaseStartDay, + uint32 thirdPhaseStartHour, + uint32 thirdPhaseEndYear, + uint32 thirdPhaseEndMonth, + uint32 thirdPhaseEndDay, + uint32 thirdPhaseEndHour, + + uint32 listingStartYear, + uint32 listingStartMonth, + uint32 listingStartDay, + uint32 listingStartHour, + + uint32 cliffEndYear, + uint32 cliffEndMonth, + uint32 cliffEndDay, + uint32 cliffEndHour, + + uint32 vestingEndYear, + uint32 vestingEndMonth, + uint32 vestingEndDay, + uint32 vestingEndHour, + + uint8 threshold, + uint8 TGE, + uint8 stepOfVesting, + + uint32 indexOfFundraising) + { + uint32 firstPhaseStartDate_t, secondPhaseStartDate_t, thirdPhaseStartDate_t, firstPhaseEndDate_t, secondPhaseEndDate_t, thirdPhaseEndDate_t, listingStartDate_t, cliffEndDate_t, vestingEndDate_t; + NOST::packNostromoDate(firstPhaseStartYear, firstPhaseStartMonth, firstPhaseStartDay, firstPhaseStartHour, 0, 0, firstPhaseStartDate_t); + NOST::packNostromoDate(secondPhaseStartYear, secondPhaseStartMonth, secondPhaseStartDay, secondPhaseStartHour, 0, 0, secondPhaseStartDate_t); + NOST::packNostromoDate(thirdPhaseStartYear, thirdPhaseStartMonth, thirdPhaseStartDay, thirdPhaseStartHour, 0, 0, thirdPhaseStartDate_t); + NOST::packNostromoDate(firstPhaseEndYear, firstPhaseEndMonth, firstPhaseEndDay, firstPhaseEndHour, 0, 0, firstPhaseEndDate_t); + NOST::packNostromoDate(secondPhaseEndYear, secondPhaseEndMonth, secondPhaseEndDay, secondPhaseEndHour, 0, 0, secondPhaseEndDate_t); + NOST::packNostromoDate(thirdPhaseEndYear, thirdPhaseEndMonth, thirdPhaseEndDay, thirdPhaseEndHour, 0, 0, thirdPhaseEndDate_t); + NOST::packNostromoDate(listingStartYear, listingStartMonth, listingStartDay, listingStartHour, 0, 0, listingStartDate_t); + NOST::packNostromoDate(cliffEndYear, cliffEndMonth, cliffEndDay, cliffEndHour, 0, 0, cliffEndDate_t); + NOST::packNostromoDate(vestingEndYear, vestingEndMonth, vestingEndDay, vestingEndHour, 0, 0, vestingEndDate_t); + + EXPECT_EQ(registerId, projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).creator); + EXPECT_EQ(tokenPrice, fundaraisings.get(indexOfFundraising).tokenPrice); + + EXPECT_EQ(soldAmount, fundaraisings.get(indexOfFundraising).soldAmount); + EXPECT_EQ(requiredFunds, fundaraisings.get(indexOfFundraising).requiredFunds); + EXPECT_EQ(indexOfProject, fundaraisings.get(indexOfFundraising).indexOfProject); + EXPECT_EQ(firstPhaseStartDate_t, fundaraisings.get(indexOfFundraising).firstPhaseStartDate); + EXPECT_EQ(secondPhaseStartDate_t, fundaraisings.get(indexOfFundraising).secondPhaseStartDate); + EXPECT_EQ(thirdPhaseStartDate_t, fundaraisings.get(indexOfFundraising).thirdPhaseStartDate); + EXPECT_EQ(firstPhaseEndDate_t, fundaraisings.get(indexOfFundraising).firstPhaseEndDate); + EXPECT_EQ(secondPhaseEndDate_t, fundaraisings.get(indexOfFundraising).secondPhaseEndDate); + EXPECT_EQ(thirdPhaseEndDate_t, fundaraisings.get(indexOfFundraising).thirdPhaseEndDate); + EXPECT_EQ(listingStartDate_t, fundaraisings.get(indexOfFundraising).listingStartDate); + EXPECT_EQ(cliffEndDate_t, fundaraisings.get(indexOfFundraising).cliffEndDate); + EXPECT_EQ(vestingEndDate_t, fundaraisings.get(indexOfFundraising).vestingEndDate); + EXPECT_EQ(threshold, fundaraisings.get(indexOfFundraising).threshold); + EXPECT_EQ(TGE, fundaraisings.get(indexOfFundraising).TGE); + EXPECT_EQ(stepOfVesting, fundaraisings.get(indexOfFundraising).stepOfVesting); + + } + uint8 getTierLevel(id registerId) + { + if (users.contains(registerId)) + { + uint8 tierLevel; + users.get(registerId, tierLevel); + return tierLevel; + } + return 0; + } + uint64 getInvestedAmount(uint32 indexOfFundraising, id registerId) + { + investors.get(registerId, tmpInvestedList); + uint32 numberOfProject; + numberOfInvestedProjects.get(registerId, numberOfProject); + + for (uint32 i = 0; i < numberOfProject; i++) + { + if (tmpInvestedList.get(i).indexOfFundraising == indexOfFundraising) + { + return tmpInvestedList.get(i).investedAmount; + } + } + + return 0; + } + uint64 getEpochRevenue() + { + return epochRevenue; + } + void totalRaisedFundChecker(uint32 indexOfFundraising, uint64 raisedFund, uint64 assetName) + { + EXPECT_EQ(raisedFund, fundaraisings.get(indexOfFundraising).raisedFunds); + + if (fundaraisings.get(indexOfFundraising).isCreatedToken) + { + Asset assetInfo; + assetInfo.assetName = assetName; + assetInfo.issuer = id(NOST_CONTRACT_INDEX, 0, 0, 0); + EXPECT_EQ(numberOfShares(assetInfo), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).supplyOfToken); + EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).creator, projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).creator, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).supplyOfToken - fundaraisings.get(indexOfFundraising).soldAmount); + } + } + void endEpochSucceedFundraisingChecker(id creator, uint32 indexOfFundraising, uint64 totalInvestedFund, uint64 originalCreatorBalance, uint64 assetName) + { + EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), creator, creator, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX), projects.get(fundaraisings.get(indexOfFundraising).indexOfProject).supplyOfToken - div(totalInvestedFund, fundaraisings.get(indexOfFundraising).tokenPrice)); + EXPECT_EQ(fundaraisings.get(indexOfFundraising).raisedFunds, 0); + } + void endEpochFailedFundraisingChecker(uint32 indexOfFundraising) + { + EXPECT_EQ(fundaraisings.get(indexOfFundraising).raisedFunds, 0); + } + void endEpochVoteStatusClearChecker() + { + id userId; + uint64 tierLevel; + uint64 idx = users.nextElementIndex(NULL_INDEX); + uint32 numberOfProject; + Array votedList; + while (idx != NULL_INDEX) + { + userId = users.key(idx); + tierLevel = users.value(idx); + + EXPECT_EQ(voteStatus.get(userId, votedList), 0); + EXPECT_EQ(numberOfVotedProject.get(userId, numberOfProject), 0); + + idx = users.nextElementIndex(idx); + } + } + void getStatsChecker(uint64 epochRevenu_t, uint64 totalPoolWeight_t, uint32 numberOfCreatedProject_t, uint32 numberOfFundraising_t, uint32 numberOfRegister_t) + { + EXPECT_EQ(epochRevenu_t, epochRevenue); + EXPECT_EQ(totalPoolWeight_t, totalPoolWeight); + EXPECT_EQ(numberOfCreatedProject_t, numberOfCreatedProject); + EXPECT_EQ(numberOfFundraising_t, numberOfFundraising); + EXPECT_EQ(numberOfRegister_t, numberOfRegister); + } + void removeElementAfterClaimChecker(id user) + { + uint32 tp; + EXPECT_EQ(investors.get(user, tmpInvestedList), 0); + EXPECT_EQ(numberOfInvestedProjects.get(user, tp), 0); + } +}; + +class ContractTestingNostromo : protected ContractTesting +{ +public: + ContractTestingNostromo() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(NOST); + callSystemProcedure(NOST_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QUOTTERY); + callSystemProcedure(QUOTTERY_CONTRACT_INDEX, INITIALIZE); + } + NostromoChecker* getState() + { + return (NostromoChecker*)contractStates[NOST_CONTRACT_INDEX]; + } + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(NOST_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + void registerInTier(const id& registerId, + uint32 tierLevel, + uint64 depositeAmount) + { + NOST::registerInTier_input input; + NOST::registerInTier_output output; + + input.tierLevel = tierLevel; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 1, input, output, registerId, depositeAmount); + } + void logoutFromTier(const id& registerId) + { + NOST::logoutFromTier_input input; + NOST::logoutFromTier_output output; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 2, input, output, registerId, 0); + } + void createProject(const id& registerId, + uint64 tokenName, + uint64 supply, + uint32 startYear, + uint32 startMonth, + uint32 startDay, + uint32 startHour, + uint32 endYear, + uint32 endMonth, + uint32 endDay, + uint32 endHour) + { + NOST::createProject_input input; + NOST::createProject_output output; + + input.tokenName = tokenName; + input.supply = supply; + input.startYear = startYear; + input.startMonth = startMonth; + input.startDay = startDay; + input.startHour = startHour; + input.endYear = endYear; + input.endMonth = endMonth; + input.endDay = endDay; + input.endHour = endHour; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 3, input, output, registerId, NOSTROMO_CREATE_PROJECT_FEE); + } + void voteInProject(const id& registerId, + uint32 indexOfProject, + bit decision) + { + NOST::voteInProject_input input; + NOST::voteInProject_output output; + + input.decision = decision; + input.indexOfProject = indexOfProject; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 4, input, output, registerId, 0); + } + void createFundraising(const id& registerId, + uint64 tokenPrice, + uint64 soldAmount, + uint64 requiredFunds, + + uint32 indexOfProject, + uint32 firstPhaseStartYear, + uint32 firstPhaseStartMonth, + uint32 firstPhaseStartDay, + uint32 firstPhaseStartHour, + uint32 firstPhaseEndYear, + uint32 firstPhaseEndMonth, + uint32 firstPhaseEndDay, + uint32 firstPhaseEndHour, + + uint32 secondPhaseStartYear, + uint32 secondPhaseStartMonth, + uint32 secondPhaseStartDay, + uint32 secondPhaseStartHour, + uint32 secondPhaseEndYear, + uint32 secondPhaseEndMonth, + uint32 secondPhaseEndDay, + uint32 secondPhaseEndHour, + + uint32 thirdPhaseStartYear, + uint32 thirdPhaseStartMonth, + uint32 thirdPhaseStartDay, + uint32 thirdPhaseStartHour, + uint32 thirdPhaseEndYear, + uint32 thirdPhaseEndMonth, + uint32 thirdPhaseEndDay, + uint32 thirdPhaseEndHour, + + uint32 listingStartYear, + uint32 listingStartMonth, + uint32 listingStartDay, + uint32 listingStartHour, + + uint32 cliffEndYear, + uint32 cliffEndMonth, + uint32 cliffEndDay, + uint32 cliffEndHour, + + uint32 vestingEndYear, + uint32 vestingEndMonth, + uint32 vestingEndDay, + uint32 vestingEndHour, + + uint8 threshold, + uint8 TGE, + uint8 stepOfVesting) + { + NOST::createFundraising_input input; + NOST::createFundraising_output output; + + input.tokenPrice = tokenPrice; + input.soldAmount = soldAmount; + input.requiredFunds = requiredFunds; + + input.indexOfProject = indexOfProject; + input.firstPhaseStartYear = firstPhaseStartYear; + input.firstPhaseStartMonth = firstPhaseStartMonth; + input.firstPhaseStartDay = firstPhaseStartDay; + input.firstPhaseStartHour = firstPhaseStartHour; + input.firstPhaseEndYear = firstPhaseEndYear; + input.firstPhaseEndMonth = firstPhaseEndMonth; + input.firstPhaseEndDay = firstPhaseEndDay; + input.firstPhaseEndHour = firstPhaseEndHour; + + input.secondPhaseStartYear = secondPhaseStartYear; + input.secondPhaseStartMonth = secondPhaseStartMonth; + input.secondPhaseStartDay = secondPhaseStartDay; + input.secondPhaseStartHour = secondPhaseStartHour; + input.secondPhaseEndYear = secondPhaseEndYear; + input.secondPhaseEndMonth = secondPhaseEndMonth; + input.secondPhaseEndDay = secondPhaseEndDay; + input.secondPhaseEndHour = secondPhaseEndHour; + + input.thirdPhaseStartYear = thirdPhaseStartYear; + input.thirdPhaseStartMonth = thirdPhaseStartMonth; + input.thirdPhaseStartDay = thirdPhaseStartDay; + input.thirdPhaseStartHour = thirdPhaseStartHour; + input.thirdPhaseEndYear = thirdPhaseEndYear; + input.thirdPhaseEndMonth = thirdPhaseEndMonth; + input.thirdPhaseEndDay = thirdPhaseEndDay; + input.thirdPhaseEndHour = thirdPhaseEndHour; + + input.listingStartYear = listingStartYear; + input.listingStartMonth = listingStartMonth; + input.listingStartDay = listingStartDay; + input.listingStartHour = listingStartHour; + + input.cliffEndYear = cliffEndYear; + input.cliffEndMonth = cliffEndMonth; + input.cliffEndDay = cliffEndDay; + input.cliffEndHour = cliffEndHour; + + input.vestingEndYear = vestingEndYear; + input.vestingEndMonth = vestingEndMonth; + input.vestingEndDay = vestingEndDay; + input.vestingEndHour = vestingEndHour; + + input.threshold = threshold; + input.TGE = TGE; + input.stepOfVesting = stepOfVesting; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 5, input, output, registerId, NOSTROMO_QX_TOKEN_ISSUANCE_FEE); + } + void investInProject(const id& investorId, + uint32 indexOfFundraising, + uint64 investmentAmount) + { + NOST::investInProject_input input; + NOST::investInProject_output output; + + input.indexOfFundraising = indexOfFundraising; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 6, input, output, investorId, investmentAmount); + } + uint64 claimToken(const id& claimerId, + uint64 claimAmount, + uint32 indexOfFundraising) + { + NOST::claimToken_input input; + NOST::claimToken_output output; + + input.amount = claimAmount; + input.indexOfFundraising = indexOfFundraising; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 7, input, output, claimerId, 0); + return output.claimedAmount; + } + void upgradeTier(const id& registerId, + uint32 newTierLevel, + uint64 depositAmount) + { + NOST::upgradeTier_input input; + NOST::upgradeTier_output output; + + input.newTierLevel = newTierLevel; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 8, input, output, registerId, depositAmount); + } + sint64 TransferShareManagementRights(const id& user, Asset asset, sint64 numberOfShares, uint32 newManagingContractIndex) + { + NOST::TransferShareManagementRights_input input; + NOST::TransferShareManagementRights_output output; + + input.asset = asset; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(NOST_CONTRACT_INDEX, 9, input, output, user, 100); + + return output.transferredNumberOfShares; + } + NOST::getStats_output getStats() const + { + NOST::getStats_input input; + NOST::getStats_output output; + + callFunction(NOST_CONTRACT_INDEX, 1, input, output); + return output; + } + NOST::getTierLevelByUser_output getTierLevelByUser(const id& registerId) const + { + NOST::getTierLevelByUser_input input; + NOST::getTierLevelByUser_output output; + + input.userId = registerId; + callFunction(NOST_CONTRACT_INDEX, 2, input, output); + return output; + } + NOST::getUserVoteStatus_output getUserVoteStatus(const id& registerId) const + { + NOST::getUserVoteStatus_input input; + NOST::getUserVoteStatus_output output; + + input.userId = registerId; + callFunction(NOST_CONTRACT_INDEX, 3, input, output); + return output; + } + NOST::checkTokenCreatability_output checkTokenCreatability(uint64 tokenName) const + { + NOST::checkTokenCreatability_input input; + NOST::checkTokenCreatability_output output; + + input.tokenName = tokenName; + callFunction(NOST_CONTRACT_INDEX, 4, input, output); + return output; + } + NOST::getNumberOfInvestedProjects_output getNumberOfInvestedProjects(const id& invsetorId) const + { + NOST::getNumberOfInvestedProjects_input input; + NOST::getNumberOfInvestedProjects_output output; + + input.userId = invsetorId; + callFunction(NOST_CONTRACT_INDEX, 5, input, output); + return output; + } + NOST::getProjectByIndex_output getProjectByIndex(uint32 indexOfProject) const + { + NOST::getProjectByIndex_input input; + NOST::getProjectByIndex_output output; + + input.indexOfProject = indexOfProject; + callFunction(NOST_CONTRACT_INDEX, 6, input, output); + return output; + } + NOST::getFundarasingByIndex_output getFundarasingByIndex(uint32 indexOfFundraising) const + { + NOST::getFundarasingByIndex_input input; + NOST::getFundarasingByIndex_output output; + + input.indexOfFundarasing = indexOfFundraising; + callFunction(NOST_CONTRACT_INDEX, 7, input, output); + return output; + } + NOST::getProjectIndexListByCreator_output getProjectIndexListByCreator(const id& creatorId) const + { + NOST::getProjectIndexListByCreator_input input; + NOST::getProjectIndexListByCreator_output output; + + input.creator = creatorId; + callFunction(NOST_CONTRACT_INDEX, 8, input, output); + return output; + } + NOST::getInfoUserInvested_output getInfoUserInvested(const id& investorId) const + { + NOST::getInfoUserInvested_input input; + NOST::getInfoUserInvested_output output; + + input.investorId = investorId; + callFunction(NOST_CONTRACT_INDEX, 9, input, output); + return output; + } + uint64 getMaxClaimAmount(const id& investorId, uint32 indexOfFundraising) const + { + NOST::getMaxClaimAmount_input input; + NOST::getMaxClaimAmount_output output; + + input.investorId = investorId; + input.indexOfFundraising = indexOfFundraising; + callFunction(NOST_CONTRACT_INDEX, 10, input, output); + return output.amount; + } +}; + +TEST(TestContractNostromo, registerAndLogoutAndUpgradeFromTierChecker) +{ + ContractTestingNostromo nostromoTestCaseA; + + std::map duplicatedUser; + auto registers = getRandomUsers(10000, 10000); + + uint32 countOfRegister = 0, totalPoolWeight = 0; + uint64 totalDepositedQubic = 0, totalLogoutFeeAmount = 0; + + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + uint32 tierLevel = (uint32)random(1, 5); + uint64 depositeAmount, upgradeDeltaDepositeAmount; + switch (tierLevel) + { + case 1: + depositeAmount = NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT; + upgradeDeltaDepositeAmount = NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT - NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT; + totalLogoutFeeAmount += NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT * NOSTROMO_TIER_CHESTBURST_UNSTAKE_FEE / 100; + totalPoolWeight += NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT; + break; + case 2: + depositeAmount = NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT; + upgradeDeltaDepositeAmount = NOSTROMO_TIER_DOG_STAKE_AMOUNT - NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT; + totalLogoutFeeAmount += NOSTROMO_TIER_DOG_STAKE_AMOUNT * NOSTROMO_TIER_DOG_UNSTAKE_FEE / 100; + totalPoolWeight += NOSTROMO_TIER_DOG_POOL_WEIGHT; + break; + case 3: + depositeAmount = NOSTROMO_TIER_DOG_STAKE_AMOUNT; + upgradeDeltaDepositeAmount = NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT - NOSTROMO_TIER_DOG_STAKE_AMOUNT; + totalLogoutFeeAmount += NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT * NOSTROMO_TIER_XENOMORPH_UNSTAKE_FEE / 100; + totalPoolWeight += NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT; + break; + case 4: + depositeAmount = NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT; + upgradeDeltaDepositeAmount = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT - NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT; + totalLogoutFeeAmount += NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT * NOSTROMO_TIER_WARRIOR_UNSTAKE_FEE / 100; + totalPoolWeight += NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; + break; + case 5: + depositeAmount = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT; + totalLogoutFeeAmount += NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT * NOSTROMO_TIER_WARRIOR_UNSTAKE_FEE / 100; + totalPoolWeight += NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; + break; + default: + break; + } + // Register Tier + totalDepositedQubic += depositeAmount; + increaseEnergy(user, depositeAmount); + nostromoTestCaseA.registerInTier(user, tierLevel, depositeAmount); + nostromoTestCaseA.getState()->registerChecker(user, tierLevel, countOfRegister); + // Upgrade Tier + totalDepositedQubic += upgradeDeltaDepositeAmount; + increaseEnergy(user, upgradeDeltaDepositeAmount); + nostromoTestCaseA.upgradeTier(user, tierLevel + 1, upgradeDeltaDepositeAmount); + + if (tierLevel == 5) + { + nostromoTestCaseA.getState()->registerChecker(user, tierLevel, countOfRegister); + } + else + { + nostromoTestCaseA.getState()->registerChecker(user, tierLevel + 1, countOfRegister); + } + + duplicatedUser[user] = 1; + countOfRegister++; + } + nostromoTestCaseA.getState()->countOfRegisterChecker(countOfRegister); + nostromoTestCaseA.getState()->epochRevenueChecker(0); + nostromoTestCaseA.getState()->totalPoolWeightChecker(totalPoolWeight); + EXPECT_EQ(totalDepositedQubic, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + // Logout From Tier + nostromoTestCaseA.logoutFromTier(user); + duplicatedUser[user] = 1; + nostromoTestCaseA.getState()->logoutFromTierChecker(user); + } + EXPECT_EQ(totalLogoutFeeAmount, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + nostromoTestCaseA.getState()->countOfRegisterChecker(0); + nostromoTestCaseA.getState()->epochRevenueChecker(totalLogoutFeeAmount); + nostromoTestCaseA.getState()->totalPoolWeightChecker(0); +} + +TEST(TestContractNostromo, createProjectAndVoteInProjectChecker) +{ + ContractTestingNostromo nostromoTestCaseB; + + auto registers = getRandomUsers(1000, 1000); + + // Register in each Tiers + increaseEnergy(registers[0], NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); + nostromoTestCaseB.registerInTier(registers[0], 1, NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT); + + increaseEnergy(registers[1], NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); + nostromoTestCaseB.registerInTier(registers[1], 2, NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT); + + increaseEnergy(registers[2], NOSTROMO_TIER_DOG_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); + nostromoTestCaseB.registerInTier(registers[2], 3, NOSTROMO_TIER_DOG_STAKE_AMOUNT); + + increaseEnergy(registers[3], NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); + nostromoTestCaseB.registerInTier(registers[3], 4, NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT); + + increaseEnergy(registers[4], NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE); + nostromoTestCaseB.registerInTier(registers[4], 5, NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT); + + setMemory(utcTime, 0); + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 12; + utcTime.Hour = 0; + updateQpiTime(); + + uint64 assetName = assetNameFromString("AAAA"); + + // This creation should be failed because there is no qualified to create the project. + nostromoTestCaseB.createProject(registers[0], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(0); + nostromoTestCaseB.getState()->epochRevenueChecker(0); + EXPECT_EQ(getBalance(registers[0]), NOSTROMO_CREATE_PROJECT_FEE); + + // This creation should be failed because there is no qualified to create the project. + assetName = assetNameFromString("BBBB"); + nostromoTestCaseB.createProject(registers[1], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(0); + nostromoTestCaseB.getState()->epochRevenueChecker(0); + EXPECT_EQ(getBalance(registers[1]), NOSTROMO_CREATE_PROJECT_FEE); + + // This creation should be failed because there is no qualified to create the project. + assetName = assetNameFromString("CCCC"); + nostromoTestCaseB.createProject(registers[2], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(0); + nostromoTestCaseB.getState()->epochRevenueChecker(0); + EXPECT_EQ(getBalance(registers[2]), NOSTROMO_CREATE_PROJECT_FEE); + + + //This creation should be succeed because there is a qualified to create the project. + assetName = assetNameFromString("DDDD"); + nostromoTestCaseB.createProject(registers[3], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(1); + nostromoTestCaseB.getState()->epochRevenueChecker(NOSTROMO_CREATE_PROJECT_FEE); + nostromoTestCaseB.getState()->createdProjectChecker(0, registers[3], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + EXPECT_EQ(getBalance(registers[3]), 0); + + // This creation should be succeed because there is a qualified to create the project. + assetName = assetNameFromString("EEEE"); + nostromoTestCaseB.createProject(registers[4], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + nostromoTestCaseB.getState()->numberOfCreatedProjectChecker(2); + nostromoTestCaseB.getState()->epochRevenueChecker(NOSTROMO_CREATE_PROJECT_FEE * 2); + nostromoTestCaseB.getState()->createdProjectChecker(1, registers[4], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + EXPECT_EQ(getBalance(registers[4]), 0); + + // checkTokenCreatability function checker + EXPECT_EQ(nostromoTestCaseB.checkTokenCreatability(assetName).result, 1); + assetName = assetNameFromString("ABCD"); + EXPECT_EQ(nostromoTestCaseB.checkTokenCreatability(assetName).result, 0); + + setMemory(utcTime, 0); + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 13; + utcTime.Hour = 0; + updateQpiTime(); + + Array votedList; + + nostromoTestCaseB.voteInProject(registers[0], 0, 0); + votedList.set(0, 0); + nostromoTestCaseB.voteInProject(registers[1], 0, 1); + nostromoTestCaseB.voteInProject(registers[2], 0, 1); + nostromoTestCaseB.voteInProject(registers[3], 0, 1); + nostromoTestCaseB.voteInProject(registers[4], 0, 0); + + nostromoTestCaseB.getState()->voteInProjectChecker(0, 3, 2); + nostromoTestCaseB.getState()->numberOfVotedProjectAndVotedListChecker(registers[0], 1, votedList); + + // This vote should be failed. + nostromoTestCaseB.voteInProject(registers[0], 0, 0); + nostromoTestCaseB.getState()->voteInProjectChecker(0, 3, 2); + nostromoTestCaseB.getState()->numberOfVotedProjectAndVotedListChecker(registers[0], 1, votedList); + + // This vote should be succeed. + nostromoTestCaseB.voteInProject(registers[0], 1, 0); + votedList.set(1, 1); + nostromoTestCaseB.getState()->voteInProjectChecker(1, 0, 1); + nostromoTestCaseB.getState()->numberOfVotedProjectAndVotedListChecker(registers[0], 2, votedList); + + nostromoTestCaseB.voteInProject(registers[1], 1, 1); + nostromoTestCaseB.voteInProject(registers[2], 1, 1); + nostromoTestCaseB.voteInProject(registers[3], 1, 1); + nostromoTestCaseB.voteInProject(registers[4], 1, 1); + nostromoTestCaseB.getState()->voteInProjectChecker(1, 4, 1); +} + +TEST(TestContractNostromo, createFundraisingAndInvestInProjectAndClaimTokenChecker) +{ + uint64 epochRevenu_t = 0; + uint32 numberOfCreatedProject_t = 0; + uint32 numberOfFundraising_t = 0;; + + ContractTestingNostromo nostromoTestCaseC; + + auto registers = getRandomUsers(10000, 10000); + + setMemory(utcTime, 0); + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 11; + utcTime.Hour = 0; + updateQpiTime(); + + increaseEnergy(registers[0], NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT + NOSTROMO_CREATE_PROJECT_FEE + NOSTROMO_QX_TOKEN_ISSUANCE_FEE); + nostromoTestCaseC.registerInTier(registers[0], 5, NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT); + uint64 assetName = assetNameFromString("GGGG"); + nostromoTestCaseC.createProject(registers[0], assetName, 21000000, 25, 6, 13, 0, 25, 6, 15, 0); + + // getProjectByIndex function Checker + NOST::getProjectByIndex_output getProjectByIndex_output = nostromoTestCaseC.getProjectByIndex(0); + + EXPECT_EQ(getProjectByIndex_output.project.creator, registers[0]); + uint32 tmpDate; + NOST::packNostromoDate(25, 6, 15, 0, 0, 0, tmpDate); + EXPECT_EQ(getProjectByIndex_output.project.endDate , tmpDate); + EXPECT_EQ(getProjectByIndex_output.project.isCreatedFundarasing , 0); + EXPECT_EQ(getProjectByIndex_output.project.numberOfNo, 0); + EXPECT_EQ(getProjectByIndex_output.project.numberOfYes, 0); + NOST::packNostromoDate(25, 6, 13, 0, 0, 0, tmpDate); + EXPECT_EQ(getProjectByIndex_output.project.startDate, tmpDate); + EXPECT_EQ(getProjectByIndex_output.project.supplyOfToken, 21000000); + EXPECT_EQ(getProjectByIndex_output.project.tokenName, assetName); + + numberOfCreatedProject_t++; + epochRevenu_t += 100000000; + + std::map duplicatedUser; + uint64 totalPoolWeight = NOSTROMO_TIER_WARRIOR_POOL_WEIGHT, totalDepositedQubic = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT; + uint32 countOfRegister = 0; + + for (const auto& user : registers) + { + if (countOfRegister == 0) + { + countOfRegister++; + continue; + } + + if (duplicatedUser[user]) + { + continue; + } + uint8 tierLevel = (uint8)random(1, 5); + uint64 depositeAmount, userPoolWeight; + switch (tierLevel) + { + case 1: + depositeAmount = NOSTROMO_TIER_FACEHUGGER_STAKE_AMOUNT; + totalPoolWeight += NOSTROMO_TIER_FACEHUGGER_POOL_WEIGHT; + userPoolWeight = NOSTROMO_TIER_FACEHUGGER_POOL_WEIGHT; + break; + case 2: + depositeAmount = NOSTROMO_TIER_CHESTBURST_STAKE_AMOUNT; + totalPoolWeight += NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT; + userPoolWeight = NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT; + break; + case 3: + depositeAmount = NOSTROMO_TIER_DOG_STAKE_AMOUNT; + totalPoolWeight += NOSTROMO_TIER_DOG_POOL_WEIGHT; + userPoolWeight = NOSTROMO_TIER_DOG_POOL_WEIGHT; + break; + case 4: + depositeAmount = NOSTROMO_TIER_XENOMORPH_STAKE_AMOUNT; + totalPoolWeight += NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT; + userPoolWeight = NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT; + break; + case 5: + depositeAmount = NOSTROMO_TIER_WARRIOR_STAKE_AMOUNT; + totalPoolWeight += NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; + userPoolWeight = NOSTROMO_TIER_WARRIOR_POOL_WEIGHT; + break; + default: + break; + } + + // Register Tier + totalDepositedQubic += depositeAmount; + increaseEnergy(user, depositeAmount); + nostromoTestCaseC.registerInTier(user, tierLevel, depositeAmount); + + duplicatedUser[user] = 1; + countOfRegister++; + + // getTierLevelByUser function Checker + EXPECT_EQ(nostromoTestCaseC.getTierLevelByUser(user).tierLevel, tierLevel); + } + + // Vote in Project + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 14; + utcTime.Hour = 0; + updateQpiTime(); + + uint32 Ynumber = 0, Nnumber = 0; + duplicatedUser.clear(); + + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + bit decision = (bit)random(0, 3); + if (decision) + { + Ynumber++; + } + else + { + Nnumber++; + } + + nostromoTestCaseC.voteInProject(user, 0, decision); + duplicatedUser[user] = 1; + } + nostromoTestCaseC.getState()->voteInProjectChecker(0, Ynumber, Nnumber); + + // Create the Fundraising + // This fundraising should not be created because the voting is not finished yet. + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 14; + utcTime.Hour = 0; + updateQpiTime(); + + nostromoTestCaseC.createFundraising(registers[0], 100, 2000000, 150000000, 0, + 25, 6, 17, 0, + 25, 6, 25, 0, + 25, 6, 28, 0, + 25, 7, 1, 0, + 25, 7, 10, 0, + 25, 7, 15, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12); + + nostromoTestCaseC.getState()->countOfFundraisingChecker(0); + + // It should be created. + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 16; + utcTime.Hour = 0; + updateQpiTime(); + + nostromoTestCaseC.createFundraising(registers[0], 100000, 2000000, 150000000000, 0, + 25, 6, 17, 0, + 25, 6, 25, 0, + 25, 6, 28, 0, + 25, 7, 1, 0, + 25, 7, 10, 0, + 25, 7, 15, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12); + numberOfFundraising_t++; + + nostromoTestCaseC.getState()->countOfFundraisingChecker(1); + nostromoTestCaseC.getState()->createFundraisingChecker(registers[0], 100000, 2000000, 150000000000, 0, + 25, 6, 17, 0, + 25, 6, 25, 0, + 25, 6, 28, 0, + 25, 7, 1, 0, + 25, 7, 10, 0, + 25, 7, 15, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12, 0); + + // getFundarasingByIndex function checker + NOST::getFundarasingByIndex_output getFundarasingByIndex_output = nostromoTestCaseC.getFundarasingByIndex(0); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.indexOfProject, 0); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.isCreatedToken, 0); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.raisedFunds, 0); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.requiredFunds, 150000000000); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.soldAmount, 2000000); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.stepOfVesting, 12); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.TGE, 10); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.threshold, 20); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.tokenPrice, 100000); + NOST::packNostromoDate(25, 6, 17, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.firstPhaseStartDate, tmpDate); + NOST::packNostromoDate(25, 6, 25, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.firstPhaseEndDate, tmpDate); + NOST::packNostromoDate(25, 6, 28, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.secondPhaseStartDate, tmpDate); + NOST::packNostromoDate(25, 7, 1, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.secondPhaseEndDate, tmpDate); + NOST::packNostromoDate(25, 7, 10, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.thirdPhaseStartDate, tmpDate); + NOST::packNostromoDate(25, 7, 15, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.thirdPhaseEndDate, tmpDate); + NOST::packNostromoDate(25, 7, 25, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.listingStartDate, tmpDate); + NOST::packNostromoDate(25, 7, 27, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.cliffEndDate, tmpDate); + NOST::packNostromoDate(26, 7, 27, 0, 0, 0, tmpDate); + EXPECT_EQ(getFundarasingByIndex_output.fundarasing.vestingEndDate, tmpDate); + + // Phase 1 Investment + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 17; + utcTime.Hour = 1; + updateQpiTime(); + + uint64 facehuggerMaxInvestAmount = 180000000000 * NOSTROMO_TIER_FACEHUGGER_POOL_WEIGHT / totalPoolWeight; + uint64 chestburstMaxInvestAmount = 180000000000 * NOSTROMO_TIER_CHESTBURST_POOL_WEIGHT / totalPoolWeight; + uint64 dogMaxInvestAmount = 180000000000 * NOSTROMO_TIER_DOG_POOL_WEIGHT / totalPoolWeight; + uint64 xenomorphMaxInvestAmount = 180000000000 * NOSTROMO_TIER_XENOMORPH_POOL_WEIGHT / totalPoolWeight; + uint64 warriorMaxInvestAmount = 180000000000 * NOSTROMO_TIER_WARRIOR_POOL_WEIGHT / totalPoolWeight; + + uint64 totalInvestedAmount = 0; + duplicatedUser.clear(); + uint32 ct = 0; + uint32 overDeposit = 1000; // it should be ignored + uint64 originalSCBalance = getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0)); + + std::map investedAmountMP; + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + ct++; + continue; + } + ct++; + increaseEnergy(user, 180000000000); + uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); + + if (ct >= 4000) + { + // Phase 2 Investment + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 29; + utcTime.Hour = 0; + updateQpiTime(); + } + + switch (tierLevel) + { + case 1: + if (ct < 4000) + { + totalInvestedAmount += facehuggerMaxInvestAmount; + investedAmountMP[user] += facehuggerMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 0, facehuggerMaxInvestAmount + overDeposit); + break; + case 2: + if (ct < 4000) + { + totalInvestedAmount += chestburstMaxInvestAmount; + investedAmountMP[user] += chestburstMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 0, chestburstMaxInvestAmount + overDeposit); + break; + case 3: + if (ct < 4000) + { + totalInvestedAmount += dogMaxInvestAmount; + investedAmountMP[user] += dogMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 0, dogMaxInvestAmount + overDeposit); + break; + case 4: + totalInvestedAmount += xenomorphMaxInvestAmount; + investedAmountMP[user] += xenomorphMaxInvestAmount; + nostromoTestCaseC.investInProject(user, 0, xenomorphMaxInvestAmount + overDeposit); + break; + case 5: + totalInvestedAmount += warriorMaxInvestAmount; + investedAmountMP[user] += warriorMaxInvestAmount; + nostromoTestCaseC.investInProject(user, 0, warriorMaxInvestAmount + overDeposit); + break; + + default: + break; + } + + duplicatedUser[user] = 1; + } + + nostromoTestCaseC.getState()->totalRaisedFundChecker(0, totalInvestedAmount, assetName); + EXPECT_EQ(originalSCBalance + totalInvestedAmount - NOSTROMO_QX_TOKEN_ISSUANCE_FEE, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + + // Phase 3 Investment + utcTime.Year = 2025; + utcTime.Month = 7; + utcTime.Day = 11; + utcTime.Hour = 0; + updateQpiTime(); + + uint64 amount = 10000000; + duplicatedUser.clear(); + ct = 0; + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + ct++; + uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); + increaseEnergy(user, amount); + nostromoTestCaseC.investInProject(user, 0, amount); + if (totalInvestedAmount + amount < 180000000000) + { + totalInvestedAmount += amount; + investedAmountMP[user] += amount; + + // getNumberOfInvestedProjects function checker + NOST::getNumberOfInvestedProjects_output getNumberOfInvestedProjects_output = nostromoTestCaseC.getNumberOfInvestedProjects(user); + + EXPECT_EQ(getNumberOfInvestedProjects_output.numberOfInvestedProjects, 1); + } + duplicatedUser[user] = 1; + } + + nostromoTestCaseC.getState()->totalRaisedFundChecker(0, totalInvestedAmount, assetName); + EXPECT_EQ(originalSCBalance + totalInvestedAmount - NOSTROMO_QX_TOKEN_ISSUANCE_FEE, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + + // getMaxClaimAmount function checker + utcTime.Year = 2025; + utcTime.Month = 7; + utcTime.Day = 26; + utcTime.Hour = 0; + updateQpiTime(); + + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + EXPECT_EQ(nostromoTestCaseC.getMaxClaimAmount(user, 0), investedAmountMP[user] / 100000 * 10 / 100); + + duplicatedUser[user] = 1; + } + + utcTime.Year = 2025; + utcTime.Month = 8; + utcTime.Day = 5; + utcTime.Hour = 0; + updateQpiTime(); + + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + EXPECT_EQ(nostromoTestCaseC.getMaxClaimAmount(user, 0), investedAmountMP[user] / 100000 * (10 + 7) / 100); + + duplicatedUser[user] = 1; + } + + utcTime.Year = 2026; + utcTime.Month = 8; + utcTime.Day = 5; + utcTime.Hour = 0; + updateQpiTime(); + + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + EXPECT_EQ(nostromoTestCaseC.getMaxClaimAmount(user, 0), investedAmountMP[user] / 100000); + + duplicatedUser[user] = 1; + } + + // claimToken Checker + std::map claimedAmountMP; + for (uint32 i = 1; i <= 12; i++) + { + if (i >= 6) + { + utcTime.Year = 2026; + } + utcTime.Month = (7 + i) % 12; + if (utcTime.Month == 0) utcTime.Month = 12; + utcTime.Day = 5; + utcTime.Hour = 0; + updateQpiTime(); + + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + uint64 investedAmount = nostromoTestCaseC.getState()->getInvestedAmount(0, user); + uint64 claimAmount = investedAmount / 100000 / 12; + claimedAmountMP[user] += nostromoTestCaseC.claimToken(user, claimAmount, 0); + + duplicatedUser[user] = 1; + } + } + + // getInfoUserInvested function checker + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + duplicatedUser[user] = 1; + + NOST::getInfoUserInvested_output getInfoUserInvested_output = nostromoTestCaseC.getInfoUserInvested(user); + EXPECT_EQ(getInfoUserInvested_output.listUserInvested.get(0).indexOfFundraising, 0); + EXPECT_EQ(getInfoUserInvested_output.listUserInvested.get(0).investedAmount, investedAmountMP[user]); + EXPECT_EQ(getInfoUserInvested_output.listUserInvested.get(0).claimedAmount, claimedAmountMP[user]); + } + + // Checking to remove element after claiming the max amount + utcTime.Year = 2026; + utcTime.Month = 8; + utcTime.Day = 5; + utcTime.Hour = 0; + updateQpiTime(); + + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + uint64 claimAmount = nostromoTestCaseC.getMaxClaimAmount(user, 0) - claimedAmountMP[user]; + claimedAmountMP[user] += nostromoTestCaseC.claimToken(user, claimAmount, 0); + + duplicatedUser[user] = 1; + + nostromoTestCaseC.getState()->removeElementAfterClaimChecker(user); + } + + ct = 0; + duplicatedUser.clear(); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + if (ct == 0) + { + EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), user, user, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX) - 19000000, claimedAmountMP[user]); + } + else + { + EXPECT_EQ(numberOfPossessedShares(assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), user, user, NOST_CONTRACT_INDEX, NOST_CONTRACT_INDEX), claimedAmountMP[user]); + } + ct++; + duplicatedUser[user] = 1; + } + + // transferShareManagementRights Checker + increaseEnergy(registers[0], 1000000); + + Asset asset; + asset.assetName = assetName; + asset.issuer = id(NOST_CONTRACT_INDEX, 0, 0, 0); + EXPECT_EQ(nostromoTestCaseC.TransferShareManagementRights(registers[0], asset, 10000, QX_CONTRACT_INDEX), 10000); + EXPECT_EQ(numberOfPossessedShares(asset.assetName, id(NOST_CONTRACT_INDEX, 0, 0, 0), registers[0], registers[0], QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 10000); + + // EndEpochSucceedFundraising Checker + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 20; + utcTime.Hour = 0; + updateQpiTime(); + + increaseEnergy(registers[0], NOSTROMO_CREATE_PROJECT_FEE); + assetName = assetNameFromString("AAAA"); + nostromoTestCaseC.createProject(registers[0], assetName, 21000000, 25, 6, 22, 0, 25, 6, 25, 0); + numberOfCreatedProject_t++; + epochRevenu_t += 100000000; + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 23; + utcTime.Hour = 0; + updateQpiTime(); + + Ynumber = 0; Nnumber = 0; + duplicatedUser.clear(); + + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + bit decision = (bit)random(0, 3); + if (decision) + { + Ynumber++; + } + else + { + Nnumber++; + } + + nostromoTestCaseC.voteInProject(user, 1, decision); + duplicatedUser[user] = 1; + + // getUserVoteStatus function Checker + NOST::getUserVoteStatus_output getUserVoteStatus_output = nostromoTestCaseC.getUserVoteStatus(user); + EXPECT_EQ(getUserVoteStatus_output.numberOfVotedProjects, 2); + EXPECT_EQ(getUserVoteStatus_output.projectIndexList.get(0), 0); + EXPECT_EQ(getUserVoteStatus_output.projectIndexList.get(1), 1); + } + nostromoTestCaseC.getState()->voteInProjectChecker(1, Ynumber, Nnumber); + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 26; + utcTime.Hour = 0; + updateQpiTime(); + increaseEnergy(registers[0], NOSTROMO_QX_TOKEN_ISSUANCE_FEE); + + nostromoTestCaseC.createFundraising(registers[0], 100000, 2000000, 150000000000, 1, + 25, 6, 27, 0, + 25, 7, 5, 0, + 25, 7, 8, 0, + 25, 7, 10, 0, + 25, 7, 20, 0, + 25, 7, 23, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12); + numberOfFundraising_t++; + + nostromoTestCaseC.getState()->countOfFundraisingChecker(2); + nostromoTestCaseC.getState()->createFundraisingChecker(registers[0], 100000, 2000000, 150000000000, 1, + 25, 6, 27, 0, + 25, 7, 5, 0, + 25, 7, 8, 0, + 25, 7, 10, 0, + 25, 7, 20, 0, + 25, 7, 23, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12, 1); + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 27; + utcTime.Hour = 1; + updateQpiTime(); + + uint64 totalInvestedAmount_2 = 0; + duplicatedUser.clear(); + ct = 0; + originalSCBalance = getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0)); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + ct++; + continue; + } + ct++; + increaseEnergy(user, 180000000000); + uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); + + if (ct >= 4000) + { + + // Phase 2 Investment + utcTime.Year = 2025; + utcTime.Month = 7; + utcTime.Day = 9; + utcTime.Hour = 0; + updateQpiTime(); + } + + switch (tierLevel) + { + case 1: + if (ct < 4000) + { + totalInvestedAmount_2 += facehuggerMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 1, facehuggerMaxInvestAmount); + break; + case 2: + if (ct < 4000) + { + totalInvestedAmount_2 += chestburstMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 1, chestburstMaxInvestAmount); + break; + case 3: + if (ct < 4000) + { + totalInvestedAmount_2 += dogMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 1, dogMaxInvestAmount); + break; + case 4: + totalInvestedAmount_2 += xenomorphMaxInvestAmount; + nostromoTestCaseC.investInProject(user, 1, xenomorphMaxInvestAmount); + break; + case 5: + totalInvestedAmount_2 += warriorMaxInvestAmount; + nostromoTestCaseC.investInProject(user, 1, warriorMaxInvestAmount); + break; + + default: + break; + } + + duplicatedUser[user] = 1; + } + + nostromoTestCaseC.getState()->totalRaisedFundChecker(1, totalInvestedAmount_2, assetName); + EXPECT_EQ(originalSCBalance + totalInvestedAmount_2 - NOSTROMO_QX_TOKEN_ISSUANCE_FEE, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + + // getStats function Checker + nostromoTestCaseC.getState()->getStatsChecker(epochRevenu_t, totalPoolWeight, numberOfCreatedProject_t, numberOfFundraising_t, countOfRegister); + + utcTime.Year = 2025; + utcTime.Month = 7; + utcTime.Day = 24; + utcTime.Hour = 0; + updateQpiTime(); + + uint64 originalCreatorBalance = getBalance(registers[0]); + nostromoTestCaseC.endEpoch(); + EXPECT_EQ(getBalance(registers[0]) - originalCreatorBalance, totalInvestedAmount - div(totalInvestedAmount * 5, 100ULL) + totalInvestedAmount_2 - div(totalInvestedAmount_2 * 5, 100ULL)); + nostromoTestCaseC.getState()->endEpochSucceedFundraisingChecker(registers[0], 1, totalInvestedAmount_2, originalCreatorBalance, assetName); + + // EndEpochFailedFundraising Checker + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 20; + utcTime.Hour = 0; + updateQpiTime(); + + increaseEnergy(registers[0], NOSTROMO_CREATE_PROJECT_FEE); + assetName = assetNameFromString("BBBB"); + nostromoTestCaseC.createProject(registers[0], assetName, 21000000, 25, 6, 22, 0, 25, 6, 25, 0); + numberOfCreatedProject_t++; + epochRevenu_t += 100000000; + + // getProjectIndexListByCreator function checker + NOST::getProjectIndexListByCreator_output getProjectIndexListByCreator_output = nostromoTestCaseC.getProjectIndexListByCreator(registers[0]); + for (uint32 i = 0; i < 128; i++) + { + if (i < 3) + { + EXPECT_EQ(getProjectIndexListByCreator_output.indexListForProjects.get(i), i); + } + else { + EXPECT_EQ(getProjectIndexListByCreator_output.indexListForProjects.get(i), 262144); + } + } + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 23; + utcTime.Hour = 0; + updateQpiTime(); + + Ynumber = 0; Nnumber = 0; + duplicatedUser.clear(); + + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + continue; + } + + bit decision = (bit)random(0, 3); + if (decision) + { + Ynumber++; + } + else + { + Nnumber++; + } + + nostromoTestCaseC.voteInProject(user, 2, decision); + duplicatedUser[user] = 1; + } + nostromoTestCaseC.getState()->voteInProjectChecker(2, Ynumber, Nnumber); + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 26; + utcTime.Hour = 0; + updateQpiTime(); + increaseEnergy(registers[0], NOSTROMO_QX_TOKEN_ISSUANCE_FEE); + + nostromoTestCaseC.createFundraising(registers[0], 100000, 2000000, 150000000000, 2, + 25, 6, 27, 0, + 25, 7, 5, 0, + 25, 7, 8, 0, + 25, 7, 10, 0, + 25, 7, 20, 0, + 25, 7, 23, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12); + numberOfFundraising_t++; + + nostromoTestCaseC.getState()->countOfFundraisingChecker(3); + nostromoTestCaseC.getState()->createFundraisingChecker(registers[0], 100000, 2000000, 150000000000, 2, + 25, 6, 27, 0, + 25, 7, 5, 0, + 25, 7, 8, 0, + 25, 7, 10, 0, + 25, 7, 20, 0, + 25, 7, 23, 0, + 25, 7, 25, 0, + 25, 7, 27, 0, + 26, 7, 27, 0, + 20, 10, 12, 2); + + utcTime.Year = 2025; + utcTime.Month = 6; + utcTime.Day = 27; + utcTime.Hour = 1; + updateQpiTime(); + + uint64 totalInvestedAmount_3 = 0; + duplicatedUser.clear(); + ct = 0; + originalSCBalance = getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0)); + for (const auto& user : registers) + { + if (duplicatedUser[user]) + { + ct++; + continue; + } + ct++; + increaseEnergy(user, 180000000000); + uint8 tierLevel = nostromoTestCaseC.getState()->getTierLevel(user); + + if (ct >= 4000) + { + + // Phase 2 Investment + utcTime.Year = 2025; + utcTime.Month = 7; + utcTime.Day = 9; + utcTime.Hour = 0; + updateQpiTime(); + } + + bit sg = 0; + switch (tierLevel) + { + case 1: + if (ct < 4000) + { + if (totalInvestedAmount_3 + facehuggerMaxInvestAmount > 120000000000) + { + sg = 1; + break; + } + totalInvestedAmount_3 += facehuggerMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 2, facehuggerMaxInvestAmount); + break; + case 2: + if (ct < 4000) + { + if (totalInvestedAmount_3 + chestburstMaxInvestAmount > 120000000000) + { + sg = 1; + break; + } + totalInvestedAmount_3 += chestburstMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 2, chestburstMaxInvestAmount); + break; + case 3: + if (ct < 4000) + { + if (totalInvestedAmount_3 + dogMaxInvestAmount > 120000000000) + { + sg = 1; + break; + } + totalInvestedAmount_3 += dogMaxInvestAmount; + } + nostromoTestCaseC.investInProject(user, 2, dogMaxInvestAmount); + break; + case 4: + if (totalInvestedAmount_3 + xenomorphMaxInvestAmount > 120000000000) + { + sg = 1; + break; + } + totalInvestedAmount_3 += xenomorphMaxInvestAmount; + nostromoTestCaseC.investInProject(user, 2, xenomorphMaxInvestAmount); + break; + case 5: + if (totalInvestedAmount_3 + warriorMaxInvestAmount > 120000000000) + { + sg = 1; + break; + } + totalInvestedAmount_3 += warriorMaxInvestAmount; + nostromoTestCaseC.investInProject(user, 2, warriorMaxInvestAmount); + break; + + default: + break; + } + + if (sg) + { + break; + } + + duplicatedUser[user] = 1; + } + + nostromoTestCaseC.getState()->totalRaisedFundChecker(2, totalInvestedAmount_3, assetName); + + utcTime.Year = 2025; + utcTime.Month = 7; + utcTime.Day = 24; + utcTime.Hour = 0; + updateQpiTime(); + + originalCreatorBalance = getBalance(registers[0]); + EXPECT_EQ(originalSCBalance + totalInvestedAmount_3, getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + + uint64 epochRevenue = nostromoTestCaseC.getState()->getEpochRevenue(); + uint64 teamFee = div(epochRevenue, 10ULL); + epochRevenue -= teamFee; + nostromoTestCaseC.endEpoch(); + + EXPECT_EQ(originalSCBalance + totalInvestedAmount_3 - teamFee - (div(epochRevenue, 676ULL) * 676), getBalance(id(NOST_CONTRACT_INDEX, 0, 0, 0))); + nostromoTestCaseC.getState()->endEpochFailedFundraisingChecker(2); + nostromoTestCaseC.getState()->endEpochVoteStatusClearChecker(); +} diff --git a/test/contract_qbay.cpp b/test/contract_qbay.cpp new file mode 100644 index 000000000..677a79a9d --- /dev/null +++ b/test/contract_qbay.cpp @@ -0,0 +1,1174 @@ +#define NO_UEFI + +#include +#include + +#include "contract_testing.h" + +const id MARKETPLACE_OWNER = ID(_R, _K, _D, _H, _C, _M, _R, _J, _Y, _C, _G, _K, _P, _D, _U, _Y, _R, _X, _G, _D, _Y, _Z, _C, _I, _Z, _I, _T, _A, _H, _Y, _O, _V, _G, _I, _U, _T, _K, _N, _D, _T, _E, _H, _P, _C, _C, _L, _W, _L, _Z, _X, _S, _H, _N, _F, _P, _D); +const id CFB_ISSUER = ID(_C, _F, _B, _M, _E, _M, _Z, _O, _I, _D, _E, _X, _Q, _A, _U, _X, _Y, _Y, _S, _Z, _I, _U, _R, _A, _D, _Q, _L, _A, _P, _W, _P, _M, _N, _J, _X, _Q, _S, _N, _V, _Q, _Z, _A, _H, _Y, _V, _O, _P, _Y, _U, _K, _K, _J, _B, _J, _U, _C); +static constexpr uint64 QBAY_ISSUE_ASSET_FEE = 1000000000ULL; +static constexpr uint64 QBAY_TOKEN_TRANSFER_FEE = 1000000ULL; +static constexpr sint64 QBAY_CREATED_CFB_AMOUNT = 1000000000000ULL; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_2_200 = 100; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_201_1000 = 200; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_1001_2000 = 400; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_2001_3000 = 600; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_3001_4000 = 800; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_4001_5000 = 1000; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_5001_6000 = 1200; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_6001_7000 = 1400; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_7001_8000 = 1600; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_8001_9000 = 1800; +constexpr uint32 QBAY_FEE_COLLECTION_CREATE_9001_10000 = 2000; + +static std::mt19937_64 rand64; + +static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) +{ + if(minValue > maxValue) + { + return 0; + } + return minValue + rand64() % (maxValue - minValue); +} + +static id getUser(unsigned long long i) +{ + return id(i, i / 2 + 4, i + 10, i * 3 + 8); +} + +static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) +{ + unsigned long long userCount = random(0, maxNum); + std::vector users; + users.reserve(userCount); + for (unsigned int i = 0; i < userCount; ++i) + { + unsigned long long userIdx = random(0, totalUsers - 1); + users.push_back(getUser(userIdx)); + } + return users; +} + +static Array getRandomURI() +{ + Array URI; + + for(sint32 i = 0 ; i < 64; i++) + { + uint8 t = (uint8)random(0, 127); + if((t >= 48 && t <= 57) || (t >= 65 && t <= 90) || (t >= 97 && t <= 122)) + { + URI.set(i, t); + continue; + } + i--; + } + + return URI; +} + +class QBAYChecker : public QBAY +{ +public: + + void stateVriableChecker(uint64 gt_priceOfCFB, uint64 gt_priceOfQubic, uint64 gt_numberOfNFTIncoming, uint32 gt_numberOfCollection, uint32 gt_numberOfNFT, bit gt_statusOfMarketPlace) + { + EXPECT_EQ(gt_priceOfCFB, priceOfCFB); + EXPECT_EQ(gt_priceOfQubic, priceOfQubic); + EXPECT_EQ(gt_numberOfNFTIncoming, numberOfNFTIncoming); + EXPECT_EQ(gt_numberOfCollection, numberOfCollection); + EXPECT_EQ(gt_numberOfNFT, numberOfNFT); + EXPECT_EQ(gt_statusOfMarketPlace, statusOfMarketPlace); + } + + void createCollectionChecker(id user, uint64 priceForDropMint, uint32 countOfNFT, uint32 royalty, uint32 maxSizePerOneId, bit typeOfCollection, uint32 countOfCollection, Array& URI) + { + EXPECT_EQ(Collections.get(countOfCollection - 1).creator, user); + EXPECT_EQ(Collections.get(countOfCollection - 1).currentSize, countOfNFT); + EXPECT_EQ(Collections.get(countOfCollection - 1).maxSizeHoldingPerOneId, maxSizePerOneId); + EXPECT_EQ(Collections.get(countOfCollection - 1).priceForDropMint, priceForDropMint); + EXPECT_EQ(Collections.get(countOfCollection - 1).royalty, royalty); + EXPECT_EQ(Collections.get(countOfCollection - 1).typeOfCollection, typeOfCollection); + for(uint8 i = 0; i < 64; i++) + { + if(i >= QBAY_LENGTH_OF_URI) + { + EXPECT_EQ(Collections.get(countOfCollection - 1).URI.get(i), 0); + } + else { + EXPECT_EQ(Collections.get(countOfCollection - 1).URI.get(i), URI.get(i)); + } + } + } + + void mintChecker(id user, uint32 royalty, uint32 collectionId, Array URI, bit typeOfMint, uint32 idOfNFT) + { + EXPECT_EQ(NFTs.get(idOfNFT).creator, user); + EXPECT_EQ(NFTs.get(idOfNFT).possessor, user); + if(typeOfMint == 0) + { + EXPECT_EQ(NFTs.get(idOfNFT).royalty, Collections.get(collectionId).royalty); + } + else { + EXPECT_EQ(NFTs.get(idOfNFT).royalty, royalty); + } + for(uint8 i = 0; i < 64; i++) + { + if(i >= QBAY_LENGTH_OF_URI) + { + EXPECT_EQ(NFTs.get(idOfNFT).URI.get(i), 0); + } + else { + EXPECT_EQ(NFTs.get(idOfNFT).URI.get(i), URI.get(i)); + } + } + } + + void transferChecker(id newUser, uint32 NFTId) + { + EXPECT_EQ(NFTs.get(NFTId).possessor, newUser); + } + + void listInMarketChecker(uint32 NFTId, uint64 price) + { + EXPECT_EQ(NFTs.get(NFTId).statusOfSale, 1); + EXPECT_EQ(NFTs.get(NFTId).salePrice, price); + } + + void buyChecker(id oldPossesor, id newPossessor, uint32 NFTId, uint64 price, bit typfOfPayment, uint64 initialBalanceOfCreator, uint64 initialBalanceOfPossesor, uint64 initialBalanceOfMarket, bit isSameCreatorAndPossesor) + { + EXPECT_EQ(NFTs.get(NFTId).possessor, newPossessor); + if(typfOfPayment == 0) + { + if(isSameCreatorAndPossesor == 1) + { + EXPECT_EQ(initialBalanceOfPossesor + price - div(price * 25ULL, 1000ULL), getBalance(oldPossesor)); + EXPECT_EQ(initialBalanceOfMarket + div(price * 25ULL, 1000ULL), getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0))); + } + else { + EXPECT_EQ(initialBalanceOfCreator + div(price * NFTs.get(NFTId).royalty * 1ULL, 100ULL), getBalance(NFTs.get(NFTId).creator)); + EXPECT_EQ(initialBalanceOfPossesor + price - div(price * (NFTs.get(NFTId).royalty * 10 + 30) * 1ULL, 1000ULL), getBalance(oldPossesor)); + } + } + else + { + if(isSameCreatorAndPossesor == 1) + { + EXPECT_EQ(initialBalanceOfPossesor + price - div(price * 25ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); + EXPECT_EQ(initialBalanceOfMarket + div(price * 25ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); + } + else + { + EXPECT_EQ(initialBalanceOfCreator + div(price * NFTs.get(NFTId).royalty * 1ULL, 100ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, NFTs.get(NFTId).creator, NFTs.get(NFTId).creator, QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, NFTs.get(NFTId).creator, NFTs.get(NFTId).creator, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); + EXPECT_EQ(initialBalanceOfPossesor + price - div(price * NFTs.get(NFTId).royalty * 1ULL, 100ULL) - div(price * 20ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, oldPossesor, oldPossesor, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); + EXPECT_EQ(initialBalanceOfMarket + div(price * 20ULL, 1000ULL), numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); + } + } + } + + void cancelSaleChecker(uint32 NFTId) + { + EXPECT_EQ(NFTs.get(NFTId).salePrice, QBAY_SALE_PRICE); + EXPECT_EQ(NFTs.get(NFTId).statusOfSale, 0); + } + + void listInExchangeChecker(id user, uint32 possessedNFT, uint32 anotherNFT) + { + EXPECT_EQ(NFTs.get(anotherNFT).NFTidForExchange, possessedNFT); + EXPECT_EQ(NFTs.get(anotherNFT).statusOfExchange, 1); + } + + void possesorChecker(id user, uint32 possessedNFT) + { + EXPECT_EQ(NFTs.get(possessedNFT).possessor, user); + } + + void cancelExchangeChecker(uint32 NFTId) + { + EXPECT_EQ(NFTs.get(NFTId).NFTidForExchange, QBAY_MAX_NUMBER_NFT); + EXPECT_EQ(NFTs.get(NFTId).statusOfExchange, 0); + } + + void makeOfferChecker(uint32 NFTId, id user, bit paymentMethod, uint64 askPrice) + { + EXPECT_EQ(NFTs.get(NFTId).askUser, user); + EXPECT_EQ(NFTs.get(NFTId).paymentMethodOfAsk, paymentMethod); + EXPECT_EQ(NFTs.get(NFTId).askMaxPrice, askPrice); + } + + void acceptOfferChecker(id user, uint32 NFTId) + { + EXPECT_LT(NFTs.get(NFTId).askUser, user); + EXPECT_EQ(NFTs.get(NFTId).askMaxPrice, 0); + EXPECT_EQ(NFTs.get(NFTId).statusOfAsk, 0); + } + + void cancelOfferChecker(uint32 NFTId) + { + EXPECT_EQ(NFTs.get(NFTId).askUser, NULL_ID); + EXPECT_EQ(NFTs.get(NFTId).askMaxPrice, 0); + } + + void createAuctionChecker(uint32 NFTId, uint64 price, bit gt_statusOfAuction, bit gt_paymentMethodOfAuction, id gt_creatorOfAuction, uint32 startYear, uint32 startMonth, uint32 startDay, uint32 startHour, uint32 endYear, uint32 endMonth, uint32 endDay, uint32 endHour) + { + uint32 startTime, endTime; + QBAY::packQbayDate(startYear, startMonth, startDay, startHour, 0, 0, startTime); + QBAY::packQbayDate(endYear, endMonth, endDay, endHour, 0, 0, endTime); + EXPECT_EQ(NFTs.get(NFTId).startTimeOfAuction, startTime); + EXPECT_EQ(NFTs.get(NFTId).endTimeOfAuction, endTime); + EXPECT_EQ(NFTs.get(NFTId).currentPriceOfAuction, price); + EXPECT_EQ(NFTs.get(NFTId).statusOfAuction, gt_statusOfAuction); + EXPECT_EQ(NFTs.get(NFTId).paymentMethodOfAuction, gt_paymentMethodOfAuction); + EXPECT_EQ(NFTs.get(NFTId).creatorOfAuction, gt_creatorOfAuction); + } + + void bidOnAuctionChecker(id user, uint32 NFTId, uint64 price) + { + EXPECT_EQ(NFTs.get(NFTId).possessor, user); + EXPECT_EQ(NFTs.get(NFTId).currentPriceOfAuction, price); + EXPECT_EQ(NFTs.get(NFTId).statusOfAuction, 2); + } + + void profitChecker(uint64 amountOfQubic, uint64 amountOfCFB) + { + EXPECT_EQ(amountOfQubic, earnedQubic); + EXPECT_EQ(amountOfCFB, earnedCFB); + } + + void getNumberOfNFTForUserChecker(getNumberOfNFTForUser_output output, uint32 numberOfNFT) + { + EXPECT_EQ(output.numberOfNFT, numberOfNFT); + } + + void getInfoOfNFTUserPossessedChecker(getInfoOfNFTUserPossessed_output output, uint32 idOfNFT) + { + EXPECT_EQ(output.creator, NFTs.get(idOfNFT).creator); + EXPECT_EQ(output.possessor, NFTs.get(idOfNFT).possessor); + EXPECT_EQ(output.askMaxPrice, NFTs.get(idOfNFT).askMaxPrice); + EXPECT_EQ(output.askUser, NFTs.get(idOfNFT).askUser); + EXPECT_EQ(output.creatorOfAuction, NFTs.get(idOfNFT).creatorOfAuction); + EXPECT_EQ(output.currentPriceOfAuction, NFTs.get(idOfNFT).currentPriceOfAuction); + EXPECT_EQ(output.statusOfSale, NFTs.get(idOfNFT).statusOfSale); + EXPECT_EQ(output.statusOfAsk, NFTs.get(idOfNFT).statusOfAsk); + EXPECT_EQ(output.statusOfAuction, NFTs.get(idOfNFT).statusOfAuction); + EXPECT_EQ(output.statusOfExchange, NFTs.get(idOfNFT).statusOfExchange); + EXPECT_EQ(output.royalty, NFTs.get(idOfNFT).royalty); + EXPECT_EQ(output.salePrice, NFTs.get(idOfNFT).salePrice); + EXPECT_EQ(output.NFTidForExchange, NFTs.get(idOfNFT).NFTidForExchange); + EXPECT_EQ(output.paymentMethodOfAsk, NFTs.get(idOfNFT).paymentMethodOfAsk); + for(uint32 i = 0 ; i < 64; i++) + { + EXPECT_EQ(output.URI.get(i), NFTs.get(idOfNFT).URI.get(i)); + } + } + + void getInfoOfMarketplaceChecker(getInfoOfMarketplace_output output) + { + EXPECT_EQ(output.earnedCFB, earnedCFB); + EXPECT_EQ(output.earnedQubic, earnedQubic); + EXPECT_EQ(output.numberOfCollection, numberOfCollection); + EXPECT_EQ(output.numberOfNFT, numberOfNFT); + EXPECT_EQ(output.numberOfNFTIncoming, numberOfNFTIncoming); + EXPECT_EQ(output.priceOfCFB, priceOfCFB); + EXPECT_EQ(output.priceOfQubic, priceOfQubic); + EXPECT_EQ(output.statusOfMarketPlace, statusOfMarketPlace); + } + + void getInfoOfCollectionByCreatorChecker(getInfoOfCollectionByCreator_output output, uint32 idOfCollection) + { + EXPECT_EQ(output.currentSize, Collections.get(idOfCollection).currentSize); + EXPECT_EQ(output.idOfCollection, idOfCollection); + EXPECT_EQ(output.maxSizeHoldingPerOneId, Collections.get(idOfCollection).maxSizeHoldingPerOneId); + EXPECT_EQ(output.priceForDropMint, Collections.get(idOfCollection).priceForDropMint); + EXPECT_EQ(output.royalty, Collections.get(idOfCollection).royalty); + EXPECT_EQ(output.typeOfCollection, Collections.get(idOfCollection).typeOfCollection); + for(uint32 i = 0 ; i < 64; i++) + { + EXPECT_EQ(output.URI.get(i), Collections.get(idOfCollection).URI.get(i)); + } + } + + void getInfoOfCollectionByIdChecker(getInfoOfCollectionById_output output, uint32 idOfCollection) + { + EXPECT_EQ(output.creator, Collections.get(idOfCollection).creator); + EXPECT_EQ(output.currentSize, Collections.get(idOfCollection).currentSize); + EXPECT_EQ(output.maxSizeHoldingPerOneId, Collections.get(idOfCollection).maxSizeHoldingPerOneId); + EXPECT_EQ(output.priceForDropMint, Collections.get(idOfCollection).priceForDropMint); + EXPECT_EQ(output.royalty, Collections.get(idOfCollection).royalty); + EXPECT_EQ(output.typeOfCollection, Collections.get(idOfCollection).typeOfCollection); + for(uint32 i = 0 ; i < 64; i++) + { + EXPECT_EQ(output.URI.get(i), Collections.get(idOfCollection).URI.get(i)); + } + } + + void getIncomingAuctionsChecker(getIncomingAuctions_output output, uint32 offset, uint32 count) + { + for(uint32 i = offset, k = 0; i < count; i++, k++) + { + EXPECT_EQ(NFTs.get(output.NFTId.get(k)).statusOfAuction, 1); + } + } + + void getInfoOfNFTByIdChecker(getInfoOfNFTById_output output, uint32 idOfNFT) + { + EXPECT_EQ(output.creator, NFTs.get(idOfNFT).creator); + EXPECT_EQ(output.possessor, NFTs.get(idOfNFT).possessor); + EXPECT_EQ(output.askMaxPrice, NFTs.get(idOfNFT).askMaxPrice); + EXPECT_EQ(output.askUser, NFTs.get(idOfNFT).askUser); + EXPECT_EQ(output.creatorOfAuction, NFTs.get(idOfNFT).creatorOfAuction); + EXPECT_EQ(output.currentPriceOfAuction, NFTs.get(idOfNFT).currentPriceOfAuction); + EXPECT_EQ(output.statusOfSale, NFTs.get(idOfNFT).statusOfSale); + EXPECT_EQ(output.statusOfAsk, NFTs.get(idOfNFT).statusOfAsk); + EXPECT_EQ(output.statusOfAuction, NFTs.get(idOfNFT).statusOfAuction); + EXPECT_EQ(output.statusOfExchange, NFTs.get(idOfNFT).statusOfExchange); + EXPECT_EQ(output.royalty, NFTs.get(idOfNFT).royalty); + EXPECT_EQ(output.salePrice, NFTs.get(idOfNFT).salePrice); + EXPECT_EQ(output.NFTidForExchange, NFTs.get(idOfNFT).NFTidForExchange); + EXPECT_EQ(output.paymentMethodOfAsk, NFTs.get(idOfNFT).paymentMethodOfAsk); + for(uint32 i = 0 ; i < 64; i++) + { + EXPECT_EQ(output.URI.get(i), NFTs.get(idOfNFT).URI.get(i)); + } + } + + uint64 getDropMintPrice(uint32 collectionId) + { + return Collections.get(collectionId).priceForDropMint; + } + + id getCreatorOfNFT(uint32 NFTId) + { + return NFTs.get(NFTId).creator; + } + + id getPossessorOfNFT(uint32 NFTId) + { + return NFTs.get(NFTId).possessor; + } + +}; + +class ContractTestingQBAY : protected ContractTesting +{ +public: + ContractTestingQBAY() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QBAY); + callSystemProcedure(QBAY_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + ~ContractTestingQBAY() + { + } + + QBAYChecker* getState() + { + return (QBAYChecker*)contractStates[QBAY_CONTRACT_INDEX]; + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QBAY_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + QBAY::getNumberOfNFTForUser_output getNumberOfNFTForUser(id user) const + { + QBAY::getNumberOfNFTForUser_input input; + QBAY::getNumberOfNFTForUser_output output; + + input.user = user; + + callFunction(QBAY_CONTRACT_INDEX, 1, input, output); + return output; + } + + QBAY::getInfoOfNFTUserPossessed_output getInfoOfNFTUserPossessed(id user, uint32 NFTNumber) const + { + QBAY::getInfoOfNFTUserPossessed_input input; + QBAY::getInfoOfNFTUserPossessed_output output; + + input.user = user; + input.NFTNumber = NFTNumber; + + callFunction(QBAY_CONTRACT_INDEX, 2, input, output); + return output; + } + + QBAY::getInfoOfMarketplace_output getInfoOfMarketplace() const + { + QBAY::getInfoOfMarketplace_input input; + QBAY::getInfoOfMarketplace_output output; + + callFunction(QBAY_CONTRACT_INDEX, 3, input, output); + return output; + } + + QBAY::getInfoOfCollectionByCreator_output getInfoOfCollectionByCreator(id creator, uint32 orderOfCollection) const + { + QBAY::getInfoOfCollectionByCreator_input input; + QBAY::getInfoOfCollectionByCreator_output output; + + input.creator = creator; + input.orderOfCollection = orderOfCollection; + + callFunction(QBAY_CONTRACT_INDEX, 4, input, output); + return output; + } + + QBAY::getInfoOfCollectionById_output getInfoOfCollectionById(uint32 idOfCollection) const + { + QBAY::getInfoOfCollectionById_input input; + QBAY::getInfoOfCollectionById_output output; + + input.idOfCollection = idOfCollection; + + callFunction(QBAY_CONTRACT_INDEX, 5, input, output); + return output; + } + + QBAY::getIncomingAuctions_output getIncomingAuctions(uint32 offset, uint32 count) const + { + QBAY::getIncomingAuctions_input input; + QBAY::getIncomingAuctions_output output; + + input.count = count; + input.offset = offset; + + callFunction(QBAY_CONTRACT_INDEX, 6, input, output); + return output; + } + + QBAY::getInfoOfNFTById_output getInfoOfNFTById(uint32 NFTId) const + { + QBAY::getInfoOfNFTById_input input; + QBAY::getInfoOfNFTById_output output; + + input.NFTId = NFTId; + + callFunction(QBAY_CONTRACT_INDEX, 7, input, output); + return output; + } + + QBAY::getUserCreatedCollection_output getUserCreatedCollection(id user, uint32 offset, uint32 count) const + { + QBAY::getUserCreatedCollection_input input; + QBAY::getUserCreatedCollection_output output; + + input.user = user; + input.offset = offset; + input.count = count; + + callFunction(QBAY_CONTRACT_INDEX, 8, input, output); + return output; + } + + QBAY::getUserCreatedNFT_output getUserCreatedNFT(id user, uint32 offset, uint32 count) const + { + QBAY::getUserCreatedNFT_input input; + QBAY::getUserCreatedNFT_output output; + + input.user = user; + input.offset = offset; + input.count = count; + + callFunction(QBAY_CONTRACT_INDEX, 9, input, output); + return output; + } + + QBAY::settingCFBAndQubicPrice_output settingCFBAndQubicPrice(const id& marketOwnerAdress, uint64 CFBPrice, uint64 QubicPrice) + { + QBAY::settingCFBAndQubicPrice_input input; + QBAY::settingCFBAndQubicPrice_output output; + + input.CFBPrice = CFBPrice; + input.QubicPrice = QubicPrice; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 1, input, output, marketOwnerAdress, 0); + + return output; + } + + QBAY::createCollection_output createCollection(const id& user, uint64 priceForDropMint, uint32 volume, uint32 royalty, uint32 maxSizePerOneId, bit typeOfCollection, Array& URI) + { + QBAY::createCollection_input input; + QBAY::createCollection_output output; + + input.maxSizePerOneId = maxSizePerOneId; + input.priceForDropMint = priceForDropMint; + input.volume = volume; + input.royalty = royalty; + input.typeOfCollection = typeOfCollection; + + for(uint32 i = 0 ; i < 64; i++) + { + if(i >= QBAY_LENGTH_OF_URI) + { + input.URI.set(i, 0); + } + else + { + input.URI.set(i, URI.get(i)); + } + } + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 2, input, output, user, 0); + + return output; + } + + QBAY::mint_output mint(const id& user, uint32 royalty, uint32 collectionId, Array URI, bit typeOfMint, uint64 mintFee) + { + QBAY::mint_input input; + QBAY::mint_output output; + + input.collectionId = collectionId; + input.royalty = royalty; + input.typeOfMint = typeOfMint; + + for(uint32 i = 0 ; i < 64; i++) + { + input.URI.set(i, URI.get(i)); + } + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 3, input, output, user, mintFee); + + return output; + } + + QBAY::mintOfDrop_output mintOfDrop(const id& user, uint32 collectionId, Array URI, uint64 mintOfDropFee) + { + QBAY::mintOfDrop_input input; + QBAY::mintOfDrop_output output; + + input.collectionId = collectionId; + + for(uint32 i = 0; i < 64; i++) + { + input.URI.set(i, URI.get(i)); + } + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 4, input, output, user, mintOfDropFee); + + return output; + } + + QBAY::transfer_output transfer(const id& user, uint32 NFTid, id receiver, uint64 transferFee) + { + QBAY::transfer_input input; + QBAY::transfer_output output; + + input.NFTid = NFTid; + input.receiver = receiver; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 5, input, output, user, transferFee); + + return output; + } + + QBAY::listInMarket_output listInMarket(const id& user, uint64 price, uint32 NFTid) + { + QBAY::listInMarket_input input; + QBAY::listInMarket_output output; + + input.NFTid = NFTid; + input.price = price; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 6, input, output, user, 0); + + return output; + } + + QBAY::buy_output buy(const id& user, uint32 NFTid, bit methodOfPayment, uint64 salePrice) + { + QBAY::buy_input input; + QBAY::buy_output output; + + input.methodOfPayment = methodOfPayment; + input.NFTid = NFTid; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 7, input, output, user, salePrice); + + return output; + } + + QBAY::cancelSale_output cancelSale(const id& user, uint32 NFTid) + { + QBAY::cancelSale_input input; + QBAY::cancelSale_output output; + + input.NFTid = NFTid; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 8, input, output, user, 0); + + return output; + } + + QBAY::listInExchange_output listInExchange(const id& user, uint32 possessedNFT, uint32 anotherNFT) + { + QBAY::listInExchange_input input; + QBAY::listInExchange_output output; + + input.anotherNFT = anotherNFT; + input.possessedNFT = possessedNFT; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 9, input, output, user, 0); + + return output; + } + + QBAY::cancelExchange_output cancelExchange(const id& user, uint32 possessedNFT, uint32 anotherNFT) + { + QBAY::cancelExchange_input input; + QBAY::cancelExchange_output output; + + input.possessedNFT = possessedNFT; + input.anotherNFT = anotherNFT; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 10, input, output, user, 0); + + return output; + } + + QBAY::makeOffer_output makeOffer(const id& user, sint64 askPrice, uint32 NFTid, bit paymentMethod) + { + QBAY::makeOffer_input input; + QBAY::makeOffer_output output; + + input.askPrice = askPrice; + input.NFTid = NFTid; + input.paymentMethod = paymentMethod; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 11, input, output, user, paymentMethod == 0?askPrice:0); + + return output; + } + + QBAY::acceptOffer_output acceptOffer(const id& user, uint32 NFTid) + { + QBAY::acceptOffer_input input; + QBAY::acceptOffer_output output; + + input.NFTid = NFTid; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 12, input, output, user, 0); + + return output; + } + + QBAY::cancelOffer_output cancelOffer(const id& user, uint32 NFTid) + { + QBAY::cancelOffer_input input; + QBAY::cancelOffer_output output; + + input.NFTid = NFTid; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 13, input, output, user, 0); + + return output; + } + + QBAY::createTraditionalAuction_output createTraditionalAuction(const id& user, uint64 minPrice, uint32 NFTId, bit paymentMethodOfAuction, uint32 startYear, uint32 startMonth, uint32 startDay, uint32 startHour, uint32 endYear, uint32 endMonth, uint32 endDay, uint32 endHour) + { + QBAY::createTraditionalAuction_input input; + QBAY::createTraditionalAuction_output output; + + input.startYear = startYear; + input.startMonth = startMonth; + input.startDay = startDay; + input.startHour = startHour; + input.endYear = startYear; + input.endMonth = endMonth; + input.endDay = endDay; + input.endHour = endHour; + input.minPrice = minPrice; + input.NFTId = NFTId; + input.paymentMethodOfAuction = paymentMethodOfAuction; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 14, input, output, user, 0); + + return output; + } + + QBAY::bidOnTraditionalAuction_output bidOnTraditionalAuction(const id& user, uint64 price, uint32 NFTId, bit paymentMethod) + { + QBAY::bidOnTraditionalAuction_input input; + QBAY::bidOnTraditionalAuction_output output; + + input.NFTId = NFTId; + input.paymentMethod = paymentMethod; + input.price = price; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 15, input, output, user, price); + + return output; + } + + QBAY::changeStatusOfMarketPlace_output changeStatusOfMarketPlace(const id& user, bit status) + { + QBAY::changeStatusOfMarketPlace_input input; + QBAY::changeStatusOfMarketPlace_output output; + + input.status = status; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 17, input, output, user, 0); + + return output; + } + + QBAY::TransferShareManagementRights_output qbayTransferShareManagementRights(const id& user, sint64 numberOfShares, uint32 newManagingContractIndex, uint64 fee) + { + QBAY::TransferShareManagementRights_input input; + QBAY::TransferShareManagementRights_output output; + + input.asset.assetName = QBAY_CFB_NAME; + input.asset.issuer = CFB_ISSUER; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QBAY_CONTRACT_INDEX, 16, input, output, user, fee); + + return output; + } + + sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) + { + QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QBAY_ISSUE_ASSET_FEE); + return output.issuedNumberOfShares; + } + + sint64 TransferShareOwnershipAndPossession(const id& issuer, uint64 assetName, sint64 numberOfShares, id newOwnerAndPossesor) + { + QX::TransferShareOwnershipAndPossession_input input; + QX::TransferShareOwnershipAndPossession_output output; + + input.assetName = assetName; + input.issuer = issuer; + input.newOwnerAndPossessor = newOwnerAndPossesor; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, issuer, QBAY_TOKEN_TRANSFER_FEE); + + return output.transferredNumberOfShares; + } + + sint64 TransferShareManagementRights(const id& issuer, uint64 assetName, uint32 newManagingContractIndex, sint64 numberOfShares, id currentOwner) + { + QX::TransferShareManagementRights_input input; + QX::TransferShareManagementRights_output output; + + input.asset.assetName = assetName; + input.asset.issuer = issuer; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, currentOwner, 0); + + return output.transferredNumberOfShares; + } + + +}; + +TEST(TestContractQBAY, testingAllProceduresAndFunctions) +{ + ContractTestingQBAY pfp; + + uint64 cfbPrice = random(1, 1000000); + uint64 qubicPrice = random(1, 1000000); + uint64 earnedQubic = 0; + uint64 earnedCFB = 0; + uint64 collectedShareHolderFee = 0; + uint32 totalIncommingNFTNumber = 0; + uint32 totalPriceForCollectionCreating = 0; + uint32 numberOfCollectionCreated = 0; + uint32 numberOfNFTCreated = 0; + + increaseEnergy(MARKETPLACE_OWNER, 1); + + pfp.settingCFBAndQubicPrice(MARKETPLACE_OWNER, cfbPrice, qubicPrice); + pfp.changeStatusOfMarketPlace(MARKETPLACE_OWNER, 1); + pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 1); + + uint64 assetName = assetNameFromString("CFB"); + + increaseEnergy(CFB_ISSUER, QBAY_ISSUE_ASSET_FEE); + EXPECT_EQ(pfp.issueAsset(CFB_ISSUER, assetName, QBAY_CREATED_CFB_AMOUNT, 0, 0), QBAY_CREATED_CFB_AMOUNT); + + auto users = getRandomUsers(10000, 10000); + + increaseEnergy(CFB_ISSUER, QBAY_TOKEN_TRANSFER_FEE); + // EXPECT_EQ(users[0], users[1]); + increaseEnergy(users[0], QBAY_TOKEN_TRANSFER_FEE); + pfp.TransferShareOwnershipAndPossession(CFB_ISSUER, assetName, 10000000000, users[0]); + + Array URI; + for(uint32 i = 0 ; i < 64; i++) + { + URI.set(i, getRandomURI().get(i)); + } + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_9001_10000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_9001_10000); + pfp.createCollection(users[0], 0, 10, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 10000, 10, 100, 1, 1, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_9001_10000; + totalIncommingNFTNumber += 10000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_9001_10000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_8001_9000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_8001_9000); + pfp.createCollection(users[0], 0, 9, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 9000, 10, 100, 1, 2, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_8001_9000; + totalIncommingNFTNumber += 9000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_8001_9000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_7001_8000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_7001_8000); + pfp.createCollection(users[0], 0, 8, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 8000, 10, 100, 1, 3, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_7001_8000; + totalIncommingNFTNumber += 8000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_7001_8000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_6001_7000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_6001_7000); + pfp.createCollection(users[0], 0, 7, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 7000, 10, 100, 1, 4, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_6001_7000; + totalIncommingNFTNumber += 7000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_6001_7000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_5001_6000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_5001_6000); + pfp.createCollection(users[0], 0, 6, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 6000, 10, 100, 1, 5, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_5001_6000; + totalIncommingNFTNumber += 6000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_5001_6000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_4001_5000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_4001_5000); + pfp.createCollection(users[0], 0, 5, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 5000, 10, 100, 1, 6, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_4001_5000; + totalIncommingNFTNumber += 5000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_4001_5000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_3001_4000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_3001_4000); + pfp.createCollection(users[0], 0, 4, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 4000, 10, 100, 1, 7, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_3001_4000; + totalIncommingNFTNumber += 4000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_3001_4000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_2001_3000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_2001_3000); + pfp.createCollection(users[0], 0, 3, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 3000, 10, 100, 1, 8, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_2001_3000; + totalIncommingNFTNumber += 3000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_2001_3000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_1001_2000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_1001_2000); + pfp.createCollection(users[0], 0, 2, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 2000, 10, 100, 1, 9, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_1001_2000; + totalIncommingNFTNumber += 2000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_1001_2000 * cfbPrice; + + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_201_1000, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_201_1000); + pfp.createCollection(users[0], 0, 1, 10, 100, 1, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 1000, 10, 100, 1, 10, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_201_1000; + totalIncommingNFTNumber += 1000; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_201_1000 * cfbPrice; + + // Collection for Drop. collection id: 10 + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, cfbPrice * QBAY_FEE_COLLECTION_CREATE_2_200, users[0]), cfbPrice * QBAY_FEE_COLLECTION_CREATE_2_200); + pfp.createCollection(users[0], 0, 0, 10, 100, 0, URI); + pfp.getState()->createCollectionChecker(users[0], 0, 200, 10, 100, 0, 11, URI); + totalPriceForCollectionCreating += QBAY_FEE_COLLECTION_CREATE_2_200; + totalIncommingNFTNumber += 200; + numberOfCollectionCreated++; + earnedCFB += QBAY_FEE_COLLECTION_CREATE_2_200 * cfbPrice; + + // getting the id of collection user created + + auto getUserCreatedCollection_output = pfp.getUserCreatedCollection(users[0], 0, 10); + for(uint32 i = 0 ; i < 11; i++) + { + EXPECT_EQ(getUserCreatedCollection_output.collectionId.get(i), 10 - i); + } + + // checking the infos of collection by creator + auto getInfoOfCollectionByCreator_output = pfp.getInfoOfCollectionByCreator(users[0], 1); + pfp.getState()->getInfoOfCollectionByCreatorChecker(getInfoOfCollectionByCreator_output, 0); + + // checking the infos of collection by id + auto getInfoOfCollectionById_output = pfp.getInfoOfCollectionById(0); + pfp.getState()->getInfoOfCollectionByIdChecker(getInfoOfCollectionById_output, 0); + + EXPECT_EQ(numberOfPossessedShares(assetName, CFB_ISSUER, id(12, 0, 0, 0), id(12, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), cfbPrice * totalPriceForCollectionCreating); + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic); + // mint the NFT using collection + + pfp.mint(users[0], 0, 0, URI, 0, 0); + numberOfNFTCreated++; + pfp.getState()->mintChecker(users[0], 0, 0, URI, 0, numberOfNFTCreated - 1); + pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 1); + + // mint the single NFT + increaseEnergy(users[0], QBAY_SINGLE_NFT_CREATE_FEE); + pfp.mint(users[0], 10, 0, URI, 1, QBAY_SINGLE_NFT_CREATE_FEE); + numberOfNFTCreated++; + totalIncommingNFTNumber++; + earnedQubic += QBAY_SINGLE_NFT_CREATE_FEE; + pfp.getState()->mintChecker(users[0], 10, 0, URI, 1, numberOfNFTCreated - 1); + pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 1); + + auto getUserCreatedNFT_output = pfp.getUserCreatedNFT(users[0], 0, 2); + for(uint32 i = 0 ; i < 2; i++) + { + EXPECT_EQ(getUserCreatedNFT_output.NFTId.get(i), 1 - i); + } + + // checking if 2 NFTs are minted by users[0] + auto getNumberOfNFTForUser_output = pfp.getNumberOfNFTForUser(users[0]); + pfp.getState()->getNumberOfNFTForUserChecker(getNumberOfNFTForUser_output, 2); + + // checking the info of users[0]'s first NFT + auto getInfoOfNFTUserPossessed_output = pfp.getInfoOfNFTUserPossessed(users[0], 1); + pfp.getState()->getInfoOfNFTUserPossessedChecker(getInfoOfNFTUserPossessed_output, 0); + + // checking the info of NFT by id + + auto getInfoOfNFTById_output = pfp.getInfoOfNFTById(0); + pfp.getState()->getInfoOfNFTByIdChecker(getInfoOfNFTById_output, 0); + + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic); + + // dropMint + increaseEnergy(users[0], pfp.getState()->getDropMintPrice(10)); + pfp.mintOfDrop(users[0], 10, URI, pfp.getState()->getDropMintPrice(10)); + numberOfNFTCreated++; + pfp.getState()->mintChecker(users[0], 10, 10, URI, 0, numberOfNFTCreated - 1); + + // transfer NFT + + increaseEnergy(users[1], 1); + pfp.transfer(users[0], 0, users[1], 0); + pfp.getState()->transferChecker(users[1], 0); + + // listInMarket + + pfp.listInMarket(users[1], 100000, 0); + pfp.getState()->listInMarketChecker(0, 100000); + + // buy with $Qubic + // small price + + increaseEnergy(users[2], 100000); + uint64 initialBalanceOfCreator = getBalance(pfp.getState()->getCreatorOfNFT(0)); + uint64 initialBalanceOfPossesor = getBalance(pfp.getState()->getPossessorOfNFT(0)); + uint64 initialBalanceOfMarket = getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)); + id oldPossesor = pfp.getState()->getPossessorOfNFT(0); + + uint64 gt_marketFee = div(100000 * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); + uint64 gt_shareHolderFee = div(100000 * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); + + pfp.buy(users[2], 0, 0, 100000); + pfp.getState()->buyChecker(oldPossesor, users[2], 0, 100000, 0, initialBalanceOfCreator, initialBalanceOfPossesor, initialBalanceOfMarket, pfp.getState()->getCreatorOfNFT(0) == pfp.getState()->getPossessorOfNFT(0)); + earnedQubic += gt_marketFee; + collectedShareHolderFee += gt_shareHolderFee; + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + + // big price + + pfp.listInMarket(users[2], 10000000, 0); + pfp.getState()->listInMarketChecker(0, 10000000); + + increaseEnergy(users[3], 10000000); + initialBalanceOfCreator = getBalance(pfp.getState()->getCreatorOfNFT(0)); + initialBalanceOfPossesor = getBalance(pfp.getState()->getPossessorOfNFT(0)); + initialBalanceOfMarket = getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)); + oldPossesor = pfp.getState()->getPossessorOfNFT(0); + + pfp.buy(users[3], 0, 0, 10000000); + gt_marketFee = div(10000000 * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); + gt_shareHolderFee = div(10000000 * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); + pfp.getState()->buyChecker(oldPossesor, users[3], 0, 10000000, 0, initialBalanceOfCreator, initialBalanceOfPossesor, initialBalanceOfMarket, pfp.getState()->getCreatorOfNFT(0) == pfp.getState()->getPossessorOfNFT(0)); + earnedQubic += gt_marketFee; + collectedShareHolderFee += gt_shareHolderFee; + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + + // buy with $CFB + + pfp.listInMarket(users[3], 10000000, 0); + pfp.getState()->listInMarketChecker(0, 10000000); + + initialBalanceOfCreator = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getCreatorOfNFT(0), pfp.getState()->getCreatorOfNFT(0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getCreatorOfNFT(0), pfp.getState()->getCreatorOfNFT(0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX); + initialBalanceOfPossesor = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getPossessorOfNFT(0), pfp.getState()->getPossessorOfNFT(0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, pfp.getState()->getPossessorOfNFT(0), pfp.getState()->getPossessorOfNFT(0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX); + initialBalanceOfMarket = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QX_CONTRACT_INDEX, QX_CONTRACT_INDEX) + numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX); + oldPossesor = pfp.getState()->getPossessorOfNFT(0); + + increaseEnergy(users[4], 10000000); + increaseEnergy(CFB_ISSUER, QBAY_TOKEN_TRANSFER_FEE); + EXPECT_EQ(pfp.TransferShareOwnershipAndPossession(CFB_ISSUER, assetName, 10000000000, users[4]), 10000000000); + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, div(10000000ULL, qubicPrice) * cfbPrice, users[4]), div(10000000ULL, qubicPrice) * cfbPrice); + + pfp.buy(users[4], 0, 1, 0); + earnedCFB += div(div(10000000ULL, qubicPrice) * cfbPrice * QBAY_FEE_NFT_SALE_MARKET, 1000ULL); + + pfp.getState()->buyChecker(oldPossesor, users[4], 0, div(10000000ULL, qubicPrice) * cfbPrice, 1, initialBalanceOfCreator, initialBalanceOfPossesor, initialBalanceOfMarket, pfp.getState()->getCreatorOfNFT(0) == pfp.getState()->getPossessorOfNFT(0)); + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + // cancelSale + + pfp.listInMarket(users[4], 10000000, 0); + pfp.getState()->listInMarketChecker(0, 10000000); + + pfp.cancelSale(users[4], 0); + pfp.getState()->cancelSaleChecker(0); + + // listInExchange + + increaseEnergy(users[4], 10000000); + increaseEnergy(users[0], 10000000); + + pfp.listInExchange(users[0], 1, 0); + pfp.getState()->listInExchangeChecker(users[0], 1, 0); + + pfp.listInExchange(users[4], 0, 1); + pfp.getState()->possesorChecker(users[0], 0); + pfp.getState()->possesorChecker(users[4], 1); + + // cancelExchange + + pfp.listInExchange(users[4], 1, 0); + pfp.cancelExchange(users[4], 1, 0); + pfp.getState()->cancelExchangeChecker(0); + + // makeOffer with $Qubic + + pfp.makeOffer(users[4], 10000000, 0, 0); + pfp.getState()->makeOfferChecker(0, users[4], 0, 10000000); + increaseEnergy(users[5], 100000000); + pfp.makeOffer(users[5], 100000000, 0, 0); + pfp.getState()->makeOfferChecker(0, users[5], 0, 100000000); + + // makeOffer with $CFB in high price + + sint64 askPrice = (div(1000000000ULL, qubicPrice) + 1) * cfbPrice; + increaseEnergy(CFB_ISSUER, 100000000); + EXPECT_EQ(pfp.TransferShareOwnershipAndPossession(CFB_ISSUER, assetName, askPrice, users[4]), askPrice); + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, askPrice, users[4]), askPrice); + + pfp.makeOffer(users[4], askPrice, 0, 1); + pfp.getState()->makeOfferChecker(0, users[4], 1, askPrice); + + // acceptOffer + + pfp.acceptOffer(users[0], 0); + pfp.getState()->acceptOfferChecker(users[0], 0); + earnedCFB += div(askPrice * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); + + // cancelOffer + + pfp.makeOffer(users[5], 100000000, 0, 0); + pfp.getState()->makeOfferChecker(0, users[5], 0, 100000000); + pfp.cancelOffer(users[5], 0); + pfp.getState()->cancelOfferChecker(0); + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + + // createTraditionalAuction + setMemory(utcTime, 0); + utcTime.Year = 2025; + utcTime.Month = 12; + utcTime.Day = 31; + utcTime.Hour = 0; + updateQpiTime(); + pfp.createTraditionalAuction(users[4], 10000000, 0, 0, 26, 1, 1, 0, 26, 1, 5, 0); + pfp.getState()->createAuctionChecker(0, 10000000, 1, 0, users[4], 26, 1, 1, 0, 26, 1, 5, 0); + + // getting the info of Auction + auto getIncomingAuctions_output = pfp.getIncomingAuctions(0, 1); + pfp.getState()->getIncomingAuctionsChecker(getIncomingAuctions_output, 0, 1); + + setMemory(utcTime, 0); + utcTime.Year = 2026; + utcTime.Month = 1; + utcTime.Day = 3; + utcTime.Hour = 0; + updateQpiTime(); + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + + // bidOnAuction + pfp.bidOnTraditionalAuction(users[5], 12000000, 0, 0); + pfp.getState()->bidOnAuctionChecker(users[5], 0, 12000000); + gt_marketFee = div(12000000 * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); + gt_shareHolderFee = div(12000000 * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); + + earnedQubic += gt_marketFee; + collectedShareHolderFee += gt_shareHolderFee; + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + + increaseEnergy(users[6], 110000000); + pfp.bidOnTraditionalAuction(users[6], 110000000, 0, 0); + pfp.getState()->bidOnAuctionChecker(users[6], 0, 110000000); + gt_marketFee = div((110000000 - 12000000) * QBAY_FEE_NFT_SALE_MARKET * 1ULL, 1000ULL); + gt_shareHolderFee = div((110000000 - 12000000) * QBAY_FEE_NFT_SALE_SHAREHOLDERS * 1ULL, 1000ULL); + + earnedQubic += gt_marketFee; + collectedShareHolderFee += gt_shareHolderFee; + + EXPECT_EQ(numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, id(QBAY_CONTRACT_INDEX, 0, 0, 0), id(QBAY_CONTRACT_INDEX, 0, 0, 0), QBAY_CONTRACT_INDEX, QBAY_CONTRACT_INDEX), earnedCFB); + EXPECT_EQ(getBalance(id(QBAY_CONTRACT_INDEX, 0, 0, 0)), earnedQubic + collectedShareHolderFee); + + auto getInfoOfMarketplace_output = pfp.getInfoOfMarketplace(); + pfp.getState()->getInfoOfMarketplaceChecker(getInfoOfMarketplace_output); + + pfp.getState()->profitChecker(earnedQubic, earnedCFB); + + pfp.changeStatusOfMarketPlace(MARKETPLACE_OWNER, 0); + pfp.getState()->stateVriableChecker(cfbPrice, qubicPrice, totalIncommingNFTNumber, numberOfCollectionCreated, numberOfNFTCreated, 0); + + pfp.endEpoch(); + + // increased the amount of MARKETPLACE_OWNER in line 771, so the balance of marketPlaceOwner should be earnedQubic + 1. + EXPECT_EQ(getBalance(MARKETPLACE_OWNER), earnedQubic + 1); + + uint64 numberOfQXCFB = numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, CFB_ISSUER, CFB_ISSUER, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); + EXPECT_EQ(pfp.TransferShareManagementRights(CFB_ISSUER, QBAY_CFB_NAME, QBAY_CONTRACT_INDEX, 10000, CFB_ISSUER), 10000); + EXPECT_EQ(numberOfQXCFB - 10000, numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, CFB_ISSUER, CFB_ISSUER, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); + increaseEnergy(CFB_ISSUER, 1000000); + EXPECT_EQ(pfp.qbayTransferShareManagementRights(CFB_ISSUER, 10000, QX_CONTRACT_INDEX, 1000000).transferredNumberOfShares, 10000); + EXPECT_EQ(numberOfQXCFB, numberOfPossessedShares(QBAY_CFB_NAME, CFB_ISSUER, CFB_ISSUER, CFB_ISSUER, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX)); +} \ No newline at end of file diff --git a/test/contract_qbond.cpp b/test/contract_qbond.cpp new file mode 100644 index 000000000..9eb9d0644 --- /dev/null +++ b/test/contract_qbond.cpp @@ -0,0 +1,444 @@ +#define NO_UEFI + +#include "contract_testing.h" + +std::string assetNameFromInt64(uint64 assetName); +std::string getCurrentMbondIndex(uint16_t epoch) +{ + if (epoch < QBOND_CYCLIC_START_EPOCH) + { + return std::to_string(epoch); + } + else + { + uint16_t index = (epoch - QBOND_CYCLIC_START_EPOCH + 1) % 53 == 0 ? 53 : (epoch - QBOND_CYCLIC_START_EPOCH + 1) % 53; + return index < 10 ? std::string("0").append(std::to_string(index)) : std::to_string(index); + } +} +const id adminAddress = ID(_B, _O, _N, _D, _A, _A, _F, _B, _U, _G, _H, _E, _L, _A, _N, _X, _G, _H, _N, _L, _M, _S, _U, _I, _V, _B, _K, _B, _H, _A, _Y, _E, _Q, _S, _Q, _B, _V, _P, _V, _N, _B, _H, _L, _F, _J, _I, _A, _Z, _F, _Q, _C, _W, _W, _B, _V, _E); +const id testAddress1 = ID(_H, _O, _G, _T, _K, _D, _N, _D, _V, _U, _U, _Z, _U, _F, _L, _A, _M, _L, _V, _B, _L, _Z, _D, _S, _G, _D, _D, _A, _E, _B, _E, _K, _K, _L, _N, _Z, _J, _B, _W, _S, _C, _A, _M, _D, _S, _X, _T, _C, _X, _A, _M, _A, _X, _U, _D, _F); +const id testAddress2 = ID(_E, _Q, _M, _B, _B, _V, _Y, _G, _Z, _O, _F, _U, _I, _H, _E, _X, _F, _O, _X, _K, _T, _F, _T, _A, _N, _E, _K, _B, _X, _L, _B, _X, _H, _A, _Y, _D, _F, _F, _M, _R, _E, _E, _M, _R, _Q, _E, _V, _A, _D, _Y, _M, _M, _E, _W, _A, _C); + +class QBondChecker : public QBOND +{ +public: + int64_t getCFAPopulation() + { + return _commissionFreeAddresses.population(); + } +}; + +class ContractTestingQBond : protected ContractTesting +{ +public: + ContractTestingQBond() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QBOND); + callSystemProcedure(QBOND_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QEARN); + callSystemProcedure(QEARN_CONTRACT_INDEX, INITIALIZE); + } + + QBondChecker* getState() + { + return (QBondChecker*)contractStates[QBOND_CONTRACT_INDEX]; + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(QBOND_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QBOND_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + void stake(const id& staker, const int64_t& quMillions, const int64_t& quAmount) + { + QBOND::Stake_input input{ quMillions }; + QBOND::Stake_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 1, input, output, staker, quAmount); + } + + QBOND::TransferMBondOwnershipAndPossession_output transfer(const id& from, const id& to, const uint16_t& epoch, const int64_t& mbondsAmount, const int64_t& quAmount) + { + QBOND::TransferMBondOwnershipAndPossession_input input{ to, epoch, mbondsAmount }; + QBOND::TransferMBondOwnershipAndPossession_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 2, input, output, from, quAmount); + return output; + } + + QBOND::AddAskOrder_output addAskOrder(const id& asker, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) + { + QBOND::AddAskOrder_input input{ epoch, price, mbondsAmount }; + QBOND::AddAskOrder_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 3, input, output, asker, quAmount); + return output; + } + + QBOND::RemoveAskOrder_output removeAskOrder(const id& asker, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) + { + QBOND::RemoveAskOrder_input input{ epoch, price, mbondsAmount }; + QBOND::RemoveAskOrder_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 4, input, output, asker, quAmount); + return output; + } + + QBOND::AddBidOrder_output addBidOrder(const id& bider, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) + { + QBOND::AddBidOrder_input input{ epoch, price, mbondsAmount }; + QBOND::AddBidOrder_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 5, input, output, bider, quAmount); + return output; + } + + QBOND::RemoveBidOrder_output removeBidOrder(const id& bider, const uint16_t& epoch, const int64_t& price, const int64_t& mbondsAmount, const int64_t& quAmount) + { + QBOND::RemoveBidOrder_input input{ epoch, price, mbondsAmount }; + QBOND::RemoveBidOrder_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 6, input, output, bider, quAmount); + return output; + } + + QBOND::BurnQU_output burnQU(const id& invocator, const int64_t& quToBurn, const int64_t& quAmount) + { + QBOND::BurnQU_input input{ quToBurn }; + QBOND::BurnQU_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 7, input, output, invocator, quAmount); + return output; + } + + bool updateCFA(const id& invocator, const id& address, const bool operation) + { + QBOND::UpdateCFA_input input{ address, operation }; + QBOND::UpdateCFA_output output; + invokeUserProcedure(QBOND_CONTRACT_INDEX, 8, input, output, invocator, 0); + return output.result; + } + + QBOND::GetEarnedFees_output getEarnedFees() + { + QBOND::GetEarnedFees_input input; + QBOND::GetEarnedFees_output output; + callFunction(QBOND_CONTRACT_INDEX, 2, input, output); + return output; + } + + QBOND::GetInfoPerEpoch_output getInfoPerEpoch(const uint16_t& epoch) + { + QBOND::GetInfoPerEpoch_input input{ epoch }; + QBOND::GetInfoPerEpoch_output output; + callFunction(QBOND_CONTRACT_INDEX, 3, input, output); + return output; + } + + QBOND::GetOrders_output getOrders(const uint16_t& epoch, const int64_t& asksOffset, const int64_t& bidsOffset) + { + QBOND::GetOrders_input input{ epoch, asksOffset, bidsOffset }; + QBOND::GetOrders_output output; + callFunction(QBOND_CONTRACT_INDEX, 4, input, output); + return output; + } + + QBOND::GetUserOrders_output getUserOrders(const id& user, const int64_t& asksOffset, const int64_t& bidsOffset) + { + QBOND::GetUserOrders_input input{ user, asksOffset, bidsOffset }; + QBOND::GetUserOrders_output output; + callFunction(QBOND_CONTRACT_INDEX, 5, input, output); + return output; + } + + QBOND::GetMBondsTable_output getMBondsTable() + { + QBOND::GetMBondsTable_input input; + QBOND::GetMBondsTable_output output; + callFunction(QBOND_CONTRACT_INDEX, 6, input, output); + return output; + } + + QBOND::GetUserMBonds_output getUserMBonds(const id& user) + { + QBOND::GetUserMBonds_input input{ user }; + QBOND::GetUserMBonds_output output; + callFunction(QBOND_CONTRACT_INDEX, 7, input, output); + return output; + } + + QBOND::GetCFA_output getCFA() + { + QBOND::GetCFA_input input; + QBOND::GetCFA_output output; + callFunction(QBOND_CONTRACT_INDEX, 8, input, output); + return output; + } +}; + +TEST(ContractQBond, Stake) +{ + system.epoch = QBOND_CYCLIC_START_EPOCH; + ContractTestingQBond qbond; + qbond.beginEpoch(); + + increaseEnergy(testAddress1, 100000000LL); + increaseEnergy(testAddress2, 100000000LL); + + // scenario 1: testAddress1 want to stake 50 millions, but send to sc 30 millions + qbond.stake(testAddress1, 50, 30000000LL); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); + + // scenario 2: testAddress1 want to stake 50 millions, but send to sc 50 millions (without commission) + qbond.stake(testAddress1, 50, 50000000LL); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); + + // scenario 3: testAddress1 want to stake 50 millions and send full amount with commission + qbond.stake(testAddress1, 50, 50250000LL); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 50LL); + + // scenario 4.1: testAddress2 want to stake 5 millions, recieve 0 MBonds, because minimum is 10 and 5 were put in queue + qbond.stake(testAddress2, 5, 5025000); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); + + // scenario 4.2: testAddress1 want to stake 7 millions, testAddress1 recieve 7 MBonds and testAddress2 recieve 5 MBonds, because the total qu millions in the queue became more than 10 + qbond.stake(testAddress1, 7, 7035000); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 57); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 5); +} + + +TEST(ContractQBond, TransferMBondOwnershipAndPossession) +{ + ContractTestingQBond qbond; + qbond.beginEpoch(); + increaseEnergy(testAddress1, 1000000000); + qbond.stake(testAddress1, 50, 50250000); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 50); + + // scenario 1: not enough gas, 100 needed + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 10, 50).transferredMBonds, 0); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); + + // scenario 2: enough gas, not enough mbonds + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 70, 100).transferredMBonds, 0); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); + + // scenario 3: success + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 40, 100).transferredMBonds, 40); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 40); +} + +TEST(ContractQBond, AddRemoveAskOrder) +{ + ContractTestingQBond qbond; + qbond.beginEpoch(); + increaseEnergy(testAddress1, 1000000000); + qbond.stake(testAddress1, 50, 50250000); + + // scenario 1: not enough mbonds + EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1500000, 100, 0).addedMBondsAmount, 0); + + // scenario 2: success to add ask, asked mbonds are blocked and cannot be transferred to another address + EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1500000, 30, 0).addedMBondsAmount, 30); + // not enough free mbonds + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 21, 100).transferredMBonds, 0); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 0); + // successful transfer + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 20, 100).transferredMBonds, 20); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 20); + + // scenario 3: no orders to remove at this price + EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1400000, 30, 0).removedMBondsAmount, 0); + EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1600000, 30, 0).removedMBondsAmount, 0); + + // scenario 4: no free mbonds, then successful removal ask order and transfer to another address + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 1, 100).transferredMBonds, 0); + EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1500000, 5, 0).removedMBondsAmount, 5); + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 5, 100).transferredMBonds, 5); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 25); + + EXPECT_EQ(qbond.removeAskOrder(testAddress1, system.epoch, 1500000, 500, 0).removedMBondsAmount, 25); +} + +TEST(ContractQBond, AddRemoveBidOrder) +{ + ContractTestingQBond qbond; + qbond.beginEpoch(); + increaseEnergy(testAddress1, 1000000000); + increaseEnergy(testAddress2, 1000000000); + qbond.stake(testAddress1, 50, 50250000); + + // scenario 1: not enough qu + EXPECT_EQ(qbond.addBidOrder(testAddress2, system.epoch, 1500000, 10, 100).addedMBondsAmount, 0); + + // scenario 2: success to add bid + EXPECT_EQ(qbond.addBidOrder(testAddress2, system.epoch, 1500000, 10, 15000000).addedMBondsAmount, 10); + + // scenario 3: testAddress1 add ask order which matches the bid order + EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1500000, 3, 0).addedMBondsAmount, 3); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress1, testAddress1, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 47); + EXPECT_EQ(numberOfPossessedShares(assetNameFromString(std::string("MBND").append(getCurrentMbondIndex(system.epoch)).c_str()), id(QBOND_CONTRACT_INDEX, 0, 0, 0), testAddress2, testAddress2, QBOND_CONTRACT_INDEX, QBOND_CONTRACT_INDEX), 3); + + // scenario 3: no orders to remove at this price + EXPECT_EQ(qbond.removeBidOrder(testAddress2, system.epoch, 1400000, 30, 0).removedMBondsAmount, 0); + EXPECT_EQ(qbond.removeBidOrder(testAddress2, system.epoch, 1600000, 30, 0).removedMBondsAmount, 0); + + // scenario 4: successful removal bid order, qu are returned (7 mbonds per 1500000 each) + int64_t prevBalance = getBalance(testAddress2); + EXPECT_EQ(qbond.removeBidOrder(testAddress2, system.epoch, 1500000, 100, 0).removedMBondsAmount, 7); + EXPECT_EQ(getBalance(testAddress2) - prevBalance, 10500000); + + // check earned fees + auto fees = qbond.getEarnedFees(); + EXPECT_EQ(fees.stakeFees, 250000); + EXPECT_EQ(fees.tradeFees, 1350); // 1500000 (MBond price) * 3 (MBonds) * 0.0003 (0.03% fees for trade) + + // getOrders checks + EXPECT_EQ(qbond.addAskOrder(testAddress1, system.epoch, 1600000, 5, 0).addedMBondsAmount, 5); + EXPECT_EQ(qbond.addAskOrder(testAddress2, system.epoch, 1500000, 3, 0).addedMBondsAmount, 3); + EXPECT_EQ(qbond.addBidOrder(testAddress1, system.epoch, 1400000, 10, 14000000).addedMBondsAmount, 10); + EXPECT_EQ(qbond.addBidOrder(testAddress2, system.epoch, 1300000, 5, 6500000).addedMBondsAmount, 5); + + // all orders sorted by price, therefore the element with index 0 contains an order with a price of 1500000 + auto orders = qbond.getOrders(system.epoch, 0, 0); + EXPECT_EQ(orders.askOrders.get(0).epoch, (sint64) QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(orders.askOrders.get(0).numberOfMBonds, 3); + EXPECT_EQ(orders.askOrders.get(0).owner, testAddress2); + EXPECT_EQ(orders.askOrders.get(0).price, 1500000); + + EXPECT_EQ(orders.bidOrders.get(0).epoch, (sint64) QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(orders.bidOrders.get(0).numberOfMBonds, 10); + EXPECT_EQ(orders.bidOrders.get(0).owner, testAddress1); + EXPECT_EQ(orders.bidOrders.get(0).price, 1400000); + + // with offset + orders = qbond.getOrders(system.epoch, 1, 1); + EXPECT_EQ(orders.askOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(orders.askOrders.get(0).numberOfMBonds, 5); + EXPECT_EQ(orders.askOrders.get(0).owner, testAddress1); + EXPECT_EQ(orders.askOrders.get(0).price, 1600000); + + EXPECT_EQ(orders.bidOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(orders.bidOrders.get(0).numberOfMBonds, 5); + EXPECT_EQ(orders.bidOrders.get(0).owner, testAddress2); + EXPECT_EQ(orders.bidOrders.get(0).price, 1300000); + + // user orders + auto userOrders = qbond.getUserOrders(testAddress1, 0, 0); + EXPECT_EQ(userOrders.askOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(userOrders.askOrders.get(0).numberOfMBonds, 5); + EXPECT_EQ(userOrders.askOrders.get(0).owner, testAddress1); + EXPECT_EQ(userOrders.askOrders.get(0).price, 1600000); + + EXPECT_EQ(userOrders.bidOrders.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(userOrders.bidOrders.get(0).numberOfMBonds, 10); + EXPECT_EQ(userOrders.bidOrders.get(0).owner, testAddress1); + EXPECT_EQ(userOrders.bidOrders.get(0).price, 1400000); + + // with offset + userOrders = qbond.getUserOrders(testAddress1, 1, 1); + EXPECT_EQ(userOrders.askOrders.get(0).epoch, 0); + EXPECT_EQ(userOrders.askOrders.get(0).numberOfMBonds, 0); + EXPECT_EQ(userOrders.askOrders.get(0).owner, NULL_ID); + EXPECT_EQ(userOrders.askOrders.get(0).price, 0); + + EXPECT_EQ(userOrders.bidOrders.get(0).epoch, 0); + EXPECT_EQ(userOrders.bidOrders.get(0).numberOfMBonds, 0); + EXPECT_EQ(userOrders.bidOrders.get(0).owner, NULL_ID); + EXPECT_EQ(userOrders.bidOrders.get(0).price, 0); +} + +TEST(ContractQBond, BurnQu) +{ + ContractTestingQBond qbond; + qbond.beginEpoch(); + increaseEnergy(testAddress1, 1000000000); + + // scenario 1: not enough qu + EXPECT_EQ(qbond.burnQU(testAddress1, 1000000, 1000).amount, -1); + + // scenario 2: successful burning + EXPECT_EQ(qbond.burnQU(testAddress1, 1000000, 1000000).amount, 1000000); + + // scenario 3: successful burning, the surplus is returned + int64_t prevBalance = getBalance(testAddress1); + EXPECT_EQ(qbond.burnQU(testAddress1, 1000000, 10000000).amount, 1000000); + EXPECT_EQ(prevBalance - getBalance(testAddress1), 1000000); +} + +TEST(ContractQBond, UpdateCFA) +{ + ContractTestingQBond qbond; + increaseEnergy(testAddress1, 1000); + increaseEnergy(adminAddress, 1000); + + // only adminAddress can update CFA + EXPECT_EQ(qbond.getState()->getCFAPopulation(), 1); + EXPECT_FALSE(qbond.updateCFA(testAddress1, testAddress2, 1)); + EXPECT_EQ(qbond.getState()->getCFAPopulation(), 1); + EXPECT_TRUE(qbond.updateCFA(adminAddress, testAddress2, 1)); + EXPECT_EQ(qbond.getState()->getCFAPopulation(), 2); + + auto cfa = qbond.getCFA(); + EXPECT_EQ(cfa.commissionFreeAddresses.get(0), testAddress2); + EXPECT_EQ(cfa.commissionFreeAddresses.get(1), adminAddress); + EXPECT_EQ(cfa.commissionFreeAddresses.get(2), NULL_ID); + + EXPECT_FALSE(qbond.updateCFA(testAddress1, testAddress2, 0)); + EXPECT_EQ(qbond.getState()->getCFAPopulation(), 2); + EXPECT_TRUE(qbond.updateCFA(adminAddress, testAddress2, 0)); + EXPECT_EQ(qbond.getState()->getCFAPopulation(), 1); +} + +TEST(ContractQBond, GetInfoPerEpoch) +{ + ContractTestingQBond qbond; + qbond.beginEpoch(); + increaseEnergy(testAddress1, 1000000000); + increaseEnergy(testAddress2, 1000000000); + + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 0); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 0); + + qbond.stake(testAddress1, 50, 50250000); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 1); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 50); + + qbond.stake(testAddress2, 100, 100500000); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 2); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 150); + + EXPECT_EQ(qbond.transfer(testAddress1, testAddress2, system.epoch, 50, 100).transferredMBonds, 50); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).stakersAmount, 1); + EXPECT_EQ(qbond.getInfoPerEpoch(system.epoch).totalStaked, 150); +} + +TEST(ContractQBond, GetMBondsTable) +{ + ContractTestingQBond qbond; + qbond.beginEpoch(); + increaseEnergy(testAddress1, 1000000000); + increaseEnergy(testAddress2, 1000000000); + + qbond.stake(testAddress1, 50, 50250000); + qbond.stake(testAddress2, 100, 100500000); + qbond.endEpoch(); + + system.epoch++; + qbond.beginEpoch(); + qbond.stake(testAddress1, 10, 10050000); + qbond.stake(testAddress2, 20, 20100000); + + auto table = qbond.getMBondsTable(); + EXPECT_EQ(table.info.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(table.info.get(1).epoch, (sint64)QBOND_CYCLIC_START_EPOCH + 1); + EXPECT_EQ(table.info.get(2).epoch, 0); + + auto userMBonds = qbond.getUserMBonds(testAddress1); + EXPECT_EQ(userMBonds.totalMBondsAmount, 60); + EXPECT_EQ(userMBonds.mbonds.get(0).epoch, (sint64)QBOND_CYCLIC_START_EPOCH); + EXPECT_EQ(userMBonds.mbonds.get(0).amount, 50); + EXPECT_EQ(userMBonds.mbonds.get(1).epoch, (sint64)QBOND_CYCLIC_START_EPOCH + 1); + EXPECT_EQ(userMBonds.mbonds.get(1).amount, 10); +} diff --git a/test/contract_qduel.cpp b/test/contract_qduel.cpp new file mode 100644 index 000000000..2e615f759 --- /dev/null +++ b/test/contract_qduel.cpp @@ -0,0 +1,1468 @@ +#define NO_UEFI + +#include "contract_testing.h" +#include +#include + +constexpr uint16 PROCEDURE_INDEX_CREATE_ROOM = 1; +constexpr uint16 PROCEDURE_INDEX_CONNECT_ROOM = 2; +constexpr uint16 PROCEDURE_INDEX_SET_PERCENT_FEES = 3; +constexpr uint16 PROCEDURE_INDEX_SET_TTL_HOURS = 4; +constexpr uint16 PROCEDURE_INDEX_DEPOSIT = 5; +constexpr uint16 PROCEDURE_INDEX_WITHDRAW = 6; +constexpr uint16 PROCEDURE_INDEX_CLOSE_ROOM = 7; +constexpr uint16 FUNCTION_INDEX_GET_PERCENT_FEES = 1; +constexpr uint16 FUNCTION_INDEX_GET_ROOMS = 2; +constexpr uint16 FUNCTION_INDEX_GET_TTL_HOURS = 3; +constexpr uint16 FUNCTION_INDEX_GET_USER_PROFILE = 4; +constexpr uint16 FUNCTION_INDEX_GET_LAST_WINNERS = 6; + +static const id QDUEL_TEAM_ADDRESS = + ID(_O, _C, _Z, _W, _N, _J, _S, _N, _R, _U, _Q, _J, _U, _A, _H, _Z, _C, _T, _R, _P, _N, _Y, _W, _G, _G, _E, _F, _C, _X, _B, _A, _V, _F, _O, _P, _R, + _S, _N, _U, _L, _U, _E, _B, _S, _P, _U, _T, _R, _Z, _N, _T, _G, _F, _B, _I, _E); + +class QpiContextUserFunctionCallWithInvocator : public QpiContextFunctionCall +{ +public: + QpiContextUserFunctionCallWithInvocator(unsigned int contractIndex, const id& invocator) + : QpiContextFunctionCall(contractIndex, invocator, 0, USER_FUNCTION_CALL) + {} +}; + +class QDuelChecker : public QDUEL +{ +public: + // Expose read-only accessors for internal state so tests can assert without + // modifying contract storage directly. + uint64 roomCount() const { return rooms.population(); } + id team() const { return teamAddress; } + uint8 ttl() const { return ttlHours; } + uint8 devFee() const { return devFeePercentBps; } + uint8 burnFee() const { return burnFeePercentBps; } + uint8 shareholdersFee() const { return shareholdersFeePercentBps; } + sint64 minDuelAmount() const { return minimumDuelAmount; } + void setState(EState newState) { currentState = newState; } + EState getState() const { return currentState; } + // Helper to fetch user record without exposing contract internals. + bool getUserData(const id& user, UserData& data) const { return users.get(user, data); } + // Directly set a user record to simulate edge-case storage edits. + void setUserData(const UserData& data) { users.set(data.userId, data); } + + RoomInfo firstRoom() const + { + // Map storage can be sparse; walk to first element. + const sint64 index = rooms.nextElementIndex(NULL_INDEX); + if (index == NULL_INDEX) + { + return RoomInfo{}; + } + return rooms.value(index); + } + + bool hasRoom(const id& roomId) const { return rooms.contains(roomId); } + + id computeWinner(const id& player1, const id& player2) const + { + // Run the same winner function as the contract to keep tests deterministic. + QpiContextUserFunctionCall qpi(QDUEL_CONTRACT_INDEX); + GetWinnerPlayer_input input{player1, player2}; + GetWinnerPlayer_output output{}; + GetWinnerPlayer_locals locals{}; + GetWinnerPlayer(qpi, *this, input, output, locals); + return output.winner; + } + + void calculateRevenue(uint64 amount, CalculateRevenue_output& output) const + { + QpiContextUserFunctionCall qpi(QDUEL_CONTRACT_INDEX); + + // Contract helpers require zeroed outputs and locals. + output = {}; + CalculateRevenue_input revenueInput{amount}; + CalculateRevenue_locals revenueLocals{}; + CalculateRevenue(qpi, *this, revenueInput, output, revenueLocals); + } + + GetUserProfile_output getUserProfileFor(const id& user) const + { + QpiContextUserFunctionCallWithInvocator qpi(QDUEL_CONTRACT_INDEX, user); + GetUserProfile_input input{user}; + GetUserProfile_output output{}; + GetUserProfile_locals locals{}; + GetUserProfile(qpi, *this, input, output, locals); + return output; + } +}; + +class ContractTestingQDuel : protected ContractTesting +{ +public: + ContractTestingQDuel() + { + // Build an empty chain state and deploy the contract under test. + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QDUEL); + system.epoch = contractDescriptions[QDUEL_CONTRACT_INDEX].constructionEpoch; + callSystemProcedure(QDUEL_CONTRACT_INDEX, INITIALIZE); + } + + // Access helper for the underlying contract state. + QDuelChecker* state() { return reinterpret_cast(contractStates[QDUEL_CONTRACT_INDEX]); } + + QDUEL::CreateRoom_output createRoom(const id& user, const id& allowedPlayer, sint64 stake, sint64 raiseStep, sint64 maxStake, sint64 reward) + { + QDUEL::CreateRoom_input input{allowedPlayer, stake, raiseStep, maxStake}; + QDUEL::CreateRoom_output output; + // Route through contract procedure to keep call path identical to production. + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_CREATE_ROOM, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + QDUEL::ConnectToRoom_output connectToRoom(const id& user, const id& roomId, sint64 reward) + { + QDUEL::ConnectToRoom_input input{roomId}; + QDUEL::ConnectToRoom_output output; + // Call the user procedure so validation and state updates are exercised. + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_CONNECT_ROOM, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + QDUEL::SetPercentFees_output setPercentFees(const id& user, uint8 devFee, uint8 burnFee, uint8 shareholdersFee, uint16 percentScale, + sint64 reward = 0) + { + QDUEL::SetPercentFees_input input{devFee, burnFee, shareholdersFee, percentScale}; + QDUEL::SetPercentFees_output output; + // System procedures are tested via normal user invocation. + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_PERCENT_FEES, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + QDUEL::SetTTLHours_output setTtlHours(const id& user, uint8 ttlHours, sint64 reward = 0) + { + QDUEL::SetTTLHours_input input{ttlHours}; + QDUEL::SetTTLHours_output output; + // Ensure contract state gets updated through procedure validation. + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_TTL_HOURS, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + QDUEL::GetPercentFees_output getPercentFees() + { + QDUEL::GetPercentFees_input input{}; + QDUEL::GetPercentFees_output output; + // Read-only function call for fee snapshot. + callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_PERCENT_FEES, input, output); + return output; + } + + QDUEL::GetRooms_output getRooms() + { + QDUEL::GetRooms_input input{}; + QDUEL::GetRooms_output output; + // Read-only function call for rooms snapshot. + callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_ROOMS, input, output); + return output; + } + + QDUEL::GetTTLHours_output getTtlHours() + { + QDUEL::GetTTLHours_input input{}; + QDUEL::GetTTLHours_output output; + // Read-only function call for TTL configuration. + callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_TTL_HOURS, input, output); + return output; + } + + QDUEL::GetUserProfile_output getUserProfile(const id& userId) + { + QDUEL::GetUserProfile_input input{userId}; + QDUEL::GetUserProfile_output output; + // Read-only function call for profile by user id. + callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_USER_PROFILE, input, output); + return output; + } + + QDUEL::GetLastWinners_output getLastWinners() + { + QDUEL::GetLastWinners_input input{}; + QDUEL::GetLastWinners_output output; + // Read-only function call for winner history snapshot. + callFunction(QDUEL_CONTRACT_INDEX, FUNCTION_INDEX_GET_LAST_WINNERS, input, output); + return output; + } + + QDUEL::Deposit_output deposit(const id& user, sint64 reward) + { + QDUEL::Deposit_input input{}; + QDUEL::Deposit_output output; + // Deposit is a user procedure that mutates balance and state. + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_DEPOSIT, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + QDUEL::Withdraw_output withdraw(const id& user, sint64 amount, sint64 reward = 0) + { + QDUEL::Withdraw_input input{amount}; + QDUEL::Withdraw_output output; + // Withdraw uses user procedure to enforce validations and limits. + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_WITHDRAW, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + QDUEL::CloseRoom_output closeRoom(const id& user, sint64 reward = 0) + { + QDUEL::CloseRoom_input input{}; + QDUEL::CloseRoom_output output; + if (!invokeUserProcedure(QDUEL_CONTRACT_INDEX, PROCEDURE_INDEX_CLOSE_ROOM, input, output, user, reward)) + { + output.returnCode = QDUEL::toReturnCode(QDUEL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + // Helpers that dispatch system procedures during lifecycle tests. + void endTick() { callSystemProcedure(QDUEL_CONTRACT_INDEX, END_TICK); } + + void endEpoch() { callSystemProcedure(QDUEL_CONTRACT_INDEX, END_EPOCH); } + + void beginEpoch() { callSystemProcedure(QDUEL_CONTRACT_INDEX, BEGIN_EPOCH); } + + // Control time and tick for deterministic tests. + void setTick(uint32 tick) { system.tick = tick; } + uint32 getTick() const { return system.tick; } + + void forceEndTick() + { + // Align tick to update period so END_TICK work executes. + system.tick = system.tick + (QDUEL_TICK_UPDATE_PERIOD - mod(system.tick, static_cast(QDUEL_TICK_UPDATE_PERIOD))); + + endTick(); + } + + void setDeterministicTime(uint16 year = 2025, uint8 month = 1, uint8 day = 1, uint8 hour = 0) + { + // Set a fixed time and reset etalon tick so tests are stable. + setMemory(utcTime, 0); + utcTime.Year = year; + utcTime.Month = month; + utcTime.Day = day; + utcTime.Hour = hour; + utcTime.Minute = 0; + utcTime.Second = 0; + utcTime.Nanosecond = 0; + updateQpiTime(); + etalonTick.prevSpectrumDigest = m256i::zero(); + } +}; + +namespace +{ + bool findPlayersForWinner(ContractTestingQDuel& qduel, bool wantPlayer1Win, id& player1, id& player2) + { + // Brute-force deterministic ids until winner matches desired side. + for (uint64 i = 1; i < 10000; ++i) + { + const id candidate1(i, 0, 0, 0); + const id candidate2(i + 1, 0, 0, 0); + const id winner = qduel.state()->computeWinner(candidate1, candidate2); + if (winner == (wantPlayer1Win ? candidate1 : candidate2)) + { + player1 = candidate1; + player2 = candidate2; + return true; + } + } + return false; + } + + void runFullGameCycleWithFees(ContractTestingQDuel& qduel, const id& player1, const id& player2, const id& expectedWinner) + { + // Setup shareholders so revenue distribution can be validated. + const id shareholder1 = id::randomValue(); + const id shareholder2 = id::randomValue(); + constexpr unsigned int rlSharesOwner1 = 100; + constexpr unsigned int rlSharesOwner2 = 576; + std::vector> rlShares{ + {shareholder1, rlSharesOwner1}, + {shareholder2, rlSharesOwner2}, + }; + issueContractShares(RL_CONTRACT_INDEX, rlShares); + + // Set fees as the team address (contract owner). + constexpr uint8 devFee = 15; + constexpr uint8 burnFee = 30; + constexpr uint8 shareholdersFee = 55; + increaseEnergy(qduel.state()->team(), 1); + EXPECT_EQ(qduel.setPercentFees(qduel.state()->team(), devFee, burnFee, shareholdersFee, QDUEL_PERCENT_SCALE).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + // Setup: give both players enough balance to cover the duel. + constexpr sint64 duelAmount = 100000LL; + increaseEnergy(player1, duelAmount); + increaseEnergy(player2, duelAmount); + const uint64 player1Before = getBalance(player1); + const uint64 player2Before = getBalance(player2); + + const uint64 teamBefore = getBalance(qduel.state()->team()); + const uint64 shareholder1Before = getBalance(shareholder1); + const uint64 shareholder2Before = getBalance(shareholder2); + + // Create room and keep initial balance snapshots for payout assertions. + EXPECT_EQ(qduel.createRoom(player1, NULL_ID, duelAmount, 1, duelAmount, duelAmount).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const uint64 player1AfterCreateRoom = getBalance(player1); + + const id winner = qduel.state()->computeWinner(player1, player2); + EXPECT_EQ(winner, expectedWinner); + + // Calculate expected revenue distribution for fees and winner. + QDUEL::CalculateRevenue_output revenueOutput{}; + qduel.state()->calculateRevenue(duelAmount * 2, revenueOutput); + + // Player 2 joins and triggers finalize logic. + const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(player2, qduel.state()->firstRoom().roomId, duelAmount); + EXPECT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(connectOutput.winner, winner); + + // Check fee distribution for team and shareholders. + EXPECT_EQ(getBalance(qduel.state()->team()), teamBefore + revenueOutput.devFee); + + // Check shareholder dividends across the full set of computors. + const uint64 dividendPerShare = revenueOutput.shareholdersFee / NUMBER_OF_COMPUTORS; + EXPECT_EQ(getBalance(shareholder1), shareholder1Before + dividendPerShare * rlSharesOwner1); + EXPECT_EQ(getBalance(shareholder2), shareholder2Before + dividendPerShare * rlSharesOwner2); + + // Check winner receives the remainder and loser only pays entry. + if (winner == player1) + { + EXPECT_EQ(getBalance(player1), player1AfterCreateRoom + revenueOutput.winner); + EXPECT_EQ(getBalance(player2), player2Before - duelAmount); + } + else + { + EXPECT_EQ(getBalance(player1), player1Before - duelAmount); + EXPECT_EQ(getBalance(player2), (player2Before - duelAmount) + revenueOutput.winner); + } + } +} // namespace + +TEST(ContractQDuel, EndEpochKeepsDepositWhileRoomsRecreatedEachEpoch) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(40, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + const uint64 epochs = 3; + const uint64 reward = stake + (stake * epochs); + increaseEnergy(owner, reward); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + QDUEL::UserData ownerData{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, ownerData)); + uint64 expectedDeposit = ownerData.depositedAmount; + id currentRoomId = ownerData.roomId; + + for (uint32 epoch = 0; epoch < epochs; ++epoch) + { + qduel.beginEpoch(); + qduel.endEpoch(); + qduel.setTick(qduel.getTick() + 1); + + QDUEL::UserData afterEndEpoch{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, afterEndEpoch)); + EXPECT_EQ(afterEndEpoch.depositedAmount, expectedDeposit); + EXPECT_EQ(afterEndEpoch.roomId, currentRoomId); + + qduel.state()->setState(QDUEL::EState::NONE); + + const id opponent(200 + epoch, 0, 0, 0); + increaseEnergy(opponent, stake); + EXPECT_EQ(qduel.connectToRoom(opponent, currentRoomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + ASSERT_TRUE(qduel.state()->getUserData(owner, ownerData)); + EXPECT_NE(ownerData.roomId, currentRoomId); + EXPECT_EQ(ownerData.locked, stake); + expectedDeposit -= stake; + EXPECT_EQ(ownerData.depositedAmount, expectedDeposit); + currentRoomId = ownerData.roomId; + } +} + +TEST(ContractQDuel, BeginEpochKeepsRoomsAndUsers) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + qduel.setDeterministicTime(2022, 4, 13, 0); + + const id owner(1, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Give the owner enough balance to create a room. + increaseEnergy(owner, stake); + + // Create a room and verify it survives epoch transition. + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + QDUEL::UserData userBefore{}; + EXPECT_TRUE(qduel.state()->getUserData(owner, userBefore)); + + // Begin epoch should not wipe persistent data. + qduel.beginEpoch(); + + // Room and user record should still exist after epoch transition. + EXPECT_TRUE(qduel.state()->hasRoom(roomBefore.roomId)); + QDUEL::UserData userAfter{}; + EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); + EXPECT_EQ(userAfter.roomId, roomBefore.roomId); +} + +TEST(ContractQDuel, FirstTickAfterUnlockResetsTimerStart) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + increaseEnergy(qduel.state()->team(), 1); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 23, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + qduel.setDeterministicTime(2022, 4, 13, 0); + + const id owner(2, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund owner so the room creation succeeds. + increaseEnergy(owner, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + const uint64 initialCloseTimer = roomBefore.closeTimer; + const DateAndTime initialLastUpdate = roomBefore.lastUpdate; + + // Locking occurs at epoch start; timers should not advance while locked. + qduel.beginEpoch(); + + // Still locked: no timer or lastUpdate changes. + qduel.setDeterministicTime(2022, 4, 13, 1); + qduel.forceEndTick(); + + const QDUEL::RoomInfo lockedRoom = qduel.state()->firstRoom(); + EXPECT_EQ(lockedRoom.closeTimer, initialCloseTimer); + EXPECT_EQ(lockedRoom.lastUpdate, initialLastUpdate); + + // First unlocked tick: reset lastUpdate to "now" without reducing timer. + qduel.setDeterministicTime(2022, 4, 14, 2); + qduel.forceEndTick(); + + const QDUEL::RoomInfo unlockedRoom = qduel.state()->firstRoom(); + EXPECT_EQ(unlockedRoom.closeTimer, initialCloseTimer); + const DateAndTime expectedNow(2022, 4, 14, 2, 0, 0); + EXPECT_EQ(unlockedRoom.lastUpdate, expectedNow); +} + +TEST(ContractQDuel, EndTickExpiresRoomCreatesNewWhenDepositAvailable) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + increaseEnergy(qduel.state()->team(), 1); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(3, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund owner with enough to re-create room after finalize. + increaseEnergy(owner, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + EXPECT_EQ(qduel.state()->roomCount(), 1ULL); + + // Advance to TTL to trigger finalize and auto room creation. + qduel.setDeterministicTime(2025, 1, 1, 1); + qduel.forceEndTick(); + + // A new room should replace the expired one. + EXPECT_EQ(qduel.state()->roomCount(), 1ULL); + const QDUEL::RoomInfo roomAfter = qduel.state()->firstRoom(); + EXPECT_NE(roomAfter.roomId, roomBefore.roomId); + + QDUEL::UserData userAfter{}; + EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); + // User should be re-bound to the new room with locked stake. + EXPECT_EQ(userAfter.roomId, roomAfter.roomId); + EXPECT_EQ(userAfter.locked, stake); +} + +TEST(ContractQDuel, EndTickExpiresRoomWithoutAvailableDepositRemovesUser) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + increaseEnergy(qduel.state()->team(), 1); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(4, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund owner just enough to create the initial room. + increaseEnergy(owner, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + QDUEL::UserData userData{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, userData)); + // Remove available balance so finalize cannot recreate the room. + userData.depositedAmount = 0; + userData.locked = 0; + qduel.state()->setUserData(userData); + + // Expire room and expect cleanup. + qduel.setDeterministicTime(2025, 1, 1, 1); + qduel.forceEndTick(); + + // Room and user data should be removed when deposit is insufficient. + EXPECT_EQ(qduel.state()->roomCount(), 0ULL); + QDUEL::UserData userAfter{}; + EXPECT_FALSE(qduel.state()->getUserData(owner, userAfter)); +} + +TEST(ContractQDuel, EndTickSkipsNonPeriodTicks) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + increaseEnergy(qduel.state()->team(), 1); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 2, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(5, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund owner to create a room. + increaseEnergy(owner, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + qduel.setDeterministicTime(2025, 1, 1, 1); + qduel.setTick(1); + // Non-period tick: no updates expected. + qduel.endTick(); + + const QDUEL::RoomInfo roomAfterSkipped = qduel.state()->firstRoom(); + EXPECT_EQ(roomAfterSkipped.closeTimer, roomBefore.closeTimer); + EXPECT_EQ(roomAfterSkipped.lastUpdate, roomBefore.lastUpdate); + + // Period tick: updates should apply. + qduel.setTick(QDUEL_TICK_UPDATE_PERIOD); + qduel.endTick(); + + const QDUEL::RoomInfo roomAfterProcessed = qduel.state()->firstRoom(); + // Close timer should have decreased by one hour and lastUpdate bumped. + EXPECT_EQ(roomAfterProcessed.closeTimer, roomBefore.closeTimer - 3600ULL); + const DateAndTime expectedNow(2025, 1, 1, 1, 0, 0); + EXPECT_EQ(roomAfterProcessed.lastUpdate, expectedNow); +} + +TEST(ContractQDuel, LockedStateBlocksCreateAndConnect) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(6, 0, 0, 0); + const id other(7, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund owner to create the baseline room. + increaseEnergy(owner, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + + // Lock contract and verify user procedures are blocked. + qduel.state()->setState(QDUEL::EState::LOCKED); + // Fund the other user so only the lock gate can fail. + increaseEnergy(other, stake); + + EXPECT_EQ(qduel.createRoom(other, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::STATE_LOCKED)); + EXPECT_EQ(qduel.connectToRoom(other, roomBefore.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::STATE_LOCKED)); + // Existing room should remain unchanged. + EXPECT_TRUE(qduel.state()->hasRoom(roomBefore.roomId)); +} + +TEST(ContractQDuel, EndTickRecreatesRoomWithUpdatedStake) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + increaseEnergy(qduel.state()->team(), 1); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(8, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund owner so next stake can be doubled. + increaseEnergy(owner, stake * 2); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 2, 0, stake * 2).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + + // Expire the room and expect a new one using computed next stake. + qduel.setDeterministicTime(2025, 1, 1, 1); + qduel.forceEndTick(); + + const QDUEL::RoomInfo roomAfter = qduel.state()->firstRoom(); + EXPECT_NE(roomAfter.roomId, roomBefore.roomId); + // Amount should reflect the raiseStep applied to the original stake. + EXPECT_EQ(roomAfter.amount, stake * 2); + + QDUEL::UserData userAfter{}; + EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); + // User should be locked into the new room with the updated stake. + EXPECT_EQ(userAfter.roomId, roomAfter.roomId); + EXPECT_EQ(userAfter.locked, stake * 2); + EXPECT_EQ(userAfter.depositedAmount, 0ULL); +} + +TEST(ContractQDuel, ConnectFinalizeIgnoresLockedAmount) +{ + ContractTestingQDuel qduel; + // Start from a deterministic time and unlocked state. + qduel.state()->setState(QDUEL::EState::NONE); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(9, 0, 0, 0); + const id opponent(10, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + // Fund both players so creation and join can proceed. + increaseEnergy(owner, stake); + increaseEnergy(opponent, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + + // On connect, finalize uses includeLocked=false, so owner data is cleared. + EXPECT_EQ(qduel.connectToRoom(opponent, roomBefore.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + // Room is removed and owner record should be purged after finalize. + EXPECT_FALSE(qduel.state()->hasRoom(roomBefore.roomId)); + QDUEL::UserData ownerAfter{}; + EXPECT_FALSE(qduel.state()->getUserData(owner, ownerAfter)); +} + +TEST(ContractQDuel, InitializeDefaults) +{ + ContractTestingQDuel qduel; + + EXPECT_EQ(qduel.state()->team(), QDUEL_TEAM_ADDRESS); + EXPECT_EQ(qduel.state()->minDuelAmount(), static_cast(QDUEL_MINIMUM_DUEL_AMOUNT)); + EXPECT_EQ(qduel.state()->devFee(), QDUEL_DEV_FEE_PERCENT_BPS); + EXPECT_EQ(qduel.state()->burnFee(), QDUEL_BURN_FEE_PERCENT_BPS); + EXPECT_EQ(qduel.state()->shareholdersFee(), QDUEL_SHAREHOLDERS_FEE_PERCENT_BPS); + EXPECT_EQ(qduel.state()->ttl(), QDUEL_TTL_HOURS); + EXPECT_EQ(qduel.state()->getState(), QDUEL::EState::NONE); + EXPECT_EQ(qduel.state()->roomCount(), 0ULL); +} + +TEST(ContractQDuel, CreateRoomStoresRoomAndUser) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + qduel.setDeterministicTime(2025, 1, 1, 0); + + const id owner(11, 0, 0, 0); + const id allowed(12, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + const uint64 reward = stake + 5000; + increaseEnergy(owner, reward); + + EXPECT_EQ(qduel.createRoom(owner, allowed, stake, 2, stake * 3, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + EXPECT_EQ(qduel.state()->roomCount(), 1ULL); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + EXPECT_EQ(room.owner, owner); + EXPECT_EQ(room.allowedPlayer, allowed); + EXPECT_EQ(room.amount, stake); + EXPECT_EQ(room.closeTimer, static_cast(qduel.state()->ttl()) * 3600ULL); + const DateAndTime expectedNow(2025, 1, 1, 0, 0, 0); + EXPECT_EQ(room.lastUpdate, expectedNow); + + QDUEL::UserData user{}; + EXPECT_TRUE(qduel.state()->getUserData(owner, user)); + EXPECT_EQ(user.roomId, room.roomId); + EXPECT_EQ(user.allowedPlayer, allowed); + EXPECT_EQ(user.depositedAmount, reward - stake); + EXPECT_EQ(user.locked, stake); + EXPECT_EQ(user.stake, stake); + EXPECT_EQ(user.raiseStep, 2ULL); + EXPECT_EQ(user.maxStake, stake * 3); +} + +TEST(ContractQDuel, CreateRoomRejectsStakeBelowMinimum) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(13, 0, 0, 0); + const uint64 stake = qduel.state()->minDuelAmount() - 1; + const uint64 reward = qduel.state()->minDuelAmount(); + increaseEnergy(owner, reward); + const uint64 balanceBefore = getBalance(owner); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INVALID_VALUE)); + EXPECT_EQ(getBalance(owner), balanceBefore); + EXPECT_EQ(qduel.state()->roomCount(), 0ULL); +} + +TEST(ContractQDuel, CreateRoomRejectsMaxStakeBelowStake) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(14, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + const uint64 reward = stake; + increaseEnergy(owner, reward); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake - 1, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INVALID_VALUE)); + EXPECT_EQ(qduel.state()->roomCount(), 0ULL); +} + +TEST(ContractQDuel, CreateRoomRejectsRewardBelowMinimum) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(15, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + const uint64 reward = qduel.state()->minDuelAmount() - 1; + increaseEnergy(owner, reward); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); + EXPECT_EQ(qduel.state()->roomCount(), 0ULL); +} + +TEST(ContractQDuel, CreateRoomRejectsRewardBelowStake) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(16, 0, 0, 0); + const uint64 stake = qduel.state()->minDuelAmount() + 1000; + const uint64 reward = stake - 1; + increaseEnergy(owner, reward); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); + EXPECT_EQ(qduel.state()->roomCount(), 0ULL); +} + +TEST(ContractQDuel, CreateRoomRejectsDuplicateUser) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(17, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake * 2); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_ALREADY_EXISTS)); + EXPECT_EQ(qduel.state()->roomCount(), 1ULL); +} + +TEST(ContractQDuel, CreateRoomRejectsWhenRoomsFull) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const sint64 stake = qduel.state()->minDuelAmount(); + for (uint32 i = 0; i < QDUEL_MAX_NUMBER_OF_ROOMS; ++i) + { + const id owner(100 + i, 0, 0, 0); + qduel.setTick(i); + increaseEnergy(owner, stake); + const QDUEL::CreateRoom_output output = qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake); + EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)) << "at[" << i << "]"; + } + + EXPECT_EQ(qduel.state()->roomCount(), static_cast(QDUEL_MAX_NUMBER_OF_ROOMS)); + + const id extraOwner(9999, 0, 0, 0); + increaseEnergy(extraOwner, stake); + EXPECT_EQ(qduel.createRoom(extraOwner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_FULL)); +} + +TEST(ContractQDuel, ConnectToRoomRejectsMissingRoom) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id player(18, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(player, stake); + + EXPECT_EQ(qduel.connectToRoom(player, id(999, 0, 0, 0), stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_NOT_FOUND)); +} + +TEST(ContractQDuel, ConnectToRoomRejectsNotAllowedPlayer) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(19, 0, 0, 0); + const id allowed(20, 0, 0, 0); + const id other(21, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake); + increaseEnergy(other, stake); + + EXPECT_EQ(qduel.createRoom(owner, allowed, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + + EXPECT_EQ(qduel.connectToRoom(other, room.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_ACCESS_DENIED)); +} + +TEST(ContractQDuel, ConnectToRoomRejectsInsufficientReward) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(22, 0, 0, 0); + const id opponent(23, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake); + increaseEnergy(opponent, stake - 1); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + + EXPECT_EQ(qduel.connectToRoom(opponent, room.roomId, stake - 1).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); +} + +TEST(ContractQDuel, ConnectToRoomRefundsExcessRewardForLoser) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + id owner; + id opponent; + ASSERT_TRUE(findPlayersForWinner(qduel, true, owner, opponent)); + + const sint64 stake = qduel.state()->minDuelAmount(); + const uint64 reward = stake + 5000; + increaseEnergy(owner, stake); + increaseEnergy(opponent, reward); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const uint64 opponentBefore = getBalance(opponent); + + const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(opponent, qduel.state()->firstRoom().roomId, reward); + EXPECT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(connectOutput.winner, owner); + EXPECT_EQ(getBalance(opponent), opponentBefore - stake); +} + +TEST(ContractQDuel, ConnectFinalizeCreatesRoomFromDeposit) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(24, 0, 0, 0); + const id opponent(25, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake * 2); + increaseEnergy(opponent, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, 0, stake * 2).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo roomBefore = qduel.state()->firstRoom(); + + qduel.setTick(10); + + EXPECT_EQ(qduel.connectToRoom(opponent, roomBefore.roomId, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + EXPECT_EQ(qduel.state()->roomCount(), 1ULL); + const QDUEL::RoomInfo roomAfter = qduel.state()->firstRoom(); + EXPECT_NE(roomAfter.roomId, roomBefore.roomId); + EXPECT_EQ(roomAfter.owner, owner); + EXPECT_EQ(roomAfter.amount, stake); + + QDUEL::UserData userAfter{}; + EXPECT_TRUE(qduel.state()->getUserData(owner, userAfter)); + EXPECT_EQ(userAfter.roomId, roomAfter.roomId); + EXPECT_EQ(userAfter.locked, stake); + EXPECT_EQ(userAfter.depositedAmount, 0ULL); +} + +TEST(ContractQDuel, GetWinnerPlayerIsOrderInvariant) +{ + ContractTestingQDuel qduel; + qduel.setTick(1234); + + const id player1(26, 0, 0, 0); + const id player2(27, 0, 0, 0); + + const id winnerForward = qduel.state()->computeWinner(player1, player2); + const id winnerReverse = qduel.state()->computeWinner(player2, player1); + EXPECT_EQ(winnerForward, winnerReverse); + EXPECT_TRUE(winnerForward == player1 || winnerForward == player2); +} + +TEST(ContractQDuel, CalculateRevenueMatchesExpectedSplits) +{ + ContractTestingQDuel qduel; + + constexpr uint64 amount = 1000000ULL; + QDUEL::CalculateRevenue_output output{}; + qduel.state()->calculateRevenue(amount, output); + + const uint64 expectedDev = (amount * qduel.state()->devFee()) / QDUEL_PERCENT_SCALE; + const uint64 expectedBurn = (amount * qduel.state()->burnFee()) / QDUEL_PERCENT_SCALE; + const uint64 expectedShareholders = ((amount * qduel.state()->shareholdersFee()) / QDUEL_PERCENT_SCALE) / 676ULL * 676ULL; + const uint64 expectedWinner = amount - (expectedDev + expectedBurn + expectedShareholders); + + EXPECT_EQ(output.devFee, expectedDev); + EXPECT_EQ(output.burnFee, expectedBurn); + EXPECT_EQ(output.shareholdersFee, expectedShareholders); + EXPECT_EQ(output.winner, expectedWinner); +} + +TEST(ContractQDuel, SetPercentFeesAccessDeniedAndGetPercentFees) +{ + ContractTestingQDuel qduel; + const QDUEL::GetPercentFees_output before = qduel.getPercentFees(); + + static constexpr sint64 userAmount = 10LL; + static constexpr uint8 devFee = 1; + static constexpr uint8 burnFee = 2; + static constexpr uint8 shareholdersFee = 3; + static constexpr uint16 percentScale = 4; + static constexpr sint64 reward = 10LL; + + const id user(28, 0, 0, 0); + increaseEnergy(user, userAmount); + const uint64 balanceBefore = getBalance(user); + + EXPECT_EQ(qduel.setPercentFees(user, devFee, burnFee, shareholdersFee, percentScale, reward).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::ACCESS_DENIED)); + EXPECT_EQ(getBalance(user), balanceBefore); + + const QDUEL::GetPercentFees_output after = qduel.getPercentFees(); + EXPECT_EQ(memcmp(&before, &after, sizeof(before)), 0); +} + +TEST(ContractQDuel, SetPercentFeesUpdatesState) +{ + ContractTestingQDuel qduel; + + static constexpr sint64 teamAmount = 1LL; + static constexpr uint8 devFee = 1; + static constexpr uint8 burnFee = 2; + static constexpr uint8 shareholdersFee = 3; + static constexpr uint16 percentScale = 4; + static constexpr sint64 reward = 1LL; + + increaseEnergy(qduel.state()->team(), teamAmount); + EXPECT_EQ(qduel.setPercentFees(qduel.state()->team(), devFee, burnFee, shareholdersFee, percentScale, reward).returnCode, + QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::GetPercentFees_output output = qduel.getPercentFees(); + EXPECT_EQ(output.devFeePercentBps, devFee); + EXPECT_EQ(output.burnFeePercentBps, burnFee); + EXPECT_EQ(output.shareholdersFeePercentBps, shareholdersFee); + EXPECT_EQ(static_cast(output.percentScale), static_cast(percentScale)); + EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); +} + +TEST(ContractQDuel, SetTTLHoursAccessDenied) +{ + ContractTestingQDuel qduel; + const uint8 ttlBefore = qduel.state()->ttl(); + + const id user(29, 0, 0, 0); + increaseEnergy(user, 5); + const uint64 balanceBefore = getBalance(user); + + EXPECT_EQ(qduel.setTtlHours(user, 5, 5).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ACCESS_DENIED)); + EXPECT_EQ(getBalance(user), balanceBefore); + EXPECT_EQ(qduel.state()->ttl(), ttlBefore); +} + +TEST(ContractQDuel, SetTTLHoursUpdatesState) +{ + ContractTestingQDuel qduel; + increaseEnergy(qduel.state()->team(), 1); + + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 6, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::GetTTLHours_output output = qduel.getTtlHours(); + EXPECT_EQ(output.ttlHours, 6); + EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); +} + +TEST(ContractQDuel, SetTTLHoursResetsCloseTimerForAllRooms) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + qduel.setDeterministicTime(2025, 1, 1, 0); + + increaseEnergy(qduel.state()->team(), 2); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 23, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const id owner1(300, 0, 0, 0); + const id owner2(301, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner1, stake); + increaseEnergy(owner2, stake); + + EXPECT_EQ(qduel.createRoom(owner1, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(qduel.createRoom(owner2, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + // Spend one hour so rooms have less than initial TTL left. + qduel.setDeterministicTime(2025, 1, 1, 1); + qduel.setTick(QDUEL_TICK_UPDATE_PERIOD); + qduel.endTick(); + + // Apply new TTL and force-reset all existing room timers to it. + qduel.setDeterministicTime(2025, 1, 1, 2); + EXPECT_EQ(qduel.setTtlHours(qduel.state()->team(), 1, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::GetRooms_output roomsOutput = qduel.getRooms(); + const DateAndTime expectedNow(2025, 1, 1, 2, 0, 0); + uint64 seenRooms = 0; + for (uint32 i = 0; i < QDUEL_MAX_NUMBER_OF_ROOMS; ++i) + { + const QDUEL::RoomInfo room = roomsOutput.rooms.get(i); + if (room.roomId != id::zero()) + { + ++seenRooms; + EXPECT_EQ(room.closeTimer, 3600ULL); + EXPECT_EQ(room.lastUpdate, expectedNow); + } + } + EXPECT_EQ(seenRooms, 2ULL); +} + +TEST(ContractQDuel, GetRoomsReturnsActiveRooms) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner1(30, 0, 0, 0); + const id owner2(31, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner1, stake); + increaseEnergy(owner2, stake); + + EXPECT_EQ(qduel.createRoom(owner1, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(qduel.createRoom(owner2, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::GetRooms_output output = qduel.getRooms(); + EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + uint64 count = 0; + bool foundOwner1 = false; + bool foundOwner2 = false; + for (uint32 i = 0; i < QDUEL_MAX_NUMBER_OF_ROOMS; ++i) + { + const QDUEL::RoomInfo room = output.rooms.get(i); + if (room.roomId != id::zero()) + { + ++count; + EXPECT_TRUE(qduel.state()->hasRoom(room.roomId)); + if (room.owner == owner1) + { + foundOwner1 = true; + } + if (room.owner == owner2) + { + foundOwner2 = true; + } + } + } + EXPECT_EQ(count, qduel.state()->roomCount()); + EXPECT_TRUE(foundOwner1); + EXPECT_TRUE(foundOwner2); +} + +TEST(ContractQDuel, GetUserProfileReportsUserData) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id missingUser(3200, 0, 0, 0); + const QDUEL::GetUserProfile_output& missing = qduel.getUserProfile(missingUser); + EXPECT_EQ(missing.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); + + const id owner(32, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake + 200); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 2, stake * 2, stake + 200).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::GetUserProfile_output profile = qduel.state()->getUserProfileFor(owner); + EXPECT_EQ(profile.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(profile.depositedAmount, 200ULL); + EXPECT_EQ(profile.locked, stake); + EXPECT_EQ(profile.stake, stake); + EXPECT_EQ(profile.raiseStep, 2ULL); + EXPECT_EQ(profile.maxStake, stake * 2); + EXPECT_NE(profile.roomId, id::zero()); +} + +TEST(ContractQDuel, DepositValidationsAndUpdatesBalance) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id missingUser(33, 0, 0, 0); + increaseEnergy(missingUser, 1); + EXPECT_EQ(qduel.deposit(missingUser, 0).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INVALID_VALUE)); + + increaseEnergy(missingUser, 100); + const uint64 missingBefore = getBalance(missingUser); + EXPECT_EQ(qduel.deposit(missingUser, 100).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); + EXPECT_EQ(getBalance(missingUser), missingBefore); + + const id owner(34, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake); + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + QDUEL::UserData before{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, before)); + increaseEnergy(owner, 500); + EXPECT_EQ(qduel.deposit(owner, 500).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + QDUEL::UserData after{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, after)); + EXPECT_EQ(after.depositedAmount, before.depositedAmount + 500); +} + +TEST(ContractQDuel, WithdrawValidationsAndTransfers) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id missingUser(35, 0, 0, 0); + increaseEnergy(missingUser, 1); + EXPECT_EQ(qduel.withdraw(missingUser, 1).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); + + const id owner(36, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake + 1000); + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake + 1000).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + EXPECT_EQ(qduel.withdraw(owner, 0).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INSUFFICIENT_FREE_DEPOSIT)); + EXPECT_EQ(qduel.withdraw(owner, 2000).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::INSUFFICIENT_FREE_DEPOSIT)); + + const uint64 balanceBefore = getBalance(owner); + EXPECT_EQ(qduel.withdraw(owner, 500).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(getBalance(owner), balanceBefore + 500); + + QDUEL::UserData userAfter{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, userAfter)); + EXPECT_EQ(userAfter.depositedAmount, 500ULL); +} + +TEST(ContractQDuel, CloseRoomReturnsFundsAndRemovesRoomAndUser) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(360, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + const sint64 deposit = 700; + const sint64 reward = stake + deposit; + increaseEnergy(owner, reward); + const uint64 ownerBalanceBeforeCreate = getBalance(owner); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + ASSERT_NE(room.roomId, id::zero()); + ASSERT_TRUE(qduel.state()->hasRoom(room.roomId)); + + const uint64 ownerBalanceAfterCreate = getBalance(owner); + EXPECT_EQ(ownerBalanceAfterCreate, ownerBalanceBeforeCreate - reward); + + EXPECT_EQ(qduel.closeRoom(owner).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_FALSE(qduel.state()->hasRoom(room.roomId)); + + QDUEL::UserData ownerData{}; + EXPECT_FALSE(qduel.state()->getUserData(owner, ownerData)); + EXPECT_EQ(getBalance(owner), ownerBalanceBeforeCreate); +} + +TEST(ContractQDuel, CloseRoomUserNotFound) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id user(361, 0, 0, 0); + increaseEnergy(user, 50); + const uint64 balanceBefore = getBalance(user); + + EXPECT_EQ(qduel.closeRoom(user, 50).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::USER_NOT_FOUND)); + EXPECT_EQ(getBalance(user), balanceBefore); +} + +TEST(ContractQDuel, CloseRoomAccessDeniedWhenUserPointsToForeignRoom) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(362, 0, 0, 0); + const id attacker(363, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo ownerRoom = qduel.state()->firstRoom(); + ASSERT_NE(ownerRoom.roomId, id::zero()); + + // Inject an inconsistent user record that points to someone else's room. + QDUEL::UserData forged{}; + forged.userId = attacker; + forged.roomId = ownerRoom.roomId; + forged.allowedPlayer = NULL_ID; + forged.depositedAmount = 123; + forged.locked = 456; + forged.stake = stake; + forged.raiseStep = 1; + forged.maxStake = stake; + qduel.state()->setUserData(forged); + + increaseEnergy(attacker, 25); + const uint64 attackerBefore = getBalance(attacker); + EXPECT_EQ(qduel.closeRoom(attacker, 25).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_ACCESS_DENIED)); + EXPECT_EQ(getBalance(attacker), attackerBefore); + + // Nothing should be deleted on access denied. + EXPECT_TRUE(qduel.state()->hasRoom(ownerRoom.roomId)); + QDUEL::UserData attackerAfter{}; + EXPECT_TRUE(qduel.state()->getUserData(attacker, attackerAfter)); +} + +TEST(ContractQDuel, CloseRoomSucceedsWhenRoomMissingAndStillRefundsAndRemovesUser) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id owner(364, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + const sint64 deposit = 900; + const sint64 reward = stake + deposit; + increaseEnergy(owner, reward); + const uint64 ownerBalanceBeforeCreate = getBalance(owner); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, reward).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + // Simulate stale user->room pointer (room missing in map). + QDUEL::UserData ownerData{}; + ASSERT_TRUE(qduel.state()->getUserData(owner, ownerData)); + ownerData.roomId = id(999999, 0, 0, 0); + qduel.state()->setUserData(ownerData); + + EXPECT_EQ(qduel.closeRoom(owner).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_FALSE(qduel.state()->getUserData(owner, ownerData)); + EXPECT_EQ(getBalance(owner), ownerBalanceBeforeCreate); +} + +TEST(ContractQDuel, CloseRoomWithZeroFundsRemovesUserWithoutTransfer) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const id user(365, 0, 0, 0); + increaseEnergy(user, 1); + + QDUEL::UserData data{}; + data.userId = user; + data.roomId = id(888888, 0, 0, 0); + data.allowedPlayer = NULL_ID; + data.depositedAmount = 0; + data.locked = 0; + data.stake = qduel.state()->minDuelAmount(); + data.raiseStep = 1; + data.maxStake = data.stake; + qduel.state()->setUserData(data); + + const uint64 balanceBefore = getBalance(user); + EXPECT_EQ(qduel.closeRoom(user).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + EXPECT_EQ(getBalance(user), balanceBefore); + + QDUEL::UserData after{}; + EXPECT_FALSE(qduel.state()->getUserData(user, after)); +} + +TEST(ContractQDuel, ConnectToRoomDistributesFeesPlayer1Wins) +{ + ContractTestingQDuel qduel; + + id player1; + id player2; + ASSERT_TRUE(findPlayersForWinner(qduel, true, player1, player2)); + runFullGameCycleWithFees(qduel, player1, player2, player1); +} + +TEST(ContractQDuel, ConnectToRoomDistributesFeesPlayer2Wins) +{ + ContractTestingQDuel qduel; + + id player1; + id player2; + ASSERT_TRUE(findPlayersForWinner(qduel, false, player1, player2)); + runFullGameCycleWithFees(qduel, player1, player2, player2); +} + +TEST(ContractQDuel, GetLastWinnersReturnsEmptyHistoryByDefault) +{ + ContractTestingQDuel qduel; + + static const QDUEL::WinnerData emptyWinnerData{}; + + const QDUEL::GetLastWinners_output output = qduel.getLastWinners(); + EXPECT_EQ(output.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + for (uint64 i = 0; i < QDUEL_MAX_NUMBER_OF_WINNER; ++i) + { + const QDUEL::WinnerData& winnerData = output.winners.get(i); + EXPECT_EQ(memcmp(&winnerData, &emptyWinnerData, sizeof(QDUEL::WinnerData)), 0); + } +} + +TEST(ContractQDuel, GetLastWinnersStoresWinnerAfterSuccessfulDuel) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + static const QDUEL::WinnerData emptyWinnerData{}; + + const id owner(5000, 0, 0, 0); + const id opponent(5001, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake); + increaseEnergy(opponent, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + EXPECT_FALSE(isZero(room.roomId)); + + const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(opponent, room.roomId, stake); + EXPECT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + QDUEL::CalculateRevenue_output revenueOutput{}; + qduel.state()->calculateRevenue(static_cast(stake * 2), revenueOutput); + + const QDUEL::GetLastWinners_output winnersOutput = qduel.getLastWinners(); + EXPECT_EQ(winnersOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + const QDUEL::WinnerData& firstWinner = winnersOutput.winners.get(0); + EXPECT_EQ(firstWinner.player1, owner); + EXPECT_EQ(firstWinner.player2, opponent); + EXPECT_EQ(firstWinner.winner, connectOutput.winner); + EXPECT_EQ(firstWinner.revenue, revenueOutput.winner); + + for (uint64 i = 1; i < QDUEL_MAX_NUMBER_OF_WINNER; ++i) + { + const QDUEL::WinnerData& winnerData = winnersOutput.winners.get(i); + EXPECT_EQ(memcmp(&winnerData, &emptyWinnerData, sizeof(QDUEL::WinnerData)), 0); + } +} + +TEST(ContractQDuel, GetLastWinnersIgnoresFailedConnectAttempts) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + static const QDUEL::WinnerData emptyWinnerData{}; + + const id owner(5100, 0, 0, 0); + const id opponent(5101, 0, 0, 0); + const sint64 stake = qduel.state()->minDuelAmount(); + increaseEnergy(owner, stake); + increaseEnergy(opponent, stake - 1); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + ASSERT_NE(room.roomId, id::zero()); + + const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(opponent, room.roomId, stake - 1); + EXPECT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::ROOM_INSUFFICIENT_DUEL_AMOUNT)); + + const QDUEL::GetLastWinners_output winnersOutput = qduel.getLastWinners(); + EXPECT_EQ(winnersOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + for (uint64 i = 0; i < QDUEL_MAX_NUMBER_OF_WINNER; ++i) + { + const QDUEL::WinnerData& winnerData = winnersOutput.winners.get(i); + EXPECT_EQ(memcmp(&winnerData, &emptyWinnerData, sizeof(QDUEL::WinnerData)), 0); + } +} + +TEST(ContractQDuel, GetLastWinnersWrapsAroundAfterCapacity) +{ + ContractTestingQDuel qduel; + qduel.state()->setState(QDUEL::EState::NONE); + + const sint64 stake = qduel.state()->minDuelAmount(); + std::array expectedBySlot; + static constexpr uint64 rounds = QDUEL_MAX_NUMBER_OF_WINNER + 2; + + for (uint64 round = 0; round < rounds; ++round) + { + const id owner(6000 + round, 0, 0, 0); + const id opponent(0, 6000 + round, 0, 0); + increaseEnergy(owner, stake); + increaseEnergy(opponent, stake); + + EXPECT_EQ(qduel.createRoom(owner, NULL_ID, stake, 1, stake, stake).returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + const QDUEL::RoomInfo room = qduel.state()->firstRoom(); + EXPECT_FALSE(isZero(room.roomId)); + + const QDUEL::ConnectToRoom_output connectOutput = qduel.connectToRoom(opponent, room.roomId, stake); + ASSERT_EQ(connectOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + QDUEL::CalculateRevenue_output revenueOutput{}; + qduel.state()->calculateRevenue(static_cast(stake * 2), revenueOutput); + + QDUEL::WinnerData expected{}; + expected.player1 = owner; + expected.player2 = opponent; + expected.winner = connectOutput.winner; + expected.revenue = revenueOutput.winner; + expectedBySlot[round % QDUEL_MAX_NUMBER_OF_WINNER] = expected; + } + + const QDUEL::GetLastWinners_output winnersOutput = qduel.getLastWinners(); + EXPECT_EQ(winnersOutput.returnCode, QDUEL::toReturnCode(QDUEL::EReturnCode::SUCCESS)); + + for (uint64 i = 0; i < QDUEL_MAX_NUMBER_OF_WINNER; ++i) + { + const QDUEL::WinnerData winnerData = winnersOutput.winners.get(i); + EXPECT_EQ(memcmp(&winnerData, &expectedBySlot[i], sizeof(QDUEL::WinnerData)), 0); + } +} diff --git a/test/contract_qearn.cpp b/test/contract_qearn.cpp new file mode 100644 index 000000000..330814de3 --- /dev/null +++ b/test/contract_qearn.cpp @@ -0,0 +1,983 @@ +#define NO_UEFI + +#include +#include + +#include "contract_testing.h" + +#define PRINT_TEST_INFO 0 + +// test config: +// - 0 is fastest +// - 1 to enable more tests with random lock/unlock +// - 2 to enable even more tests with random lock/unlock +// - 3 to also check values more often (expensive functions) +// - 4 to also test out-of-user error +#define LARGE_SCALE_TEST 0 + + +static const id QEARN_CONTRACT_ID(QEARN_CONTRACT_INDEX, 0, 0, 0); + +static std::mt19937_64 rand64; + +static id getUser(unsigned long long i); +static unsigned long long random(unsigned long long maxValue); + +static std::vector fullyUnlockedAmount; +static std::vector fullyUnlockedUser; + + +class QearnChecker : public QEARN +{ +public: + void checkLockerArray(bool beforeEndEpoch, bool printInfo = false) + { + // check that locker array is in consistent state + std::map epochTotalLocked; + uint32 minEpoch = 0xffff; + uint32 maxEpoch = 0; + for (uint64 idx = 0; idx < locker.capacity(); ++idx) + { + const QEARN::LockInfo& lock = locker.get(idx); + if (lock._lockedAmount == 0) + { + EXPECT_TRUE(isZero(lock.ID)); + EXPECT_EQ(lock._lockedEpoch, 0); + } + else + { + EXPECT_GT(lock._lockedAmount, QEARN_MINIMUM_LOCKING_AMOUNT); + EXPECT_LE(lock._lockedAmount, QEARN_MAX_LOCK_AMOUNT); + EXPECT_FALSE(isZero(lock.ID)); + const QEARN::EpochIndexInfo& epochRange = _epochIndex.get(lock._lockedEpoch); + EXPECT_GE(idx, epochRange.startIndex); + EXPECT_LT(idx, epochRange.endIndex); + epochTotalLocked[lock._lockedEpoch] += lock._lockedAmount; + + minEpoch = std::min(minEpoch, lock._lockedEpoch); + maxEpoch = std::max(minEpoch, lock._lockedEpoch); + } + } + + const uint32 beginEpoch = std::max((int)contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch, system.epoch - 52); + EXPECT_LE(beginEpoch, minEpoch); + EXPECT_LE(maxEpoch, uint32(system.epoch)); + + if (PRINT_TEST_INFO) + { + const char * beforeAfterStr = (beforeEndEpoch) ? "Before" : "After"; + std::cout << "--- " << beforeAfterStr << " END_EPOCH in epoch " << system.epoch << std::endl; + } + + for (uint32 epoch = beginEpoch; epoch <= system.epoch; ++epoch) + { + const QEARN::RoundInfo& currentRoundInfo = _currentRoundInfo.get(epoch); + //if (!currentRoundInfo._Epoch_Bonus_Amount && !currentRoundInfo._Total_Locked_Amount) + // continue; + unsigned long long totalLocked = epochTotalLocked[epoch]; + if (printInfo) + { + std::cout << "Total locked amount in epoch " << epoch << " = " << totalLocked << ", total bonus " << currentRoundInfo._epochBonusAmount << std::endl; + } + if (beforeEndEpoch || epoch != system.epoch - 52) + EXPECT_EQ(currentRoundInfo._totalLockedAmount, totalLocked); + } + + // check that old epoch indices have been reset + for (uint32 epoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; epoch < beginEpoch; ++epoch) + { + EXPECT_EQ(this->_epochIndex.get(epoch).startIndex, this->_epochIndex.get(epoch).endIndex); + } + } + + void checkGetUnlockedInfo(uint32 epoch) + { + fullyUnlockedAmount.clear(); + fullyUnlockedUser.clear(); + + const QEARN::EpochIndexInfo& epochIndex = _epochIndex.get(epoch); + for(uint64 idx = epochIndex.startIndex; idx < epochIndex.endIndex; ++idx) + { + if(locker.get(idx)._lockedAmount != 0) + { + fullyUnlockedAmount.push_back(locker.get(idx)._lockedAmount); + fullyUnlockedUser.push_back(locker.get(idx).ID); + } + } + } + + void checkFullyUnlockedAmount() + { + for(uint32 idx = 0; idx < _fullyUnlockedCnt; idx++) + { + const QEARN::HistoryInfo& FullyUnlockedInfo = fullyUnlocker.get(idx); + + EXPECT_EQ(fullyUnlockedAmount[idx], FullyUnlockedInfo._unlockedAmount); + EXPECT_EQ(fullyUnlockedUser[idx], FullyUnlockedInfo._unlockedID); + } + } + + void checkStatsPerEpoch(getBurnedAndBoostedStatsPerEpoch_output result, uint16 epoch) + { + EXPECT_EQ(result.boostedAmount, statsInfo.get(epoch).boostedAmount); + EXPECT_EQ(result.burnedAmount, statsInfo.get(epoch).burnedAmount); + EXPECT_EQ(result.rewardedAmount, statsInfo.get(epoch).rewardedAmount); + EXPECT_EQ(result.boostedPercent, div(result.boostedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount)); + EXPECT_EQ(result.burnedPercent, div(result.burnedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount)); + EXPECT_EQ(result.rewardedPercent, div(result.rewardedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount)); + } + + void checkStatsForAll(getBurnedAndBoostedStats_output result) + { + uint64 totalBurnedAmountInSC = 0; + uint64 totalBoostedAmountInSC = 0; + uint64 totalRewardedAmountInSC = 0; + uint64 sumBurnedPercent = 0; + uint64 sumBoostedPercent = 0; + uint64 sumRewardedPercent = 0; + + for(uint32 epoch = 138 ; epoch < system.epoch; epoch++) + { + totalBurnedAmountInSC += statsInfo.get(epoch).burnedAmount; + totalBoostedAmountInSC += statsInfo.get(epoch).boostedAmount; + totalRewardedAmountInSC += statsInfo.get(epoch).rewardedAmount; + + sumBurnedPercent += div(statsInfo.get(epoch).burnedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount); + sumBoostedPercent += div(statsInfo.get(epoch).boostedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount); + sumRewardedPercent += div(statsInfo.get(epoch).rewardedAmount * 10000000, _initialRoundInfo.get(epoch)._epochBonusAmount); + } + + EXPECT_EQ(result.boostedAmount, totalBoostedAmountInSC); + EXPECT_EQ(result.burnedAmount, totalBurnedAmountInSC); + EXPECT_EQ(result.rewardedAmount, totalRewardedAmountInSC); + EXPECT_EQ(result.averageBoostedPercent, div(sumBoostedPercent, system.epoch - 138ULL)); + EXPECT_EQ(result.averageBurnedPercent, div(sumBurnedPercent, system.epoch - 138ULL)); + EXPECT_EQ(result.averageRewardedPercent, div(sumRewardedPercent, system.epoch - 138ULL)); + } + + QEARN::EpochIndexInfo getEpochIndex(uint32 epoch) const + { + return _epochIndex.get(epoch); + } +}; + +class ContractTestingQearn : protected ContractTesting +{ + struct UnlockTableEntry + { + unsigned long long rewardPercent; + unsigned long long burnPercent; + }; + std::vector epochChangesToUnlockParams; + +public: + ContractTestingQearn() + { + INIT_CONTRACT(QEARN); + initEmptySpectrum(); + rand64.seed(42); + + for (unsigned int epChanges = 0; epChanges <= 52; ++epChanges) + { + if (epChanges <= 4) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 0, 0 }); + else if (epChanges <= 12) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 5, 45 }); + else if (epChanges <= 16) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 10, 45 }); + else if (epChanges <= 20) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 15, 40 }); + else if (epChanges <= 24) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 20, 40 }); + else if (epChanges <= 28) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 25, 35 }); + else if (epChanges <= 32) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 30, 35 }); + else if (epChanges <= 36) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 35, 35 }); + else if (epChanges <= 40) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 40, 30 }); + else if (epChanges <= 44) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 45, 30 }); + else if (epChanges <= 48) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 50, 30 }); + else if (epChanges <= 52) + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 55, 25 }); + else + epochChangesToUnlockParams.push_back(UnlockTableEntry{ 100, 0 }); + } + } + + QearnChecker* getState() + { + return (QearnChecker*)contractStates[QEARN_CONTRACT_INDEX]; + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(QEARN_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + + // If there is no entry for this epoch in allEpochData, create one with default init (all 0) + allEpochData[system.epoch]; + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QEARN_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + QEARN::getLockInfoPerEpoch_output getLockInfoPerEpoch(uint16 epoch) const + { + QEARN::getLockInfoPerEpoch_input input{ epoch }; + QEARN::getLockInfoPerEpoch_output output; + callFunction(QEARN_CONTRACT_INDEX, 1, input, output); + return output; + } + + uint64 getUserLockedInfo(uint16 epoch, const id& user) const + { + QEARN::getUserLockedInfo_input input; + input.epoch = epoch; + input.user = user; + QEARN::getUserLockedInfo_output output; + callFunction(QEARN_CONTRACT_INDEX, 2, input, output); + return output.lockedAmount; + } + + uint32 getStateOfRound(uint16 epoch) const + { + QEARN::getStateOfRound_input input{ epoch }; + QEARN::getStateOfRound_output output; + callFunction(QEARN_CONTRACT_INDEX, 3, input, output); + return output.state; + } + + uint64 getUserLockStatus(const id& user) const + { + QEARN::getUserLockStatus_input input{ user }; + QEARN::getUserLockStatus_output output; + callFunction(QEARN_CONTRACT_INDEX, 4, input, output); + return output.status; + } + + QEARN::getEndedStatus_output getEndedStatus(const id& user) const + { + QEARN::getEndedStatus_input input{ user }; + QEARN::getEndedStatus_output output; + callFunction(QEARN_CONTRACT_INDEX, 5, input, output); + return output; + } + + QEARN::getStatsPerEpoch_output getStatsPerEpoch(uint16 epoch) const + { + QEARN::getStatsPerEpoch_input input{ epoch }; + QEARN::getStatsPerEpoch_output output; + callFunction(QEARN_CONTRACT_INDEX, 6, input, output); + return output; + } + + QEARN::getBurnedAndBoostedStats_output getBurnedAndBoostedStats() const + { + QEARN::getBurnedAndBoostedStats_input input; + QEARN::getBurnedAndBoostedStats_output output; + callFunction(QEARN_CONTRACT_INDEX, 7, input, output); + return output; + } + + QEARN::getBurnedAndBoostedStatsPerEpoch_output getBurnedAndBoostedStatsPerEpoch(uint16 epoch) const + { + QEARN::getBurnedAndBoostedStatsPerEpoch_input input{ epoch }; + QEARN::getBurnedAndBoostedStatsPerEpoch_output output; + callFunction(QEARN_CONTRACT_INDEX, 8, input, output); + return output; + } + + sint32 lock(const id& user, long long amount, bool expectSuccess = true) + { + QEARN::lock_input input; + QEARN::lock_output output; + EXPECT_EQ(invokeUserProcedure(QEARN_CONTRACT_INDEX, 1, input, output, user, amount), expectSuccess); + return output.returnCode; + } + + sint32 unlock(const id& user, long long amount, uint16 lockedEpoch, bool expectSuccess = true) + { + QEARN::unlock_input input; + input.amount = amount; + input.lockedEpoch = lockedEpoch; + QEARN::unlock_output output; + EXPECT_EQ(invokeUserProcedure(QEARN_CONTRACT_INDEX, 2, input, output, user, 0), expectSuccess); + return output.returnCode; + } + + struct UserData + { + std::map locked; + }; + + std::map allUserData; + + struct EpochData + { + unsigned long long initialBonusAmount; + unsigned long long initialTotalLockedAmount; + unsigned long long bonusAmount; + unsigned long long amountCurrentlyLocked; + }; + + std::map allEpochData; + + std::map amountUnlockPerUser; + + void simulateDonation(const unsigned long long donationAmount) + { + increaseEnergy(QEARN_CONTRACT_ID, donationAmount); + + unsigned long long& totalBonusAmount = allEpochData[system.epoch + 1].bonusAmount; + totalBonusAmount += donationAmount; + if (totalBonusAmount > QEARN_MAX_BONUS_AMOUNT) + totalBonusAmount = QEARN_MAX_BONUS_AMOUNT; + } + + bool lockAndCheck(const id& user, uint64 amountLock, bool expectSuccess = true) + { + // check consistency of epoch info expected vs returned by contract + checkEpochInfo(system.epoch); + + // get amount and balances before action +#if LARGE_SCALE_TEST >= 3 + uint64 amountBefore = getUserLockedInfo(system.epoch, user); + EXPECT_EQ(allUserData[user].locked[system.epoch], amountBefore); +#else + uint64 amountBefore = allUserData[user].locked[system.epoch]; +#endif + sint64 userBalanceBefore = getBalance(user); + sint64 contractBalanceBefore = getBalance(QEARN_CONTRACT_ID); + + // call lock prcoedure + uint32 retCode = lock(user, amountLock, expectSuccess); + + // check new amount and balances + uint64 amountAfter = getUserLockedInfo(system.epoch, user); + sint64 userBalanceAfter = getBalance(user); + sint64 contractBalanceAfter = getBalance(QEARN_CONTRACT_ID); + if (retCode == QEARN_LOCK_SUCCESS && expectSuccess) + { + EXPECT_EQ(amountAfter, amountBefore + amountLock); + EXPECT_EQ(userBalanceAfter, userBalanceBefore - amountLock); + EXPECT_EQ(contractBalanceAfter, contractBalanceBefore + amountLock); + + allUserData[user].locked[system.epoch] += amountLock; + allEpochData[system.epoch].amountCurrentlyLocked += amountLock; + allEpochData[system.epoch].initialTotalLockedAmount += amountLock; + } + else + { + EXPECT_EQ(amountAfter, amountBefore); + EXPECT_EQ(userBalanceAfter, userBalanceBefore); + EXPECT_EQ(contractBalanceAfter, contractBalanceBefore); + } + + if (!expectSuccess) + return false; + + // check return code + if (retCode != QEARN_OVERFLOW_USER) + { + if (amountLock < QEARN_MINIMUM_LOCKING_AMOUNT || system.epoch < QEARN_INITIAL_EPOCH) + { + EXPECT_EQ(retCode, QEARN_INVALID_INPUT_AMOUNT); + } + else if (amountBefore + amountLock > QEARN_MAX_LOCK_AMOUNT) + { + EXPECT_EQ(retCode, QEARN_LIMIT_LOCK); + } + } + + return retCode == QEARN_LOCK_SUCCESS; + } + + unsigned long long getAndCheckRewardFactorTenmillionth(uint16 epoch) const + { + auto edIt = allEpochData.find(epoch); + EXPECT_NE(edIt, allEpochData.end()); + const EpochData& ed = edIt->second; + const unsigned long long rewardFactorTenmillionth = QPI::div(ed.bonusAmount * 10000000ULL, ed.amountCurrentlyLocked); + if (rewardFactorTenmillionth) + { + // detect overflow in computation of rewardFactorTenmillionth + const double rewardFactorTenmillionthDouble = ed.bonusAmount * 10000000.0 / ed.amountCurrentlyLocked; + double arthmeticError = double(rewardFactorTenmillionth) - rewardFactorTenmillionthDouble; + EXPECT_LT(fabs(arthmeticError), 1e5); + } + + return rewardFactorTenmillionth; + } + + void checkEpochInfo(uint16 epoch) + { + const auto scEpochInfo = getLockInfoPerEpoch(epoch); + EXPECT_LE(scEpochInfo.currentBonusAmount, QEARN_MAX_BONUS_AMOUNT); + if (epoch < QEARN_INITIAL_EPOCH) + return; + auto edIt = allEpochData.find(epoch); + EXPECT_NE(edIt, allEpochData.end()); + const EpochData& ed = edIt->second; + EXPECT_EQ(getAndCheckRewardFactorTenmillionth(epoch), scEpochInfo.yield); + EXPECT_EQ(ed.bonusAmount, scEpochInfo.currentBonusAmount); + EXPECT_EQ(ed.amountCurrentlyLocked, scEpochInfo.currentLockedAmount); + + const auto scStatsInfo = getStatsPerEpoch(epoch); + + EXPECT_EQ(scStatsInfo.earlyUnlockedAmount, ed.initialTotalLockedAmount - ed.amountCurrentlyLocked); + EXPECT_EQ(scStatsInfo.earlyUnlockedPercent, QPI::div((ed.initialTotalLockedAmount - ed.amountCurrentlyLocked) * 10000, ed.initialTotalLockedAmount)); + + const auto scBurnedAndBoostedStatsPerEpoch = getBurnedAndBoostedStatsPerEpoch(epoch); + const auto scBurnedAndBoostedStatsForAllEpoch = getBurnedAndBoostedStats(); + + getState()->checkStatsPerEpoch(scBurnedAndBoostedStatsPerEpoch, epoch); + getState()->checkStatsForAll(scBurnedAndBoostedStatsForAllEpoch); + + uint64 averageAPY = 0; + uint32 cnt = 0; + for(uint16 t = system.epoch - 1; t >= system.epoch - 52; t--) + { + auto preEdIt = allEpochData.find(t); + const EpochData& preED = preEdIt->second; + if (t < QEARN_INITIAL_EPOCH) + { + break; + } + if(preED.amountCurrentlyLocked == 0) + { + continue; + } + + cnt++; + EXPECT_EQ(getLockInfoPerEpoch(t).currentLockedAmount, preED.amountCurrentlyLocked); + averageAPY += QPI::div(preED.bonusAmount * 10000000ULL, preED.amountCurrentlyLocked); + } + EXPECT_EQ(scStatsInfo.totalLockedAmount, getBalance(QEARN_CONTRACT_ID)); + EXPECT_EQ(scStatsInfo.averageAPY, QPI::div(averageAPY, cnt * 1ULL)); + } + + bool unlockAndCheck(const id& user, uint16 lockingEpoch, uint64 amountUnlock, bool expectSuccess = true) + { + // make sure that user exists in spectrum + increaseEnergy(user, 1); + + // get old locked amount +#if LARGE_SCALE_TEST >= 3 + uint64 amountBefore = getUserLockedInfo(lockingEpoch, user); + EXPECT_EQ(allUserData[user].locked[lockingEpoch], amountBefore); +#else + uint64 amountBefore = allUserData[user].locked[lockingEpoch]; +#endif + sint64 userBalanceBefore = getBalance(user); + sint64 contractBalanceBefore = getBalance(QEARN_CONTRACT_ID); + + // call unlock prcoedure + uint32 retCode = unlock(user, amountUnlock, lockingEpoch); + + // check new locked amount and balances + uint64 amountAfter = getUserLockedInfo(lockingEpoch, user); + sint64 userBalanceAfter = getBalance(user); + sint64 contractBalanceAfter = getBalance(QEARN_CONTRACT_ID); + if (retCode == QEARN_UNLOCK_SUCCESS && expectSuccess) + { + EXPECT_GE(amountBefore, amountUnlock); + uint64 expectedAmountAfter = amountBefore - amountUnlock; + if (expectedAmountAfter < QEARN_MINIMUM_LOCKING_AMOUNT) + { + expectedAmountAfter = 0; + } + EXPECT_EQ(amountAfter, expectedAmountAfter); + uint64 amountUnlocked = amountBefore - amountAfter; + + uint16 epochTransitions = system.epoch - lockingEpoch; + unsigned long long rewardFactorTenmillionth = getAndCheckRewardFactorTenmillionth(lockingEpoch); + unsigned long long commonFactor = QPI::div(rewardFactorTenmillionth * amountUnlocked, 100ULL); + unsigned long long amountReward = QPI::div(commonFactor * epochChangesToUnlockParams[epochTransitions].rewardPercent, 10000000ULL); + unsigned long long amountBurn = QPI::div(commonFactor * epochChangesToUnlockParams[epochTransitions].burnPercent, 10000000ULL); + { + // Check for overflows + double commonFactorError = fabs((double(rewardFactorTenmillionth) * double(amountUnlocked) / 100.0) - commonFactor); + EXPECT_LT(commonFactorError, 1e3); + double amountRewardError = fabs((double(commonFactor) * double(epochChangesToUnlockParams[epochTransitions].rewardPercent) / 10000000.0) - amountReward); + EXPECT_LE(amountRewardError, 1); + double amountBurnError = fabs((double(commonFactor) * double(epochChangesToUnlockParams[epochTransitions].burnPercent) / 10000000.0) - amountBurn); + EXPECT_LE(amountBurnError, 1); + } + + allUserData[user].locked[lockingEpoch] -= amountUnlocked; + if(system.epoch == lockingEpoch) + { + allEpochData[lockingEpoch].initialTotalLockedAmount -= amountUnlocked; + } + allEpochData[lockingEpoch].amountCurrentlyLocked -= amountUnlocked; + allEpochData[lockingEpoch].bonusAmount -= amountReward + amountBurn; + + // Edge case of unlocking of all locked funds in previous epoch -> bonus added to next round + if (lockingEpoch != system.epoch && !allEpochData[lockingEpoch].amountCurrentlyLocked) + { + amountBurn += allEpochData[lockingEpoch].bonusAmount; + allEpochData[lockingEpoch].bonusAmount = 0; + } + + EXPECT_EQ(userBalanceAfter, userBalanceBefore + amountUnlocked + amountReward); + EXPECT_EQ(contractBalanceAfter, contractBalanceBefore - amountUnlocked - amountReward - amountBurn); + + // Check consistency of epoch info expected vs returned by contract + checkEpochInfo(lockingEpoch); + + // getEndedStatus() only included Early_Unlocked_Amount if unlocked after locking epoch + if (lockingEpoch != system.epoch) + { + amountUnlockPerUser[user] += amountUnlocked; + } + } + else + { + EXPECT_EQ(amountAfter, amountBefore); + EXPECT_EQ(userBalanceAfter, userBalanceBefore); + EXPECT_EQ(contractBalanceAfter, contractBalanceBefore); + } + + return retCode == QEARN_UNLOCK_SUCCESS; + } + + void endEpochAndCheck() + { + // check getStateOfRound + uint16 payoutEpoch = system.epoch - 52; + EXPECT_EQ(getStateOfRound(QEARN_INITIAL_EPOCH - 1), 2); + EXPECT_EQ(getStateOfRound(payoutEpoch - 1), 2); + EXPECT_EQ(getStateOfRound(payoutEpoch), (payoutEpoch >= QEARN_INITIAL_EPOCH) ? 1 : 2); + EXPECT_EQ(getStateOfRound(system.epoch - 1), (system.epoch - 1 >= QEARN_INITIAL_EPOCH) ? 1 : 2); + EXPECT_EQ(getStateOfRound(system.epoch), (system.epoch >= QEARN_INITIAL_EPOCH) ? 1 : 2); + EXPECT_EQ(getStateOfRound(system.epoch + 1), (system.epoch + 1 >= QEARN_INITIAL_EPOCH) ? 0 : 2); + + // test getUserLockStatus() + { + id user = getUser(random(10)); + uint64 lockStatus = getUserLockStatus(user); + const auto userDataIt = allUserData.find(user); + if (userDataIt == allUserData.end()) + { + EXPECT_EQ(lockStatus, 0); + } + else + { + auto& userData = userDataIt->second; + for (int i = 0; i <= 52; ++i) + { + if (lockStatus & 1) + { + EXPECT_GT(userData.locked[system.epoch - i], 0ll); + } + else + { + EXPECT_EQ(userData.locked[system.epoch - i], 0ll); + } + lockStatus >>= 1; + } + } + } + + // check unlocked amounts returned by getEndedStatus() + for (const auto& userAmountPairs : amountUnlockPerUser) + { + QEARN::getEndedStatus_output endedStatus = getEndedStatus(userAmountPairs.first); + EXPECT_EQ(userAmountPairs.second, endedStatus.earlyUnlockedAmount); + } + + checkEpochInfo(system.epoch); + + bool beforeEndEpoch = true; + getState()->checkLockerArray(beforeEndEpoch, PRINT_TEST_INFO); + getState()->checkGetUnlockedInfo(payoutEpoch); + + // get entity balances to check payout in END_EPOCH + std::map oldUserBalance; + long long oldContractBalance = getBalance(QEARN_CONTRACT_ID); + for (const auto& userIdDataPair : allUserData) + { + const id& user = userIdDataPair.first; + oldUserBalance[user] = getBalance(user); + } + checkEpochInfo(payoutEpoch); + + amountUnlockPerUser.clear(); + endEpoch(); + + // check payout after END_EPOCH + bool expectPayout = (allEpochData.find(payoutEpoch) != allEpochData.end()); + checkEpochInfo(payoutEpoch); + if (expectPayout) + { + // compute and check expected payouts + unsigned long long rewardFactorTenmillionth = getAndCheckRewardFactorTenmillionth(payoutEpoch); + unsigned long long totalRewardsPaid = 0; + const EpochData& ed = allEpochData[payoutEpoch]; + + for (const auto& userIdBalancePair : oldUserBalance) + { + const id& user = userIdBalancePair.first; + const long long oldUserBalance = userIdBalancePair.second; + const UserData& userData = allUserData[user]; + + auto userLockedAmountIter = userData.locked.find(payoutEpoch); + if (userLockedAmountIter == userData.locked.end() || userLockedAmountIter->second == 0) + continue; + const long long userLockedAmount = userLockedAmountIter->second; + const unsigned long long userReward = userLockedAmount * rewardFactorTenmillionth / 10000000ULL; + if (rewardFactorTenmillionth) + EXPECT_EQ((userLockedAmount * rewardFactorTenmillionth) / rewardFactorTenmillionth, userLockedAmount); + totalRewardsPaid += userReward; + + EXPECT_EQ(oldUserBalance + userLockedAmount + userReward, getBalance(user)); + } + EXPECT_EQ(oldContractBalance - ed.bonusAmount - ed.amountCurrentlyLocked, getBalance(QEARN_CONTRACT_ID)); + + + // all the bonus that has not been paid is burned (remainder due to inaccurate arithmetic and full bonus if nothing is locked until the end) + EXPECT_LE(totalRewardsPaid, ed.bonusAmount); + if (ed.amountCurrentlyLocked && ed.bonusAmount) + EXPECT_GE(QPI::div(totalRewardsPaid * 1000, ed.bonusAmount), 998); // only small part of bonus should be burned + else + EXPECT_EQ(totalRewardsPaid, 0ull); + } + else + { + // no payout expected + for (const auto& userIdBalancePair : oldUserBalance) + { + const id& user = userIdBalancePair.first; + const long long oldUserBalance = userIdBalancePair.second; + const long long currentUserBalance = getBalance(user); + EXPECT_EQ(oldUserBalance, currentUserBalance); + } + EXPECT_EQ(oldContractBalance, getBalance(QEARN_CONTRACT_ID)); + } + + beforeEndEpoch = false; + getState()->checkLockerArray(beforeEndEpoch, PRINT_TEST_INFO); + getState()->checkFullyUnlockedAmount(); + } +}; + + +static id getUser(unsigned long long i) +{ + return id(i, i / 2 + 4, i + 10, i * 3 + 8); +} + +static unsigned long long random(unsigned long long maxValue) +{ + return rand64() % (maxValue + 1); +} + +static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) +{ + unsigned long long userCount = random(maxNum); + std::vector users; + users.reserve(userCount); + for (unsigned int i = 0; i < userCount; ++i) + { + unsigned long long userIdx = random(totalUsers - 1); + users.push_back(getUser(userIdx)); + } + return users; +} + + +TEST(TestContractQearn, ErrorChecking) +{ + ContractTestingQearn qearn; + id user(1, 2, 3, 4); + + system.epoch = QEARN_INITIAL_EPOCH - 1; + + qearn.beginEpoch(); + + // special test case: trying to lock/unlock before QEARN_INITIAL_EPOCH must fail + { + id user2(98765, 43, 2, 1); + increaseEnergy(user2, QEARN_MAX_LOCK_AMOUNT); + EXPECT_FALSE(qearn.lockAndCheck(user2, QEARN_MAX_LOCK_AMOUNT)); + EXPECT_EQ(qearn.unlock(user2, QEARN_MAX_LOCK_AMOUNT, system.epoch), QEARN_INVALID_INPUT_LOCKED_EPOCH); + } + + qearn.endEpoch(); + + system.epoch = QEARN_INITIAL_EPOCH; + + qearn.beginEpoch(); + + // test cases, for which procedures is not executed: + { + // 1. non-existing entities = invalid ID) + EXPECT_FALSE(qearn.lockAndCheck(id::zero(), QEARN_MAX_LOCK_AMOUNT, false)); + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MAX_LOCK_AMOUNT, false)); + + // 2. valid ID but negative amount / insufficient balance + increaseEnergy(user, 1); + EXPECT_FALSE(qearn.lockAndCheck(user, -10, false)); + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT, false)); + } + + // test cases, for which procedure is executed (valid ID, enough balance) + increaseEnergy(user, QEARN_MAX_LOCK_AMOUNT * 1000); + { + EXPECT_FALSE(qearn.lockAndCheck(user, 0)); + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT / 2)); + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT - 1)); + + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MAX_LOCK_AMOUNT + 1)); + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MAX_LOCK_AMOUNT * 2)); + } + + // in order trigger out-of-lock-slots error, lock with many users +#if LARGE_SCALE_TEST >= 4 + // notes: - disabled by default because it takes long + // - seems like the last locker slot is never used in QEARN (FIXME) + for (uint64 i = 0; i < QEARN_MAX_LOCKS - 1; ++i) + { + id otherUser(i, 42, 1234, 642); + long long amount = QEARN_MINIMUM_LOCKING_AMOUNT + (7 * i) % (QEARN_MAX_LOCK_AMOUNT - QEARN_MINIMUM_LOCKING_AMOUNT); + increaseEnergy(otherUser, amount); + EXPECT_TRUE(qearn.lockAndCheck(otherUser, amount)); + } + EXPECT_FALSE(qearn.lockAndCheck(user, QEARN_MINIMUM_LOCKING_AMOUNT)); +#endif + + // note: lock implements no checking of system.epoch + + // for unlock, successfully lock some funds + id otherUser(1, 42, 1234, 642); + long long amount = QEARN_MINIMUM_LOCKING_AMOUNT; + increaseEnergy(otherUser, amount); + EXPECT_TRUE(qearn.lockAndCheck(otherUser, amount)); + + // unlock with too high amount + EXPECT_EQ(qearn.unlock(otherUser, QEARN_MAX_LOCK_AMOUNT + 1, system.epoch), QEARN_INVALID_INPUT_UNLOCK_AMOUNT); + + // unlock with too low amount + EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT - 1, system.epoch), QEARN_INVALID_INPUT_AMOUNT); + + // unlock with wrong user + EXPECT_EQ(qearn.unlock(user, QEARN_MINIMUM_LOCKING_AMOUNT, system.epoch), QEARN_EMPTY_LOCKED); + + // unlock with wrong epoch + EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT, 1), QEARN_INVALID_INPUT_LOCKED_EPOCH); + EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT, QEARN_MAX_EPOCHS + 1), QEARN_INVALID_INPUT_LOCKED_EPOCH); + + // finally, test success case + EXPECT_EQ(qearn.unlock(otherUser, QEARN_MINIMUM_LOCKING_AMOUNT, system.epoch), QEARN_UNLOCK_SUCCESS); +} + +// Test case for gap removal logic in overflow check (lines 635-656 in Qearn.h) +// This test verifies that when the locker array is near capacity and contains gaps, +// attempting to lock triggers gap removal, allowing the lock to succeed. +// Note: This test is disabled by default because it requires filling many slots (QEARN_MAX_LOCKS - 1) +// Enable with LARGE_SCALE_TEST >= 4 to run this comprehensive test + +#if LARGE_SCALE_TEST >= 4 +TEST(TestContractQearn, GapRemovalOnOverflow) +{ + std::cout << "gap removal test. If you want to test this case as soon, please set the QEARN_MAX_LOCKS to a smaller value on the contract." << std::endl; + ContractTestingQearn qearn; + + system.epoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; + qearn.beginEpoch(); + qearn.endEpoch(); + + system.epoch = QEARN_INITIAL_EPOCH; + + qearn.beginEpoch(); + + // Create a scenario where we fill up the locker array and create gaps + // Strategy: Fill up to near capacity, unlock some to create gaps, + // then try to lock again which triggers gap removal + + const uint64 numGapsToCreate = 100; // Create some gaps by unlocking + // Fill up to QEARN_MAX_LOCKS - 1 so that after unlocking (which doesn't change endIndex), + // the next lock attempt will trigger the overflow check (endIndex >= QEARN_MAX_LOCKS - 1) + const uint64 targetEndIndex = QEARN_MAX_LOCKS - 1; + + std::vector usersToUnlock; + usersToUnlock.reserve(numGapsToCreate); + + // Step 1: Fill up the array to near capacity + // We'll fill up to targetEndIndex, then unlock some to create gaps + // The endIndex will stay high, so when we try to lock again, it will trigger overflow check + for (uint64 i = 0; i < targetEndIndex; ++i) + { + id testUser(i, 100, 200, 300); + uint64 amount = QEARN_MINIMUM_LOCKING_AMOUNT + 1; + increaseEnergy(testUser, amount); + EXPECT_TRUE(qearn.lockAndCheck(testUser, amount)); + + // Store some users to unlock later (to create gaps) + if (i < numGapsToCreate) + { + usersToUnlock.push_back(testUser); + } + } + + // Step 2: Verify we're near capacity + QearnChecker* state = qearn.getState(); + uint32 endIndexBeforeUnlock = state->getEpochIndex(system.epoch).endIndex; + EXPECT_GE(endIndexBeforeUnlock, targetEndIndex); + + // Step 3: Unlock some users to create gaps in the locker array + // Note: endIndex doesn't decrease when unlocking, so gaps are created but endIndex stays high + for (const auto& userToUnlock : usersToUnlock) + { + uint64 unlockAmount = QEARN_MINIMUM_LOCKING_AMOUNT + 1; + EXPECT_EQ(qearn.unlock(userToUnlock, unlockAmount, system.epoch), QEARN_UNLOCK_SUCCESS); + } + + // Step 4: Verify endIndex is still high (gaps created but not removed yet) + uint32 endIndexAfterUnlock = state->getEpochIndex(system.epoch).endIndex; + EXPECT_EQ(endIndexAfterUnlock, endIndexBeforeUnlock); // endIndex doesn't change on unlock + + // Step 5: Try to lock one more user - this should trigger overflow check and gap removal + // After gap removal, the lock should succeed because we created gaps earlier + id finalUser(targetEndIndex + 1, 100, 200, 300); + uint64 finalAmount = QEARN_MINIMUM_LOCKING_AMOUNT + 1; + increaseEnergy(finalUser, finalAmount); + + // The lock should succeed after gap removal + sint32 retCode = qearn.lock(finalUser, finalAmount); + + // Verify that gap removal happened and lock succeeded + // After gap removal, endIndex should be less than QEARN_MAX_LOCKS - 1 + uint32 endIndexAfterGapRemoval = state->getEpochIndex(system.epoch).endIndex; + + // The lock should succeed because gaps were removed + EXPECT_EQ(retCode, QEARN_LOCK_SUCCESS); + EXPECT_EQ(endIndexAfterGapRemoval, QEARN_MAX_LOCKS - numGapsToCreate); + EXPECT_LT(endIndexAfterGapRemoval, QEARN_MAX_LOCKS - 1); + EXPECT_LT(endIndexAfterGapRemoval, endIndexAfterUnlock); // endIndex should decrease after gap removal + + // Verify the locker array is consistent after gap removal + qearn.getState()->checkLockerArray(true, false); + + // Verify the final user's lock was successful + EXPECT_EQ(qearn.getUserLockedInfo(system.epoch, finalUser), finalAmount); + + qearn.endEpoch(); +} +#endif + +void testRandomLockWithoutUnlock(const uint16 numEpochs, const unsigned int totalUsers, const unsigned int maxUserLocking) +{ + std::cout << "random test without early unlock for " << numEpochs << " epochs with " << totalUsers << " total users and up to " << maxUserLocking << " lock calls per epoch" << std::endl; + ContractTestingQearn qearn; + + const uint16 firstEpoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; + const uint16 lastEpoch = firstEpoch + numEpochs; + + // first epoch is without donation/bonus + for (system.epoch = firstEpoch; system.epoch <= lastEpoch; ++system.epoch) + { + // invoke BEGIN_EPOCH + qearn.beginEpoch(); + + // simulate a random additional donation during the epoch + qearn.simulateDonation(random(ISSUANCE_RATE / 2)); + + // locking + auto lockUsers = getRandomUsers(totalUsers, maxUserLocking); + for (const auto& user : lockUsers) + { + // get random amount for locking and make sure that user has enough qus (may be invalid amount for locking) + uint64 amountLock = random(QEARN_MAX_LOCK_AMOUNT * 4 / 3); + increaseEnergy(user, amountLock); + + qearn.lockAndCheck(user, amountLock); + } + + // invoke END_EPOCH and check correct payouts + qearn.endEpochAndCheck(); + + // send revenue donation to qearn contract (happens after END_EPOCH but before system.epoch is incremented and before BEGIN_EPOCH + qearn.simulateDonation(random(ISSUANCE_RATE)); + } +} + +TEST(TestContractQearn, RandomLockWithoutUnlock) +{ + // params: epochs, total number of users, max users locking in epoch + testRandomLockWithoutUnlock(100, 40, 10); + testRandomLockWithoutUnlock(100, 20, 20); +#if LARGE_SCALE_TEST >= 1 + testRandomLockWithoutUnlock(300, 1000, 1000); +#endif +#if LARGE_SCALE_TEST >= 2 + testRandomLockWithoutUnlock(100, 20000, 10000); +#endif +} + +void testRandomLockWithUnlock(const uint16 numEpochs, const unsigned int totalUsers, const unsigned int maxUserLocking, const unsigned int maxUserUnlocking) +{ + std::cout << "random test with early unlock for " << numEpochs << " epochs with " << totalUsers << " total users, up to " << maxUserLocking << " lock calls (per epoch), and up to " << maxUserUnlocking << " unlock calls (per running round)" << std::endl; + ContractTestingQearn qearn; + + const uint16 firstEpoch = contractDescriptions[QEARN_CONTRACT_INDEX].constructionEpoch; + const uint16 lastEpoch = firstEpoch + numEpochs; + + for (system.epoch = firstEpoch; system.epoch <= lastEpoch; ++system.epoch) + { + // invoke BEGIN_EPOCH + qearn.beginEpoch(); + + // simulate a random additional donation during the epoch + qearn.simulateDonation(random(ISSUANCE_RATE / 2)); + + // locking + auto lockUsers = getRandomUsers(totalUsers, maxUserLocking); + for (const auto& user : lockUsers) + { + // get random amount for locking and make sure that user has enough qus (may be invalid amount for locking) + uint64 amountLock = random(QEARN_MAX_LOCK_AMOUNT * 4 / 3); + increaseEnergy(user, amountLock); + + qearn.lockAndCheck(user, amountLock); + } + + // unlocking + auto unlockUsers = getRandomUsers(totalUsers, maxUserUnlocking); + for (const auto& user : unlockUsers) + { + for (sint32 lockedEpoch = system.epoch; lockedEpoch >= system.epoch - 52; lockedEpoch--) + { + uint64 amountUnlock = random(qearn.allUserData[user].locked[lockedEpoch] * 11 / 10); + qearn.unlockAndCheck(user, lockedEpoch, amountUnlock); + } + } + + // invoke END_EPOCH and check correct payouts + qearn.endEpochAndCheck(); + + // send revenue donation to qearn contract (happens after END_EPOCH but before system.epoch is incremented and before BEGIN_EPOCH + qearn.simulateDonation(random(ISSUANCE_RATE)); + } +} + +TEST(TestContractQearn, RandomLockAndUnlock) +{ + // params: epochs, total number of users, max users locking in epoch, maxUserUnlocking + testRandomLockWithUnlock(100, 40, 10, 10); + testRandomLockWithUnlock(100, 40, 10, 8); // less early unlocking + testRandomLockWithUnlock(100, 40, 20, 19); // more user activity +#if LARGE_SCALE_TEST >= 1 + testRandomLockWithUnlock(300, 1000, 1000, 1000); + testRandomLockWithUnlock(300, 1000, 1000, 800); +#endif +#if LARGE_SCALE_TEST >= 2 + testRandomLockWithUnlock(400, 2000, 1500, 1200); + testRandomLockWithUnlock(100, 20000, 10000, 8000); +#endif +} diff --git a/test/contract_qip.cpp b/test/contract_qip.cpp new file mode 100644 index 000000000..034fd9b1e --- /dev/null +++ b/test/contract_qip.cpp @@ -0,0 +1,1444 @@ +#define NO_UEFI + +#include "contract_testing.h" + +static constexpr uint64 QIP_ISSUE_ASSET_FEE = 1000000000ull; +static constexpr uint64 QIP_TRANSFER_ASSET_FEE = 100ull; +static constexpr uint64 QIP_TRANSFER_RIGHTS_FEE = 100ull; + +static const id QIP_CONTRACT_ID(QIP_CONTRACT_INDEX, 0, 0, 0); + +const id QIP_testIssuer = ID(_T, _E, _S, _T, _I, _S, _S, _U, _E, _R, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T); +const id QIP_testAddress1 = ID(_A, _D, _D, _R, _A, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); +const id QIP_testAddress2 = ID(_A, _D, _D, _R, _B, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); +const id QIP_testAddress3 = ID(_A, _D, _D, _R, _C, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); +const id QIP_testBuyer = ID(_B, _U, _Y, _E, _R, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y); + +class QIPChecker : public QIP +{ +public: + uint32 getNumberOfICO() const { return numberOfICO; } + + void checkICOInfo(const QIP::getICOInfo_output& output, const QIP::createICO_input& input, const id& creator) + { + EXPECT_EQ(output.creatorOfICO, creator); + EXPECT_EQ(output.issuer, input.issuer); + EXPECT_EQ(output.address1, input.address1); + EXPECT_EQ(output.address2, input.address2); + EXPECT_EQ(output.address3, input.address3); + EXPECT_EQ(output.address4, input.address4); + EXPECT_EQ(output.address5, input.address5); + EXPECT_EQ(output.address6, input.address6); + EXPECT_EQ(output.address7, input.address7); + EXPECT_EQ(output.address8, input.address8); + EXPECT_EQ(output.address9, input.address9); + EXPECT_EQ(output.address10, input.address10); + EXPECT_EQ(output.assetName, input.assetName); + EXPECT_EQ(output.price1, input.price1); + EXPECT_EQ(output.price2, input.price2); + EXPECT_EQ(output.price3, input.price3); + EXPECT_EQ(output.saleAmountForPhase1, input.saleAmountForPhase1); + EXPECT_EQ(output.saleAmountForPhase2, input.saleAmountForPhase2); + EXPECT_EQ(output.saleAmountForPhase3, input.saleAmountForPhase3); + EXPECT_EQ(output.remainingAmountForPhase1, input.saleAmountForPhase1); + EXPECT_EQ(output.remainingAmountForPhase2, input.saleAmountForPhase2); + EXPECT_EQ(output.remainingAmountForPhase3, input.saleAmountForPhase3); + EXPECT_EQ(output.percent1, input.percent1); + EXPECT_EQ(output.percent2, input.percent2); + EXPECT_EQ(output.percent3, input.percent3); + EXPECT_EQ(output.percent4, input.percent4); + EXPECT_EQ(output.percent5, input.percent5); + EXPECT_EQ(output.percent6, input.percent6); + EXPECT_EQ(output.percent7, input.percent7); + EXPECT_EQ(output.percent8, input.percent8); + EXPECT_EQ(output.percent9, input.percent9); + EXPECT_EQ(output.percent10, input.percent10); + EXPECT_EQ(output.startEpoch, input.startEpoch); + } +}; + +class ContractTestingQIP : protected ContractTesting +{ +public: + ContractTestingQIP() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QIP); + callSystemProcedure(QIP_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + QIPChecker* getState() + { + return (QIPChecker*)contractStates[QIP_CONTRACT_INDEX]; + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QIP_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares) + { + QX::IssueAsset_input input; + input.assetName = assetName; + input.numberOfShares = numberOfShares; + input.unitOfMeasurement = 0; + input.numberOfDecimalPlaces = 0; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QIP_ISSUE_ASSET_FEE); + return output.issuedNumberOfShares; + } + + sint64 transferAsset(const id& from, const id& to, uint64 assetName, const id& issuer, sint64 numberOfShares) + { + QX::TransferShareOwnershipAndPossession_input input; + input.assetName = assetName; + input.issuer = issuer; + input.newOwnerAndPossessor = to; + input.numberOfShares = numberOfShares; + QX::TransferShareOwnershipAndPossession_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, from, QIP_TRANSFER_ASSET_FEE); + return output.transferredNumberOfShares; + } + + QIP::createICO_output createICO(const id& creator, const QIP::createICO_input& input) + { + QIP::createICO_output output; + invokeUserProcedure(QIP_CONTRACT_INDEX, 1, input, output, creator, 0); + return output; + } + + QIP::buyToken_output buyToken(const id& buyer, uint32 indexOfICO, uint64 amount, sint64 invocationReward) + { + QIP::buyToken_input input; + input.indexOfICO = indexOfICO; + input.amount = amount; + QIP::buyToken_output output; + invokeUserProcedure(QIP_CONTRACT_INDEX, 2, input, output, buyer, invocationReward); + return output; + } + + QIP::getICOInfo_output getICOInfo(uint32 indexOfICO) + { + QIP::getICOInfo_input input; + input.indexOfICO = indexOfICO; + QIP::getICOInfo_output output; + callFunction(QIP_CONTRACT_INDEX, 1, input, output); + return output; + } + + sint64 transferShareManagementRightsQX(const id& invocator, const Asset& asset, sint64 numberOfShares, uint32 newManagingContractIndex, sint64 fee) + { + QX::TransferShareManagementRights_input input; + input.asset = asset; + input.numberOfShares = numberOfShares; + input.newManagingContractIndex = newManagingContractIndex; + QX::TransferShareManagementRights_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, invocator, fee); + return output.transferredNumberOfShares; + } + + sint64 transferShareManagementRights(const id& invocator, const Asset& asset, sint64 numberOfShares, uint32 newManagingContractIndex, sint64 invocationReward) + { + QIP::TransferShareManagementRights_input input; + input.asset = asset; + input.numberOfShares = numberOfShares; + input.newManagingContractIndex = newManagingContractIndex; + QIP::TransferShareManagementRights_output output; + invokeUserProcedure(QIP_CONTRACT_INDEX, 3, input, output, invocator, invocationReward); + return output.transferredNumberOfShares; + } +}; + +TEST(ContractQIP, createICO_Success) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + // Issue asset and transfer to creator + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + // Prepare ICO input + QIP::createICO_input input; + input.issuer = issuer; + input.address1 = QIP_testAddress1; + input.address2 = QIP_testAddress2; + input.address3 = QIP_testAddress3; + input.address4 = QIP_testAddress1; + input.address5 = QIP_testAddress2; + input.address6 = QIP_testAddress3; + input.address7 = QIP_testAddress1; + input.address8 = QIP_testAddress2; + input.address9 = QIP_testAddress3; + input.address10 = QIP_testAddress1; + input.assetName = assetName; + input.price1 = 100; + input.price2 = 200; + input.price3 = 300; + input.saleAmountForPhase1 = 300000; + input.saleAmountForPhase2 = 300000; + input.saleAmountForPhase3 = 400000; + input.percent1 = 10; + input.percent2 = 10; + input.percent3 = 10; + input.percent4 = 10; + input.percent5 = 10; + input.percent6 = 10; + input.percent7 = 10; + input.percent8 = 10; + input.percent9 = 10; + input.percent10 = 5; + input.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output output = QIP.createICO(creator, input); + EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_success); + + // Check ICO info + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + QIP.getState()->checkICOInfo(icoInfo, input, creator); + + // Verify shares were transferred to contract + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, creator, creator, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 0); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares); +} + +TEST(ContractQIP, createICO_InvalidStartEpoch) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input input; + input.issuer = issuer; + input.address1 = QIP_testAddress1; + input.address2 = QIP_testAddress2; + input.address3 = QIP_testAddress3; + input.address4 = QIP_testAddress1; + input.address5 = QIP_testAddress2; + input.address6 = QIP_testAddress3; + input.address7 = QIP_testAddress1; + input.address8 = QIP_testAddress2; + input.address9 = QIP_testAddress3; + input.address10 = QIP_testAddress1; + input.assetName = assetName; + input.price1 = 100; + input.price2 = 200; + input.price3 = 300; + input.saleAmountForPhase1 = 300000; + input.saleAmountForPhase2 = 300000; + input.saleAmountForPhase3 = 400000; + input.percent1 = 10; + input.percent2 = 10; + input.percent3 = 10; + input.percent4 = 10; + input.percent5 = 10; + input.percent6 = 10; + input.percent7 = 10; + input.percent8 = 10; + input.percent9 = 10; + input.percent10 = 5; + + // Test with startEpoch <= current epoch + 1 + input.startEpoch = system.epoch; + increaseEnergy(creator, 1); + QIP::createICO_output output1 = QIP.createICO(creator, input); + EXPECT_EQ(output1.returnCode, QIPLogInfo::QIP_invalidStartEpoch); + + input.startEpoch = system.epoch + 1; + QIP::createICO_output output2 = QIP.createICO(creator, input); + EXPECT_EQ(output2.returnCode, QIPLogInfo::QIP_invalidStartEpoch); +} + +TEST(ContractQIP, createICO_InvalidSaleAmount) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input input; + input.issuer = issuer; + input.address1 = QIP_testAddress1; + input.address2 = QIP_testAddress2; + input.address3 = QIP_testAddress3; + input.address4 = QIP_testAddress1; + input.address5 = QIP_testAddress2; + input.address6 = QIP_testAddress3; + input.address7 = QIP_testAddress1; + input.address8 = QIP_testAddress2; + input.address9 = QIP_testAddress3; + input.address10 = QIP_testAddress1; + input.assetName = assetName; + input.price1 = 100; + input.price2 = 200; + input.price3 = 300; + input.saleAmountForPhase1 = 300000; + input.saleAmountForPhase2 = 300000; + input.saleAmountForPhase3 = 400001; // Total doesn't match + input.percent1 = 10; + input.percent2 = 10; + input.percent3 = 10; + input.percent4 = 10; + input.percent5 = 10; + input.percent6 = 10; + input.percent7 = 10; + input.percent8 = 10; + input.percent9 = 10; + input.percent10 = 5; + input.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output output = QIP.createICO(creator, input); + EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_invalidSaleAmount); +} + +TEST(ContractQIP, createICO_InvalidPrice) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input input; + input.issuer = issuer; + input.address1 = QIP_testAddress1; + input.address2 = QIP_testAddress2; + input.address3 = QIP_testAddress3; + input.address4 = QIP_testAddress1; + input.address5 = QIP_testAddress2; + input.address6 = QIP_testAddress3; + input.address7 = QIP_testAddress1; + input.address8 = QIP_testAddress2; + input.address9 = QIP_testAddress3; + input.address10 = QIP_testAddress1; + input.assetName = assetName; + input.price1 = 0; // Invalid price + input.price2 = 200; + input.price3 = 300; + input.saleAmountForPhase1 = 300000; + input.saleAmountForPhase2 = 300000; + input.saleAmountForPhase3 = 400000; + input.percent1 = 10; + input.percent2 = 10; + input.percent3 = 10; + input.percent4 = 10; + input.percent5 = 10; + input.percent6 = 10; + input.percent7 = 10; + input.percent8 = 10; + input.percent9 = 10; + input.percent10 = 5; + input.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output output = QIP.createICO(creator, input); + EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_invalidPrice); +} + +TEST(ContractQIP, createICO_InvalidPercent) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input input; + input.issuer = issuer; + input.address1 = QIP_testAddress1; + input.address2 = QIP_testAddress2; + input.address3 = QIP_testAddress3; + input.address4 = QIP_testAddress1; + input.address5 = QIP_testAddress2; + input.address6 = QIP_testAddress3; + input.address7 = QIP_testAddress1; + input.address8 = QIP_testAddress2; + input.address9 = QIP_testAddress3; + input.address10 = QIP_testAddress1; + input.assetName = assetName; + input.price1 = 100; + input.price2 = 200; + input.price3 = 300; + input.saleAmountForPhase1 = 300000; + input.saleAmountForPhase2 = 300000; + input.saleAmountForPhase3 = 400000; + input.percent1 = 10; + input.percent2 = 10; + input.percent3 = 10; + input.percent4 = 10; + input.percent5 = 10; + input.percent6 = 10; + input.percent7 = 10; + input.percent8 = 10; + input.percent9 = 5; + input.percent10 = 1; // Total is 96, should be 95 + input.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output output = QIP.createICO(creator, input); + EXPECT_EQ(output.returnCode, QIPLogInfo::QIP_invalidPercent); +} + +TEST(ContractQIP, buyToken_Phase1) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Advance to start epoch + ++system.epoch; + ++system.epoch; + + id buyer = QIP_testBuyer; + uint64 buyAmount = 10000; + uint64 price = createInput.price1; + sint64 requiredReward = buyAmount * price; + + increaseEnergy(buyer, requiredReward); + increaseEnergy(QIP_testAddress1, 1); + increaseEnergy(QIP_testAddress2, 1); + increaseEnergy(QIP_testAddress3, 1); + + // Record balances before purchase for all addresses + sint64 balanceBefore1 = getBalance(QIP_testAddress1); + sint64 balanceBefore2 = getBalance(QIP_testAddress2); + sint64 balanceBefore3 = getBalance(QIP_testAddress3); + sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); + + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); + + // Verify buyer received the shares + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); + + // Check remaining amounts + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + EXPECT_EQ(icoInfo.remainingAmountForPhase1, createInput.saleAmountForPhase1 - buyAmount); + EXPECT_EQ(icoInfo.remainingAmountForPhase2, createInput.saleAmountForPhase2); + EXPECT_EQ(icoInfo.remainingAmountForPhase3, createInput.saleAmountForPhase3); + + // Calculate expected distributions for all 10 addresses + sint64 totalPayment = buyAmount * price; + uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); + uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); + uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); + uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); + uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); + uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); + uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); + uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); + uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); + uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); + + // Calculate total distributed to addresses (should be 95% of total payment) + sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + + expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; + + // Calculate expected dividend amount (remaining 5% divided by 676) + sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; + uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; + + // Verify all addresses received correct amounts + // Note: addresses 1, 4, 7, 10 map to QIP_testAddress1 + // addresses 2, 5, 8 map to QIP_testAddress2 + // addresses 3, 6, 9 map to QIP_testAddress3 + sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; + sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; + sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; + + EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); + EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); + EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); + + // Verify contract balance decreased by total payment (minus any refund to buyer) + sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); + sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; + // Contract should have received the payment and distributed it, so balance should increase by fee minus distributions + // But since we're transferring from contract to addresses, the contract balance should decrease + // Actually, the contract receives the invocation reward, then transfers to addresses + // So the contract balance should be: initial + requiredReward - totalDistributedToAddresses - expectedDividendAmount + sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; + EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); +} + +TEST(ContractQIP, buyToken_Phase2) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Advance to Phase 2 (startEpoch + 1) + ++system.epoch; + ++system.epoch; + ++system.epoch; // Now at startEpoch + 1 + + id buyer = QIP_testBuyer; + uint64 buyAmount = 10000; + uint64 price = createInput.price2; + sint64 requiredReward = buyAmount * price; + + increaseEnergy(buyer, requiredReward); + increaseEnergy(QIP_testAddress1, 1); + increaseEnergy(QIP_testAddress2, 1); + increaseEnergy(QIP_testAddress3, 1); + + // Record balances before purchase + sint64 balanceBefore1 = getBalance(QIP_testAddress1); + sint64 balanceBefore2 = getBalance(QIP_testAddress2); + sint64 balanceBefore3 = getBalance(QIP_testAddress3); + sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); + + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); + + // Verify buyer received the shares + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); + + // Check remaining amounts + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + EXPECT_EQ(icoInfo.remainingAmountForPhase1, createInput.saleAmountForPhase1); + EXPECT_EQ(icoInfo.remainingAmountForPhase2, createInput.saleAmountForPhase2 - buyAmount); + EXPECT_EQ(icoInfo.remainingAmountForPhase3, createInput.saleAmountForPhase3); + + // Verify fee distribution for all addresses + sint64 totalPayment = buyAmount * price; + uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); + uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); + uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); + uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); + uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); + uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); + uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); + uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); + uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); + uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); + + sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + + expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; + sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; + uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; + + sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; + sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; + sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; + + EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); + EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); + EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); + + sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); + sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; + sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; + EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); +} + +TEST(ContractQIP, buyToken_Phase3) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Advance to Phase 3 (startEpoch + 2) + ++system.epoch; + ++system.epoch; + ++system.epoch; // Now at startEpoch + 1 + ++system.epoch; // Now at startEpoch + 2 + + id buyer = QIP_testBuyer; + uint64 buyAmount = 10000; + uint64 price = createInput.price3; + sint64 requiredReward = buyAmount * price; + + increaseEnergy(buyer, requiredReward); + increaseEnergy(QIP_testAddress1, 1); + increaseEnergy(QIP_testAddress2, 1); + increaseEnergy(QIP_testAddress3, 1); + + // Record balances before purchase + sint64 balanceBefore1 = getBalance(QIP_testAddress1); + sint64 balanceBefore2 = getBalance(QIP_testAddress2); + sint64 balanceBefore3 = getBalance(QIP_testAddress3); + sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); + + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); + + // Verify buyer received the shares + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, buyer, buyer, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); + + // Check remaining amounts + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + EXPECT_EQ(icoInfo.remainingAmountForPhase1, createInput.saleAmountForPhase1); + EXPECT_EQ(icoInfo.remainingAmountForPhase2, createInput.saleAmountForPhase2); + EXPECT_EQ(icoInfo.remainingAmountForPhase3, createInput.saleAmountForPhase3 - buyAmount); + + // Verify fee distribution for all addresses + sint64 totalPayment = buyAmount * price; + uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); + uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); + uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); + uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); + uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); + uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); + uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); + uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); + uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); + uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); + + sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + + expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; + sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; + uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; + + sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; + sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; + sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; + + EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); + EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); + EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); + + sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); + sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; + sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; + EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); +} + +TEST(ContractQIP, buyToken_InvalidEpoch) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Try to buy before start epoch + id buyer = QIP_testBuyer; + uint64 buyAmount = 10000; + uint64 price = createInput.price1; + sint64 requiredReward = buyAmount * price; + + increaseEnergy(buyer, requiredReward); + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_invalidEpoch); +} + +TEST(ContractQIP, buyToken_InvalidAmount) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Advance to start epoch + ++system.epoch; + ++system.epoch; + + id buyer = QIP_testBuyer; + uint64 buyAmount = 300001; // More than remaining + uint64 price = createInput.price1; + sint64 requiredReward = buyAmount * price; + + increaseEnergy(buyer, requiredReward); + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_invalidAmount); +} + +TEST(ContractQIP, buyToken_ICONotFound) +{ + ContractTestingQIP QIP; + + id buyer = QIP_testBuyer; + uint64 buyAmount = 10000; + sint64 requiredReward = buyAmount * 100; + + increaseEnergy(buyer, requiredReward); + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 999, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_ICONotFound); +} + +TEST(ContractQIP, buyToken_InsufficientInvocationReward) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Advance to start epoch + ++system.epoch; + ++system.epoch; + + id buyer = QIP_testBuyer; + uint64 buyAmount = 10000; + uint64 price = createInput.price1; + sint64 requiredReward = buyAmount * price; + sint64 insufficientReward = requiredReward - 1; + + increaseEnergy(buyer, insufficientReward); + QIP::buyToken_output buyOutput = QIP.buyToken(buyer, 0, buyAmount, insufficientReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_insufficientInvocationReward); +} + +TEST(ContractQIP, END_EPOCH_Phase1Rollover) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Check initial state + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + uint64 initialPhase1 = icoInfo.remainingAmountForPhase1; + uint64 initialPhase2 = icoInfo.remainingAmountForPhase2; + + // Advance to startEpoch (Phase 1 ends) + ++system.epoch; // epoch = startEpoch - 1 + ++system.epoch; // epoch = startEpoch + + // End epoch should rollover Phase 1 remaining to Phase 2 + QIP.endEpoch(); + + // Check that Phase 1 remaining was set to 0 + icoInfo = QIP.getICOInfo(0); + EXPECT_EQ(icoInfo.remainingAmountForPhase1, 0); + EXPECT_EQ(icoInfo.remainingAmountForPhase2, initialPhase2 + initialPhase1); +} + +TEST(ContractQIP, END_EPOCH_Phase2Rollover) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Check initial state + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + uint64 initialPhase2 = icoInfo.remainingAmountForPhase2; + uint64 initialPhase3 = icoInfo.remainingAmountForPhase3; + + // Advance to startEpoch + 1 (Phase 2 ends) + ++system.epoch; // epoch = startEpoch - 1 + ++system.epoch; // epoch = startEpoch + ++system.epoch; // epoch = startEpoch + 1 + + // End epoch should rollover Phase 2 remaining to Phase 3 + QIP.endEpoch(); + + // Check that Phase 2 remaining was set to 0 + icoInfo = QIP.getICOInfo(0); + EXPECT_EQ(icoInfo.remainingAmountForPhase2, 0); + EXPECT_EQ(icoInfo.remainingAmountForPhase3, initialPhase3 + initialPhase2); +} + +TEST(ContractQIP, END_EPOCH_Phase3ReturnToCreator) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Check initial state - verify shares are in contract + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares); + QIP::getICOInfo_output icoInfo = QIP.getICOInfo(0); + uint64 remainingPhase3 = icoInfo.remainingAmountForPhase3; + sint64 creatorSharesBefore = numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX); + + // Advance to startEpoch + 2 (Phase 3 ends) + ++system.epoch; // epoch = startEpoch - 1 + ++system.epoch; // epoch = startEpoch + ++system.epoch; // epoch = startEpoch + 1 + ++system.epoch; // epoch = startEpoch + 2 + + // End epoch should return Phase 3 remaining to creator and remove ICO + QIP.endEpoch(); + + // Verify shares were returned to creator + sint64 creatorSharesAfter = numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX); + EXPECT_EQ(creatorSharesAfter, creatorSharesBefore + remainingPhase3); + + // Verify ICO was removed + QIPChecker* state = QIP.getState(); + EXPECT_EQ(state->getNumberOfICO(), 0); + + // Verify contract no longer has the returned shares + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares - remainingPhase3); +} + +TEST(ContractQIP, TransferShareManagementRights) +{ + ContractTestingQIP QIP; + + id issuer = QIP_testIssuer; + uint64 assetName = assetNameFromString("ICOASS"); + sint64 totalShares = 1000000; + + increaseEnergy(issuer, QIP_ISSUE_ASSET_FEE); + EXPECT_EQ(QIP.issueAsset(issuer, assetName, totalShares), totalShares); + + id creator = QIP_testBuyer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + increaseEnergy(issuer, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferAsset(issuer, creator, assetName, issuer, totalShares), totalShares); + + // Transfer management rights to QIP contract + Asset asset; + asset.assetName = assetName; + asset.issuer = issuer; + increaseEnergy(creator, QIP_TRANSFER_ASSET_FEE); + EXPECT_EQ(QIP.transferShareManagementRightsQX(creator, asset, totalShares, QIP_CONTRACT_INDEX, QIP_TRANSFER_ASSET_FEE), totalShares); + + // Transfer shares to QIP contract + QIP::createICO_input createInput; + createInput.issuer = issuer; + createInput.address1 = QIP_testAddress1; + createInput.address2 = QIP_testAddress2; + createInput.address3 = QIP_testAddress3; + createInput.address4 = QIP_testAddress1; + createInput.address5 = QIP_testAddress2; + createInput.address6 = QIP_testAddress3; + createInput.address7 = QIP_testAddress1; + createInput.address8 = QIP_testAddress2; + createInput.address9 = QIP_testAddress3; + createInput.address10 = QIP_testAddress1; + createInput.assetName = assetName; + createInput.price1 = 100; + createInput.price2 = 200; + createInput.price3 = 300; + createInput.saleAmountForPhase1 = 300000; + createInput.saleAmountForPhase2 = 300000; + createInput.saleAmountForPhase3 = 400000; + createInput.percent1 = 10; + createInput.percent2 = 10; + createInput.percent3 = 10; + createInput.percent4 = 10; + createInput.percent5 = 10; + createInput.percent6 = 10; + createInput.percent7 = 10; + createInput.percent8 = 10; + createInput.percent9 = 10; + createInput.percent10 = 5; + createInput.startEpoch = system.epoch + 2; + + increaseEnergy(creator, 1); + QIP::createICO_output createOutput = QIP.createICO(creator, createInput); + EXPECT_EQ(createOutput.returnCode, QIPLogInfo::QIP_success); + + // Verify shares are in QIP contract + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares); + + system.epoch += 2; + // buy token + uint64 buyAmount = 100000; + uint64 price = createInput.price1; + sint64 requiredReward = buyAmount * price; + + increaseEnergy(creator, requiredReward); + increaseEnergy(QIP_testAddress1, 1); + increaseEnergy(QIP_testAddress2, 1); + increaseEnergy(QIP_testAddress3, 1); + + // Record balances before purchase + sint64 balanceBefore1 = getBalance(QIP_testAddress1); + sint64 balanceBefore2 = getBalance(QIP_testAddress2); + sint64 balanceBefore3 = getBalance(QIP_testAddress3); + sint64 contractBalanceBefore = getBalance(QIP_CONTRACT_ID); + + QIP::buyToken_output buyOutput = QIP.buyToken(creator, 0, buyAmount, requiredReward); + EXPECT_EQ(buyOutput.returnCode, QIPLogInfo::QIP_success); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), buyAmount); + + // Verify fee distribution for all addresses + sint64 totalPayment = buyAmount * price; + uint64 expectedDist1 = div(totalPayment * createInput.percent1 * 1ULL, 100ULL); + uint64 expectedDist2 = div(totalPayment * createInput.percent2 * 1ULL, 100ULL); + uint64 expectedDist3 = div(totalPayment * createInput.percent3 * 1ULL, 100ULL); + uint64 expectedDist4 = div(totalPayment * createInput.percent4 * 1ULL, 100ULL); + uint64 expectedDist5 = div(totalPayment * createInput.percent5 * 1ULL, 100ULL); + uint64 expectedDist6 = div(totalPayment * createInput.percent6 * 1ULL, 100ULL); + uint64 expectedDist7 = div(totalPayment * createInput.percent7 * 1ULL, 100ULL); + uint64 expectedDist8 = div(totalPayment * createInput.percent8 * 1ULL, 100ULL); + uint64 expectedDist9 = div(totalPayment * createInput.percent9 * 1ULL, 100ULL); + uint64 expectedDist10 = div(totalPayment * createInput.percent10 * 1ULL, 100ULL); + + sint64 totalDistributedToAddresses = expectedDist1 + expectedDist2 + expectedDist3 + expectedDist4 + expectedDist5 + + expectedDist6 + expectedDist7 + expectedDist8 + expectedDist9 + expectedDist10; + sint64 remainingForDividends = totalPayment - totalDistributedToAddresses; + uint64 expectedDividendAmount = div(remainingForDividends * 1ULL, 676ULL) * 676; + + sint64 expectedForAddress1 = expectedDist1 + expectedDist4 + expectedDist7 + expectedDist10; + sint64 expectedForAddress2 = expectedDist2 + expectedDist5 + expectedDist8; + sint64 expectedForAddress3 = expectedDist3 + expectedDist6 + expectedDist9; + + EXPECT_EQ(getBalance(QIP_testAddress1), balanceBefore1 + expectedForAddress1); + EXPECT_EQ(getBalance(QIP_testAddress2), balanceBefore2 + expectedForAddress2); + EXPECT_EQ(getBalance(QIP_testAddress3), balanceBefore3 + expectedForAddress3); + + sint64 contractBalanceAfter = getBalance(QIP_CONTRACT_ID); + sint64 contractBalanceChange = contractBalanceAfter - contractBalanceBefore; + sint64 expectedContractBalanceChange = requiredReward - totalDistributedToAddresses - expectedDividendAmount; + EXPECT_EQ(contractBalanceChange, expectedContractBalanceChange); + + // Transfer management rights + sint64 transferAmount = 100000; + + increaseEnergy(creator, QIP_TRANSFER_RIGHTS_FEE * 2); + QIP.endEpoch(); + system.epoch += 1; + QIP.endEpoch(); + system.epoch += 1; + sint64 transferred = QIP.transferShareManagementRights(creator, asset, transferAmount, QX_CONTRACT_INDEX, QIP_TRANSFER_RIGHTS_FEE); + EXPECT_EQ(transferred, 0); + QIP.endEpoch(); + transferred = QIP.transferShareManagementRights(creator, asset, transferAmount, QX_CONTRACT_INDEX, QIP_TRANSFER_RIGHTS_FEE); + EXPECT_EQ(transferred, transferAmount); + + // Verify shares were transferred + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, QIP_CONTRACT_ID, QIP_CONTRACT_ID, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), 0); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, creator, creator, QIP_CONTRACT_INDEX, QIP_CONTRACT_INDEX), totalShares - transferAmount); +} + diff --git a/test/contract_qraffle.cpp b/test/contract_qraffle.cpp new file mode 100644 index 000000000..afbf3e503 --- /dev/null +++ b/test/contract_qraffle.cpp @@ -0,0 +1,1576 @@ +#define NO_UEFI + +#include +#include + +#include "contract_testing.h" + +static std::mt19937_64 rand64; + +static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) +{ + if(minValue > maxValue) + { + return 0; + } + return minValue + rand64() % (maxValue - minValue); +} + +static id getUser(unsigned long long i) +{ + return id(i, i / 2 + 4, i + 10, i * 3 + 8); +} + +static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) +{ + std::map userMap; + unsigned long long userCount = random(0, maxNum); + std::vector users; + users.reserve(userCount); + for (unsigned int i = 0; i < userCount; ++i) + { + unsigned long long userIdx = random(0, totalUsers - 1); + id user = getUser(userIdx); + if (userMap.contains(user)) + { + continue; + } + userMap[user] = true; + users.push_back(user); + } + return users; +} + +class QRaffleChecker : public QRAFFLE +{ +public: + void registerChecker(const id& user, uint32 expectedRegisters, bool isRegistered) + { + if (isRegistered) + { + EXPECT_EQ(registers.contains(user), 1); + } + else + { + EXPECT_EQ(registers.contains(user), 0); + } + EXPECT_EQ(numberOfRegisters, expectedRegisters); + } + + void unregisterChecker(const id& user, uint32 expectedRegisters) + { + EXPECT_EQ(registers.contains(user), 0); + EXPECT_EQ(numberOfRegisters, expectedRegisters); + } + + void entryAmountChecker(const id& user, uint64 expectedAmount, uint32 expectedSubmitted) + { + uint64 amount = 0; + if (quRaffleEntryAmount.contains(user)) + { + quRaffleEntryAmount.get(user, amount); + EXPECT_EQ(amount, expectedAmount); + } + EXPECT_EQ(numberOfEntryAmountSubmitted, expectedSubmitted); + } + + void proposalChecker(uint32 index, const Asset& expectedToken, uint64 expectedEntryAmount) + { + EXPECT_EQ(proposals.get(index).token.assetName, expectedToken.assetName); + EXPECT_EQ(proposals.get(index).token.issuer, expectedToken.issuer); + EXPECT_EQ(proposals.get(index).entryAmount, expectedEntryAmount); + EXPECT_EQ(numberOfProposals, index + 1); + } + + void voteChecker(uint32 proposalIndex, uint32 expectedYes, uint32 expectedNo) + { + EXPECT_EQ(proposals.get(proposalIndex).nYes, expectedYes); + EXPECT_EQ(proposals.get(proposalIndex).nNo, expectedNo); + } + + void quRaffleMemberChecker(const id& user, uint32 expectedMembers) + { + bool found = false; + for (uint32 i = 0; i < numberOfQuRaffleMembers; i++) + { + if (quRaffleMembers.get(i) == user) + { + found = true; + break; + } + } + EXPECT_EQ(found, 1); + EXPECT_EQ(numberOfQuRaffleMembers, expectedMembers); + } + + void tokenRaffleMemberChecker(uint32 raffleIndex, const id& user, uint32 expectedMembers) + { + tokenRaffleMembers.get(raffleIndex, tmpTokenRaffleMembers); + bool found = false; + for (uint32 i = 0; i < numberOfTokenRaffleMembers.get(raffleIndex); i++) + { + if (tmpTokenRaffleMembers.get(i) == user) + { + found = true; + break; + } + } + EXPECT_EQ(found, 1); + EXPECT_EQ(numberOfTokenRaffleMembers.get(raffleIndex), expectedMembers); + } + + void analyticsChecker(uint64 expectedBurn, uint64 expectedCharity, uint64 expectedShareholder, + uint64 expectedRegister, uint64 expectedFee, uint64 expectedWinner, + uint64 expectedLargestWinner, uint32 expectedRegisters, uint32 expectedProposals, + uint32 expectedQuMembers, uint32 expectedActiveTokenRaffle, + uint32 expectedEndedTokenRaffle, uint32 expectedEntrySubmitted) + { + EXPECT_EQ(totalBurnAmount, expectedBurn); + EXPECT_EQ(totalCharityAmount, expectedCharity); + EXPECT_EQ(totalShareholderAmount, expectedShareholder); + EXPECT_EQ(totalRegisterAmount, expectedRegister); + EXPECT_EQ(totalFeeAmount, expectedFee); + EXPECT_EQ(totalWinnerAmount, expectedWinner); + EXPECT_EQ(largestWinnerAmount, expectedLargestWinner); + EXPECT_EQ(numberOfRegisters, expectedRegisters); + EXPECT_EQ(numberOfProposals, expectedProposals); + EXPECT_EQ(numberOfQuRaffleMembers, expectedQuMembers); + EXPECT_EQ(numberOfActiveTokenRaffle, expectedActiveTokenRaffle); + EXPECT_EQ(numberOfEndedTokenRaffle, expectedEndedTokenRaffle); + EXPECT_EQ(numberOfEntryAmountSubmitted, expectedEntrySubmitted); + } + + void activeTokenRaffleChecker(uint32 index, const Asset& expectedToken, uint64 expectedEntryAmount) + { + EXPECT_EQ(activeTokenRaffle.get(index).token.assetName, expectedToken.assetName); + EXPECT_EQ(activeTokenRaffle.get(index).token.issuer, expectedToken.issuer); + EXPECT_EQ(activeTokenRaffle.get(index).entryAmount, expectedEntryAmount); + } + + void endedTokenRaffleChecker(uint32 index, const id& expectedWinner, const Asset& expectedToken, + uint64 expectedEntryAmount, uint32 expectedMembers, uint32 expectedWinnerIndex, uint32 expectedEpoch) + { + EXPECT_EQ(tokenRaffle.get(index).epochWinner, expectedWinner); + EXPECT_EQ(tokenRaffle.get(index).token.assetName, expectedToken.assetName); + EXPECT_EQ(tokenRaffle.get(index).token.issuer, expectedToken.issuer); + EXPECT_EQ(tokenRaffle.get(index).entryAmount, expectedEntryAmount); + EXPECT_EQ(tokenRaffle.get(index).numberOfMembers, expectedMembers); + EXPECT_EQ(tokenRaffle.get(index).winnerIndex, expectedWinnerIndex); + EXPECT_EQ(tokenRaffle.get(index).epoch, expectedEpoch); + } + + void quRaffleWinnerChecker(uint16 epoch, const id& expectedWinner, uint64 expectedReceived, + uint64 expectedEntryAmount, uint32 expectedMembers, uint32 expectedWinnerIndex) + { + EXPECT_EQ(QuRaffles.get(epoch).epochWinner, expectedWinner); + EXPECT_EQ(QuRaffles.get(epoch).receivedAmount, expectedReceived); + EXPECT_EQ(QuRaffles.get(epoch).entryAmount, expectedEntryAmount); + EXPECT_EQ(QuRaffles.get(epoch).numberOfMembers, expectedMembers); + EXPECT_EQ(QuRaffles.get(epoch).winnerIndex, expectedWinnerIndex); + } + + uint64 getQuRaffleEntryAmount() + { + return qREAmount; + } + + uint32 getNumberOfActiveTokenRaffle() + { + return numberOfActiveTokenRaffle; + } + + uint32 getNumberOfEndedTokenRaffle() + { + return numberOfEndedTokenRaffle; + } + + uint64 getEpochQXMRRevenue() + { + return epochQXMRRevenue; + } + + uint32 getNumberOfRegisters() + { + return numberOfRegisters; + } + + id getQXMRIssuer() + { + return QXMRIssuer; + } +}; + +class ContractTestingQraffle : protected ContractTesting +{ +public: + ContractTestingQraffle() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QRAFFLE); + callSystemProcedure(QRAFFLE_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + QRaffleChecker* getState() + { + return (QRaffleChecker*)contractStates[QRAFFLE_CONTRACT_INDEX]; + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QRAFFLE_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + QRAFFLE::registerInSystem_output registerInSystem(const id& user, uint64 amount, bit useQXMR) + { + QRAFFLE::registerInSystem_input input; + QRAFFLE::registerInSystem_output output; + + input.useQXMR = useQXMR; + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 1, input, output, user, amount); + return output; + } + + QRAFFLE::logoutInSystem_output logoutInSystem(const id& user) + { + QRAFFLE::logoutInSystem_input input; + QRAFFLE::logoutInSystem_output output; + + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 2, input, output, user, 0); + return output; + } + + QRAFFLE::submitEntryAmount_output submitEntryAmount(const id& user, uint64 amount) + { + QRAFFLE::submitEntryAmount_input input; + QRAFFLE::submitEntryAmount_output output; + + input.amount = amount; + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 3, input, output, user, 0); + return output; + } + + QRAFFLE::submitProposal_output submitProposal(const id& user, const Asset& token, uint64 entryAmount) + { + QRAFFLE::submitProposal_input input; + QRAFFLE::submitProposal_output output; + + input.tokenIssuer = token.issuer; + input.tokenName = token.assetName; + input.entryAmount = entryAmount; + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 4, input, output, user, 0); + return output; + } + + QRAFFLE::voteInProposal_output voteInProposal(const id& user, uint32 proposalIndex, bit yes) + { + QRAFFLE::voteInProposal_input input; + QRAFFLE::voteInProposal_output output; + + input.indexOfProposal = proposalIndex; + input.yes = yes; + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 5, input, output, user, 0); + return output; + } + + QRAFFLE::depositInQuRaffle_output depositInQuRaffle(const id& user, uint64 amount) + { + QRAFFLE::depositInQuRaffle_input input; + QRAFFLE::depositInQuRaffle_output output; + + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 6, input, output, user, amount); + return output; + } + + QRAFFLE::depositInTokenRaffle_output depositInTokenRaffle(const id& user, uint32 raffleIndex, uint64 amount) + { + QRAFFLE::depositInTokenRaffle_input input; + QRAFFLE::depositInTokenRaffle_output output; + + input.indexOfTokenRaffle = raffleIndex; + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 7, input, output, user, amount); + return output; + } + + QRAFFLE::getRegisters_output getRegisters(uint32 offset, uint32 limit) + { + QRAFFLE::getRegisters_input input; + QRAFFLE::getRegisters_output output; + + input.offset = offset; + input.limit = limit; + callFunction(QRAFFLE_CONTRACT_INDEX, 1, input, output); + return output; + } + + QRAFFLE::getAnalytics_output getAnalytics() + { + QRAFFLE::getAnalytics_input input; + QRAFFLE::getAnalytics_output output; + + callFunction(QRAFFLE_CONTRACT_INDEX, 2, input, output); + return output; + } + + QRAFFLE::getActiveProposal_output getActiveProposal(uint32 proposalIndex) + { + QRAFFLE::getActiveProposal_input input; + QRAFFLE::getActiveProposal_output output; + + input.indexOfProposal = proposalIndex; + callFunction(QRAFFLE_CONTRACT_INDEX, 3, input, output); + return output; + } + + QRAFFLE::getEndedTokenRaffle_output getEndedTokenRaffle(uint32 raffleIndex) + { + QRAFFLE::getEndedTokenRaffle_input input; + QRAFFLE::getEndedTokenRaffle_output output; + + input.indexOfRaffle = raffleIndex; + callFunction(QRAFFLE_CONTRACT_INDEX, 4, input, output); + return output; + } + + QRAFFLE::getEndedQuRaffle_output getEndedQuRaffle(uint16 epoch) + { + QRAFFLE::getEndedQuRaffle_input input; + QRAFFLE::getEndedQuRaffle_output output; + + input.epoch = epoch; + callFunction(QRAFFLE_CONTRACT_INDEX, 5, input, output); + return output; + } + + QRAFFLE::getActiveTokenRaffle_output getActiveTokenRaffle(uint32 raffleIndex) + { + QRAFFLE::getActiveTokenRaffle_input input; + QRAFFLE::getActiveTokenRaffle_output output; + + input.indexOfTokenRaffle = raffleIndex; + callFunction(QRAFFLE_CONTRACT_INDEX, 6, input, output); + return output; + } + + QRAFFLE::getEpochRaffleIndexes_output getEpochRaffleIndexes(uint16 epoch) + { + QRAFFLE::getEpochRaffleIndexes_input input; + QRAFFLE::getEpochRaffleIndexes_output output; + + input.epoch = epoch; + callFunction(QRAFFLE_CONTRACT_INDEX, 7, input, output); + return output; + } + + QRAFFLE::getQuRaffleEntryAmountPerUser_output getQuRaffleEntryAmountPerUser(const id& user) + { + QRAFFLE::getQuRaffleEntryAmountPerUser_input input; + QRAFFLE::getQuRaffleEntryAmountPerUser_output output; + + input.user = user; + callFunction(QRAFFLE_CONTRACT_INDEX, 8, input, output); + return output; + } + + QRAFFLE::getQuRaffleEntryAverageAmount_output getQuRaffleEntryAverageAmount() + { + QRAFFLE::getQuRaffleEntryAverageAmount_input input; + QRAFFLE::getQuRaffleEntryAverageAmount_output output; + + callFunction(QRAFFLE_CONTRACT_INDEX, 9, input, output); + return output; + } + + sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) + { + QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, 1000000000ULL); + return output.issuedNumberOfShares; + } + + sint64 transferShareOwnershipAndPossession(const id& issuer, uint64 assetName, const id& currentOwnerAndPossesor, sint64 numberOfShares, const id& newOwnerAndPossesor) + { + QX::TransferShareOwnershipAndPossession_input input; + QX::TransferShareOwnershipAndPossession_output output; + + input.assetName = assetName; + input.issuer = issuer; + input.newOwnerAndPossessor = newOwnerAndPossesor; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, currentOwnerAndPossesor, 100); + return output.transferredNumberOfShares; + } + + sint64 TransferShareManagementRights(const id& issuer, uint64 assetName, uint32 newManagingContractIndex, sint64 numberOfShares, const id& currentOwner) + { + QX::TransferShareManagementRights_input input; + QX::TransferShareManagementRights_output output; + + input.asset.assetName = assetName; + input.asset.issuer = issuer; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, currentOwner, 0); + + return output.transferredNumberOfShares; + } + + sint64 TransferShareManagementRightsQraffle(const id& issuer, uint64 assetName, uint32 newManagingContractIndex, sint64 numberOfShares, const id& currentOwner) + { + QRAFFLE::TransferShareManagementRights_input input; + QRAFFLE::TransferShareManagementRights_output output; + + input.tokenName = assetName; + input.tokenIssuer = issuer; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QRAFFLE_CONTRACT_INDEX, 8, input, output, currentOwner, QRAFFLE_TRANSFER_SHARE_FEE); + return output.transferredNumberOfShares; + } +}; + +TEST(ContractQraffle, RegisterInSystem) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Test successful registration + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->registerChecker(user, ++registerCount, true); + } + + // // Test insufficient funds + id poorUser = getUser(9999); + increaseEnergy(poorUser, QRAFFLE_REGISTER_AMOUNT - 1); + auto result = qraffle.registerInSystem(poorUser, QRAFFLE_REGISTER_AMOUNT - 1, 0); + EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_FUND); + qraffle.getState()->registerChecker(poorUser, registerCount, false); + + // Test already registered + increaseEnergy(users[0], QRAFFLE_REGISTER_AMOUNT); + result = qraffle.registerInSystem(users[0], QRAFFLE_REGISTER_AMOUNT, 0); + EXPECT_EQ(result.returnCode, QRAFFLE_ALREADY_REGISTERED); + qraffle.getState()->registerChecker(users[0], registerCount, true); +} + +TEST(ContractQraffle, LogoutInSystem) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Test successful logout + for (const auto& user : users) + { + auto result = qraffle.logoutInSystem(user); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(getBalance(user), QRAFFLE_REGISTER_AMOUNT - QRAFFLE_LOGOUT_FEE); + qraffle.getState()->unregisterChecker(user, --registerCount); + } + + // Test unregistered user logout + qraffle.getState()->unregisterChecker(users[0], registerCount); + auto result = qraffle.logoutInSystem(users[0]); + EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); +} + +TEST(ContractQraffle, SubmitEntryAmount) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + uint32 entrySubmittedCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Test successful entry amount submission + for (const auto& user : users) + { + uint64 amount = random(1000000, 1000000000); + auto result = qraffle.submitEntryAmount(user, amount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->entryAmountChecker(user, amount, ++entrySubmittedCount); + } + + // Test unregistered user + id unregisteredUser = getUser(9999); + increaseEnergy(unregisteredUser, QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.submitEntryAmount(unregisteredUser, 1000000); + EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); + + // Test update entry amount + uint64 newAmount = random(1000000, 1000000000); + result = qraffle.submitEntryAmount(users[0], newAmount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->entryAmountChecker(users[0], newAmount, entrySubmittedCount); +} + +TEST(ContractQraffle, SubmitProposal) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + uint32 proposalCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Issue some test assets + id issuer = getUser(2000); + increaseEnergy(issuer, 1000000000ULL); + uint64 assetName1 = assetNameFromString("TEST1"); + uint64 assetName2 = assetNameFromString("TEST2"); + qraffle.issueAsset(issuer, assetName1, 1000000, 0, 0); + qraffle.issueAsset(issuer, assetName2, 2000000, 0, 0); + + Asset token1, token2; + token1.assetName = assetName1; + token1.issuer = issuer; + token2.assetName = assetName2; + token2.issuer = issuer; + + // Test successful proposal submission + for (const auto& user : users) + { + uint64 entryAmount = random(1000000, 1000000000); + Asset token = (random(0, 2) == 0) ? token1 : token2; + + if (proposalCount == QRAFFLE_MAX_PROPOSAL_EPOCH - 1) + { + break; + } + increaseEnergy(user, 1000); + auto result = qraffle.submitProposal(user, token, entryAmount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->proposalChecker(proposalCount, token, entryAmount); + proposalCount++; + } + + // Test unregistered user + id unregisteredUser = getUser(1999); + increaseEnergy(unregisteredUser, QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.submitProposal(unregisteredUser, token1, 1000000); + EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); +} + +TEST(ContractQraffle, VoteInProposal) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + uint32 proposalCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Create a proposal + id issuer = getUser(2000); + increaseEnergy(issuer, 1000000000ULL); + uint64 assetName = assetNameFromString("VOTETS"); + qraffle.issueAsset(issuer, assetName, 1000000, 0, 0); + + Asset token; + token.assetName = assetName; + token.issuer = issuer; + + qraffle.submitProposal(users[0], token, 1000000); + proposalCount++; + + uint32 yesVotes = 0, noVotes = 0; + + // Test voting + for (const auto& user : users) + { + bit vote = (bit)random(0, 2); + auto result = qraffle.voteInProposal(user, 0, vote); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + if (vote) + yesVotes++; + else + noVotes++; + + qraffle.getState()->voteChecker(0, yesVotes, noVotes); + } + + // Test duplicate vote (should change vote) + bit newVote = (bit)random(0, 2); + auto result = qraffle.voteInProposal(users[0], 0, newVote); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + if (newVote) + { + yesVotes++; + noVotes--; + } + else + { + noVotes++; + yesVotes--; + } + + qraffle.getState()->voteChecker(0, yesVotes, noVotes); + + // Test unregistered user + id unregisteredUser = getUser(9999); + increaseEnergy(unregisteredUser, 1000000000ULL); + result = qraffle.voteInProposal(unregisteredUser, 0, 1); + EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); + + // Test invalid proposal index + result = qraffle.voteInProposal(users[0], 9999, 1); + EXPECT_EQ(result.returnCode, QRAFFLE_INVALID_PROPOSAL); +} + +TEST(ContractQraffle, depositInQuRaffle) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + uint32 memberCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Test successful deposit + for (const auto& user : users) + { + increaseEnergy(user, qraffle.getState()->getQuRaffleEntryAmount()); + auto result = qraffle.depositInQuRaffle(user, qraffle.getState()->getQuRaffleEntryAmount()); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->quRaffleMemberChecker(user, ++memberCount); + } + + // Test insufficient funds + id poorUser = getUser(9999); + increaseEnergy(poorUser, qraffle.getState()->getQuRaffleEntryAmount() - 1); + auto result = qraffle.depositInQuRaffle(poorUser, qraffle.getState()->getQuRaffleEntryAmount() - 1); + EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_FUND); + + // Test already registered + increaseEnergy(users[0], qraffle.getState()->getQuRaffleEntryAmount()); + result = qraffle.depositInQuRaffle(users[0], qraffle.getState()->getQuRaffleEntryAmount()); + EXPECT_EQ(result.returnCode, QRAFFLE_ALREADY_REGISTERED); +} + +TEST(ContractQraffle, DepositInTokenRaffle) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Create a proposal and vote for it + id issuer = getUser(2000); + increaseEnergy(issuer, 2000000000ULL); + uint64 assetName = assetNameFromString("TOKENRF"); + qraffle.issueAsset(issuer, assetName, 1000000000000, 0, 0); + + Asset token; + token.assetName = assetName; + token.issuer = issuer; + + qraffle.submitProposal(users[0], token, 1000000); + + // Vote yes for the proposal + for (const auto& user : users) + { + qraffle.voteInProposal(user, 0, 1); + } + + // End epoch to activate token raffle + qraffle.endEpoch(); + + // Test active token raffle + auto activeRaffle = qraffle.getActiveTokenRaffle(0); + EXPECT_EQ(activeRaffle.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(activeRaffle.tokenName, assetName); + EXPECT_EQ(activeRaffle.tokenIssuer, issuer); + EXPECT_EQ(activeRaffle.entryAmount, 1000000); + + // Test successful token raffle deposit + uint32 memberCount = 0; + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_TRANSFER_SHARE_FEE); + EXPECT_EQ(qraffle.transferShareOwnershipAndPossession(issuer, assetName, issuer, 1000000, user), 1000000); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user, user, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 1000000); + + EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName, QRAFFLE_CONTRACT_INDEX, 1000000, user), 1000000); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user, user, QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), 1000000); + + auto result = qraffle.depositInTokenRaffle(user, 0, QRAFFLE_TRANSFER_SHARE_FEE); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + memberCount++; + qraffle.getState()->tokenRaffleMemberChecker(0, user, memberCount); + } + + // Test insufficient funds + id poorUser = getUser(9999); + increaseEnergy(poorUser, QRAFFLE_TRANSFER_SHARE_FEE - 1); + auto result = qraffle.depositInTokenRaffle(poorUser, 0, QRAFFLE_TRANSFER_SHARE_FEE - 1); + EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_FUND); + + // Test insufficient Token + id poorUser2 = getUser(8888); + increaseEnergy(poorUser2, QRAFFLE_TRANSFER_SHARE_FEE); + qraffle.transferShareOwnershipAndPossession(issuer, assetName, issuer, 999999, poorUser2); + result = qraffle.depositInTokenRaffle(poorUser2, 0, QRAFFLE_TRANSFER_SHARE_FEE); + EXPECT_EQ(result.returnCode, QRAFFLE_FAILED_TO_DEPOSIT); + + // Test invalid token raffle index + increaseEnergy(users[0], QRAFFLE_TRANSFER_SHARE_FEE); + result = qraffle.depositInTokenRaffle(users[0], 999, QRAFFLE_TRANSFER_SHARE_FEE); + EXPECT_EQ(result.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); +} + +TEST(ContractQraffle, TransferShareManagementRights) +{ + ContractTestingQraffle qraffle; + + id issuer = getUser(1000); + increaseEnergy(issuer, 2000000000ULL); + uint64 assetName = assetNameFromString("TOKENRF"); + qraffle.issueAsset(issuer, assetName, 1000000000000, 0, 0); + + id user1 = getUser(1001); + increaseEnergy(user1, 1000000000ULL); + qraffle.transferShareOwnershipAndPossession(issuer, assetName, issuer, 1000000, user1); + EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName, QRAFFLE_CONTRACT_INDEX, 1000000, user1), 1000000); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user1, user1, QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), 1000000); + + increaseEnergy(user1, 1000000000ULL); + qraffle.TransferShareManagementRightsQraffle(issuer, assetName, QX_CONTRACT_INDEX, 1000000, user1); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, user1, user1, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), 1000000); +} + +TEST(ContractQraffle, GetFunctions) +{ + ContractTestingQraffle qraffle; + system.epoch = 0; + + // Setup: Create test users and register them + auto users = getRandomUsers(1000, 1000); // Use smaller set for more predictable testing + uint32 registerCount = 5; + uint32 proposalCount = 0; + uint32 entrySubmittedCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + registerCount++; + } + + // Submit entry amounts for some users + for (size_t i = 0; i < users.size() / 2; ++i) + { + uint64 amount = random(1000000, 1000000000); + auto result = qraffle.submitEntryAmount(users[i], amount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + entrySubmittedCount++; + } + + // Create some proposals + id issuer = getUser(2000); + increaseEnergy(issuer, 1000000000ULL); + uint64 assetName1 = assetNameFromString("TEST1"); + uint64 assetName2 = assetNameFromString("TEST2"); + qraffle.issueAsset(issuer, assetName1, 1000000000, 0, 0); + qraffle.issueAsset(issuer, assetName2, 2000000000, 0, 0); + + Asset token1, token2; + token1.assetName = assetName1; + token1.issuer = issuer; + token2.assetName = assetName2; + token2.issuer = issuer; + + // Submit proposals + for (size_t i = 0; i < std::min(users.size(), (size_t)5); ++i) + { + uint64 entryAmount = random(1000000, 1000000000); + Asset token = (i % 2 == 0) ? token1 : token2; + + increaseEnergy(users[i], 1000); + auto result = qraffle.submitProposal(users[i], token, entryAmount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + proposalCount++; + } + + // Vote on proposals + for (const auto& user : users) + { + for (uint32 i = 0; i < proposalCount; ++i) + { + bit vote = (bit)(i % 2); + auto result = qraffle.voteInProposal(user, i, vote); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + } + } + + // Deposit in QuRaffle + uint32 memberCount = 0; + for (size_t i = 0; i < users.size() / 3; ++i) + { + increaseEnergy(users[i], qraffle.getState()->getQuRaffleEntryAmount()); + auto result = qraffle.depositInQuRaffle(users[i], qraffle.getState()->getQuRaffleEntryAmount()); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + memberCount++; + } + + // Test 1: getActiveProposal function + { + // Test with valid proposal indices + for (uint32 i = 0; i < proposalCount; ++i) + { + auto proposal = qraffle.getActiveProposal(i); + EXPECT_EQ(proposal.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(proposal.tokenName, (i % 2 == 0) ? assetName1 : assetName2); + EXPECT_EQ(proposal.tokenIssuer, issuer); + EXPECT_GT(proposal.entryAmount, 0); + EXPECT_GE(proposal.nYes, 0u); + EXPECT_GE(proposal.nNo, 0u); + } + + // Test with invalid proposal index (beyond available proposals) + auto invalidProposal = qraffle.getActiveProposal(proposalCount + 10); + EXPECT_EQ(invalidProposal.returnCode, QRAFFLE_INVALID_PROPOSAL); + + // Test with very large proposal index + auto largeIndexProposal = qraffle.getActiveProposal(UINT32_MAX); + EXPECT_EQ(largeIndexProposal.returnCode, QRAFFLE_INVALID_PROPOSAL); + } + + + // End epoch to create some ended raffles + qraffle.endEpoch(); + + // ===== DETAILED TEST CASES FOR EACH GETTER FUNCTION ===== + + // Test 2: getRegisters function + { + // Test with valid offset and limit + auto registers = qraffle.getRegisters(0, 10); + EXPECT_EQ(registers.returnCode, QRAFFLE_SUCCESS); + + // Test with offset beyond available registers + auto registers2 = qraffle.getRegisters(registerCount + 10, 5); + EXPECT_EQ(registers2.returnCode, QRAFFLE_INVALID_OFFSET_OR_LIMIT); + + // Test with limit exceeding maximum (1024) + auto registers3 = qraffle.getRegisters(0, 1025); + EXPECT_EQ(registers3.returnCode, QRAFFLE_INVALID_OFFSET_OR_LIMIT); + + // Test with offset + limit exceeding total registers + auto registers4 = qraffle.getRegisters(registerCount - 5, 10); + EXPECT_EQ(registers4.returnCode, QRAFFLE_INVALID_OFFSET_OR_LIMIT); + + // Test with zero limit + auto registers5 = qraffle.getRegisters(0, 0); + EXPECT_EQ(registers5.returnCode, QRAFFLE_SUCCESS); + } + + // Test 3: getAnalytics function + { + auto analytics = qraffle.getAnalytics(); + EXPECT_EQ(analytics.returnCode, QRAFFLE_SUCCESS); + + // Validate all analytics fields + EXPECT_GE(analytics.totalBurnAmount, 0); + EXPECT_GE(analytics.totalCharityAmount, 0); + EXPECT_GE(analytics.totalShareholderAmount, 0); + EXPECT_GE(analytics.totalRegisterAmount, 0); + EXPECT_GE(analytics.totalFeeAmount, 0); + EXPECT_GE(analytics.totalWinnerAmount, 0); + EXPECT_GE(analytics.largestWinnerAmount, 0); + EXPECT_EQ(analytics.numberOfRegisters, registerCount); + EXPECT_EQ(analytics.numberOfProposals, 0); + EXPECT_EQ(analytics.numberOfQuRaffleMembers, 0); + EXPECT_GE(analytics.numberOfActiveTokenRaffle, 0u); + EXPECT_GE(analytics.numberOfEndedTokenRaffle, 0u); + EXPECT_EQ(analytics.numberOfEntryAmountSubmitted, 0u); + + // Cross-validate with internal state + qraffle.getState()->analyticsChecker(analytics.totalBurnAmount, analytics.totalCharityAmount, + analytics.totalShareholderAmount, analytics.totalRegisterAmount, + analytics.totalFeeAmount, analytics.totalWinnerAmount, + analytics.largestWinnerAmount, analytics.numberOfRegisters, + analytics.numberOfProposals, analytics.numberOfQuRaffleMembers, + analytics.numberOfActiveTokenRaffle, analytics.numberOfEndedTokenRaffle, + analytics.numberOfEntryAmountSubmitted); + + // Direct-validate with calculated values + // Calculate expected values based on the test setup + uint64 expectedTotalBurnAmount = 0; + uint64 expectedTotalCharityAmount = 0; + uint64 expectedTotalShareholderAmount = 0; + uint64 expectedTotalRegisterAmount = 0; + uint64 expectedTotalFeeAmount = 0; + uint64 expectedTotalWinnerAmount = 0; + uint64 expectedLargestWinnerAmount = 0; + + // Calculate expected values from QuRaffle (if any members participated) + if (memberCount > 0) { + uint64 qREAmount = 10000000; // initial entry amount + uint64 totalQuRaffleAmount = qREAmount * memberCount; + + expectedTotalBurnAmount += (totalQuRaffleAmount * QRAFFLE_BURN_FEE) / 100; + expectedTotalCharityAmount += (totalQuRaffleAmount * QRAFFLE_CHARITY_FEE) / 100; + expectedTotalShareholderAmount += ((totalQuRaffleAmount * QRAFFLE_SHRAEHOLDER_FEE) / 100) / 676 * 676; + expectedTotalRegisterAmount += ((totalQuRaffleAmount * QRAFFLE_REGISTER_FEE) / 100) / registerCount * registerCount; + expectedTotalFeeAmount += (totalQuRaffleAmount * QRAFFLE_FEE) / 100; + + // Winner amount calculation (after all fees) + uint64 winnerAmount = totalQuRaffleAmount - expectedTotalBurnAmount - expectedTotalCharityAmount + - expectedTotalShareholderAmount - expectedTotalRegisterAmount - expectedTotalFeeAmount; + expectedTotalWinnerAmount += winnerAmount; + expectedLargestWinnerAmount = winnerAmount; // First winner sets the largest + } + + // Validate calculated values + EXPECT_EQ(analytics.totalBurnAmount, expectedTotalBurnAmount); + EXPECT_EQ(analytics.totalCharityAmount, expectedTotalCharityAmount); + EXPECT_EQ(analytics.totalShareholderAmount, expectedTotalShareholderAmount); + EXPECT_EQ(analytics.totalRegisterAmount, expectedTotalRegisterAmount); + EXPECT_EQ(analytics.totalFeeAmount, expectedTotalFeeAmount); + EXPECT_EQ(analytics.totalWinnerAmount, expectedTotalWinnerAmount); + EXPECT_EQ(analytics.largestWinnerAmount, expectedLargestWinnerAmount); + + // Validate counters + EXPECT_EQ(analytics.numberOfRegisters, registerCount); + EXPECT_EQ(analytics.numberOfProposals, 0); // Proposals are cleared after epoch end + EXPECT_EQ(analytics.numberOfQuRaffleMembers, 0); // Members are cleared after epoch end + EXPECT_EQ(analytics.numberOfActiveTokenRaffle, qraffle.getState()->getNumberOfActiveTokenRaffle()); + EXPECT_EQ(analytics.numberOfEndedTokenRaffle, qraffle.getState()->getNumberOfEndedTokenRaffle()); + EXPECT_EQ(analytics.numberOfEntryAmountSubmitted, 0); // Entry amounts are cleared after epoch end + + } + + // Test 4: getEndedTokenRaffle function + { + // Test with valid raffle indices (if any ended raffles exist) + for (uint32 i = 0; i < qraffle.getState()->getNumberOfEndedTokenRaffle(); ++i) + { + auto endedRaffle = qraffle.getEndedTokenRaffle(i); + EXPECT_EQ(endedRaffle.returnCode, QRAFFLE_SUCCESS); + EXPECT_NE(endedRaffle.epochWinner, id(0, 0, 0, 0)); // Winner should be set + EXPECT_GT(endedRaffle.entryAmount, 0); + EXPECT_GT(endedRaffle.numberOfMembers, 0u); + EXPECT_GE(endedRaffle.epoch, 0u); + } + + // Test with invalid raffle index (beyond available ended raffles) + auto invalidEndedRaffle = qraffle.getEndedTokenRaffle(qraffle.getState()->getNumberOfEndedTokenRaffle() + 10); + EXPECT_EQ(invalidEndedRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); + + // Test with very large raffle index + auto largeIndexEndedRaffle = qraffle.getEndedTokenRaffle(UINT32_MAX); + EXPECT_EQ(largeIndexEndedRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); + } + + // Test 5: getEpochRaffleIndexes function + { + // Test with current epoch (0) + auto raffleIndexes = qraffle.getEpochRaffleIndexes(0); + EXPECT_EQ(raffleIndexes.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(raffleIndexes.StartIndex, 0); + EXPECT_EQ(raffleIndexes.EndIndex, qraffle.getState()->getNumberOfActiveTokenRaffle()); + + // Test with future epoch + auto futureRaffleIndexes = qraffle.getEpochRaffleIndexes(1); + EXPECT_EQ(futureRaffleIndexes.returnCode, QRAFFLE_INVALID_EPOCH); + + // Test with past epoch (if any exist) + if (qraffle.getState()->getNumberOfEndedTokenRaffle() > 0) + { + auto pastRaffleIndexes = qraffle.getEpochRaffleIndexes(0); // Should work for epoch 0 + EXPECT_EQ(pastRaffleIndexes.returnCode, QRAFFLE_SUCCESS); + } + } + + // Test 6: getEndedQuRaffle function + { + // Test with current epoch (0) + auto endedQuRaffle = qraffle.getEndedQuRaffle(0); + EXPECT_EQ(endedQuRaffle.returnCode, QRAFFLE_SUCCESS); + EXPECT_NE(endedQuRaffle.epochWinner, id(0, 0, 0, 0)); // Winner should be set + EXPECT_GT(endedQuRaffle.receivedAmount, 0); + EXPECT_EQ(endedQuRaffle.entryAmount, 10000000); + EXPECT_EQ(endedQuRaffle.numberOfMembers, memberCount); + + // Test with future epoch + auto futureQuRaffle = qraffle.getEndedQuRaffle(1); + EXPECT_EQ(futureQuRaffle.returnCode, QRAFFLE_SUCCESS); + + // Test with very large epoch number + auto largeEpochQuRaffle = qraffle.getEndedQuRaffle(UINT16_MAX); + EXPECT_EQ(largeEpochQuRaffle.returnCode, QRAFFLE_SUCCESS); + } + + // Test 7: getActiveTokenRaffle function + { + // Test with valid raffle indices (if any active raffles exist) + for (uint32 i = 0; i < qraffle.getState()->getNumberOfActiveTokenRaffle(); ++i) + { + auto activeRaffle = qraffle.getActiveTokenRaffle(i); + EXPECT_EQ(activeRaffle.returnCode, QRAFFLE_SUCCESS); + EXPECT_GT(activeRaffle.tokenName, 0); + EXPECT_NE(activeRaffle.tokenIssuer, id(0, 0, 0, 0)); + EXPECT_GT(activeRaffle.entryAmount, 0); + } + + // Test with invalid raffle index (beyond available active raffles) + auto invalidActiveRaffle = qraffle.getActiveTokenRaffle(qraffle.getState()->getNumberOfActiveTokenRaffle() + 10); + EXPECT_EQ(invalidActiveRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); + + // Test with very large raffle index + auto largeIndexActiveRaffle = qraffle.getActiveTokenRaffle(UINT32_MAX); + EXPECT_EQ(largeIndexActiveRaffle.returnCode, QRAFFLE_INVALID_TOKEN_RAFFLE); + } +} + +TEST(ContractQraffle, EndEpoch) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + } + + // Submit entry amounts + for (const auto& user : users) + { + uint64 amount = random(1000000, 1000000000); + qraffle.submitEntryAmount(user, amount); + } + + // Create proposals and vote for them + id issuer = getUser(2000); + increaseEnergy(issuer, 3000000000ULL); + uint64 assetName1 = assetNameFromString("TOKEN1"); + uint64 assetName2 = assetNameFromString("TOKEN2"); + qraffle.issueAsset(issuer, assetName1, 1000000000, 0, 0); + qraffle.issueAsset(issuer, assetName2, 2000000000, 0, 0); + + Asset token1, token2; + token1.assetName = assetName1; + token1.issuer = issuer; + token2.assetName = assetName2; + token2.issuer = issuer; + + qraffle.submitProposal(users[0], token1, 1000000); + qraffle.submitProposal(users[1], token2, 2000000); + + // Vote yes for both proposals + for (const auto& user : users) + { + qraffle.voteInProposal(user, 0, 1); + qraffle.voteInProposal(user, 1, 1); + } + + // Deposit in QuRaffle + for (const auto& user : users) + { + increaseEnergy(user, qraffle.getState()->getQuRaffleEntryAmount()); + qraffle.depositInQuRaffle(user, qraffle.getState()->getQuRaffleEntryAmount()); + } + + // Deposit in token raffles + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_TRANSFER_SHARE_FEE + 1000000); + EXPECT_EQ(qraffle.transferShareOwnershipAndPossession(issuer, assetName1, issuer, 1000000, user), 1000000); + EXPECT_EQ(qraffle.transferShareOwnershipAndPossession(issuer, assetName2, issuer, 2000000, user), 2000000); + } + + // End epoch + qraffle.endEpoch(); + + qraffle.getState()->activeTokenRaffleChecker(0, token1, 1000000); + qraffle.getState()->activeTokenRaffleChecker(1, token2, 2000000); + + // Deposit in token raffles + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_TRANSFER_SHARE_FEE); + EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName1, QRAFFLE_CONTRACT_INDEX, 1000000, user), 1000000); + EXPECT_EQ(qraffle.TransferShareManagementRights(issuer, assetName2, QRAFFLE_CONTRACT_INDEX, 2000000, user), 2000000); + + qraffle.depositInTokenRaffle(user, 0, QRAFFLE_TRANSFER_SHARE_FEE); + qraffle.depositInTokenRaffle(user, 1, QRAFFLE_TRANSFER_SHARE_FEE); + } + + // Check that QuRaffle was processed + auto quRaffle = qraffle.getEndedQuRaffle(0); + EXPECT_EQ(quRaffle.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->quRaffleWinnerChecker(0, quRaffle.epochWinner, quRaffle.receivedAmount, + quRaffle.entryAmount, quRaffle.numberOfMembers, quRaffle.winnerIndex); + + qraffle.endEpoch(); + // Check that token raffles were processed + auto tokenRaffle1 = qraffle.getEndedTokenRaffle(0); + EXPECT_EQ(tokenRaffle1.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->endedTokenRaffleChecker(0, tokenRaffle1.epochWinner, token1, + tokenRaffle1.entryAmount, tokenRaffle1.numberOfMembers, + tokenRaffle1.winnerIndex, tokenRaffle1.epoch); + + auto tokenRaffle2 = qraffle.getEndedTokenRaffle(1); + EXPECT_EQ(tokenRaffle2.returnCode, QRAFFLE_SUCCESS); + qraffle.getState()->endedTokenRaffleChecker(1, tokenRaffle2.epochWinner, token2, + tokenRaffle2.entryAmount, tokenRaffle2.numberOfMembers, + tokenRaffle2.winnerIndex, tokenRaffle2.epoch); + + // Check analytics after epoch + auto analytics = qraffle.getAnalytics(); + EXPECT_EQ(analytics.returnCode, QRAFFLE_SUCCESS); + EXPECT_GT(analytics.totalBurnAmount, 0); + EXPECT_GT(analytics.totalCharityAmount, 0); + EXPECT_GT(analytics.totalShareholderAmount, 0); + EXPECT_GT(analytics.totalWinnerAmount, 0); +} + +TEST(ContractQraffle, RegisterInSystemWithQXMR) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Issue QXMR tokens to users + id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); + increaseEnergy(qxmrIssuer, 2000000000ULL); + qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); + + // Test successful registration with QXMR tokens + for (const auto& user : users) + { + increaseEnergy(user, 1000); + // Transfer QXMR tokens to user + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, user); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, user); + + // Register using QXMR tokens + auto result = qraffle.registerInSystem(user, 0, 1); // useQXMR = 1 + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + registerCount++; + qraffle.getState()->registerChecker(user, registerCount, true); + } + + // Test insufficient QXMR tokens + id poorUser = getUser(9999); + increaseEnergy(poorUser, 1000); + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT - 1, poorUser); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT - 1, poorUser); + auto result = qraffle.registerInSystem(poorUser, 0, 1); + EXPECT_EQ(result.returnCode, QRAFFLE_INSUFFICIENT_QXMR); + qraffle.getState()->registerChecker(poorUser, registerCount, false); + + // Test already registered with QXMR + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, users[0]); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, users[0]); + result = qraffle.registerInSystem(users[0], 0, 1); + EXPECT_EQ(result.returnCode, QRAFFLE_ALREADY_REGISTERED); + qraffle.getState()->registerChecker(users[0], registerCount, true); +} + +TEST(ContractQraffle, LogoutInSystemWithQXMR) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Issue QXMR tokens + id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); + increaseEnergy(qxmrIssuer, 2000000000ULL); + qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); + + // Register users with QXMR tokens first + for (const auto& user : users) + { + increaseEnergy(user, 1000); + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, user); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, user); + qraffle.registerInSystem(user, 0, 1); + registerCount++; + } + + // Test successful logout with QXMR tokens + for (const auto& user : users) + { + increaseEnergy(user, 1000); + auto result = qraffle.logoutInSystem(user); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + // Check that user received QXMR refund + uint64 expectedRefund = QRAFFLE_QXMR_REGISTER_AMOUNT - QRAFFLE_QXMR_LOGOUT_FEE; + EXPECT_EQ(numberOfPossessedShares(QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, user, user, QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), expectedRefund); + + registerCount--; + qraffle.getState()->unregisterChecker(user, registerCount); + } + + // Test unregistered user logout with QXMR + increaseEnergy(users[0], 1000); + auto result = qraffle.logoutInSystem(users[0]); + EXPECT_EQ(result.returnCode, QRAFFLE_UNREGISTERED); +} + +TEST(ContractQraffle, MixedRegistrationAndLogout) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Issue QXMR tokens + id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); + increaseEnergy(qxmrIssuer, 2000000000ULL); + qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); + + // Register some users with qubic, some with QXMR + for (size_t i = 0; i < users.size(); ++i) + { + if (i % 2 == 0) + { + // Register with qubic + increaseEnergy(users[i], QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.registerInSystem(users[i], QRAFFLE_REGISTER_AMOUNT, 0); // useQXMR = 0 + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + } + else + { + // Register with QXMR + increaseEnergy(users[i], 1000); + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, users[i]); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, users[i]); + auto result = qraffle.registerInSystem(users[i], 0, 1); // useQXMR = 1 + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + } + registerCount++; + } + + // Logout some users with qubic, some with QXMR + for (size_t i = 0; i < users.size(); ++i) + { + if (i % 2 == 0) + { + // Logout with qubic + auto result = qraffle.logoutInSystem(users[i]); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(getBalance(users[i]), QRAFFLE_REGISTER_AMOUNT - QRAFFLE_LOGOUT_FEE); + } + else + { + // Logout with QXMR + auto result = qraffle.logoutInSystem(users[i]); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + uint64 expectedRefund = QRAFFLE_QXMR_REGISTER_AMOUNT - QRAFFLE_QXMR_LOGOUT_FEE; + EXPECT_EQ(numberOfPossessedShares(QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, users[i], users[i], QRAFFLE_CONTRACT_INDEX, QRAFFLE_CONTRACT_INDEX), expectedRefund); + } + registerCount--; + } + + // Verify final state + EXPECT_EQ(qraffle.getState()->getNumberOfRegisters(), registerCount); +} + +TEST(ContractQraffle, QXMRInvalidTokenType) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Issue QXMR tokens + id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); + increaseEnergy(qxmrIssuer, 2000000000ULL); + qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); + + // Register user with qubic (token type 1) + increaseEnergy(users[0], QRAFFLE_REGISTER_AMOUNT); + qraffle.registerInSystem(users[0], QRAFFLE_REGISTER_AMOUNT, 0); + registerCount++; + + // Try to logout with QXMR when registered with qubic + auto result = qraffle.logoutInSystem(users[0]); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + // Register user with QXMR (token type 2) + increaseEnergy(users[1], 1000); + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, users[1]); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, users[1]); + qraffle.registerInSystem(users[1], 0, 1); + registerCount++; + + // Try to logout + result = qraffle.logoutInSystem(users[1]); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + registerCount--; +} + +TEST(ContractQraffle, QXMRRevenueDistribution) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + + // Issue QXMR tokens + id qxmrIssuer = qraffle.getState()->getQXMRIssuer(); + increaseEnergy(qxmrIssuer, 2000000000ULL); + qraffle.issueAsset(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, 10000000000000, 0, 0); + + // Register some users with QXMR to generate QXMR revenue + for (const auto& user : users) + { + increaseEnergy(user, 1000); + qraffle.transferShareOwnershipAndPossession(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, qxmrIssuer, QRAFFLE_QXMR_REGISTER_AMOUNT, user); + qraffle.TransferShareManagementRights(qxmrIssuer, QRAFFLE_QXMR_ASSET_NAME, QRAFFLE_CONTRACT_INDEX, QRAFFLE_QXMR_REGISTER_AMOUNT, user); + qraffle.registerInSystem(user, 0, 1); + registerCount++; + } + + uint64 expectedQXMRRevenue = 0; + // Logout some users to generate QXMR revenue + for (size_t i = 0; i < users.size(); ++i) + { + auto result = qraffle.logoutInSystem(users[i]); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + expectedQXMRRevenue += QRAFFLE_QXMR_LOGOUT_FEE; + registerCount--; + } + + // Check that QXMR revenue was recorded + EXPECT_EQ(qraffle.getState()->getEpochQXMRRevenue(), expectedQXMRRevenue); + + // Test QXMR revenue distribution during epoch end + increaseEnergy(users[0], QRAFFLE_DEFAULT_QRAFFLE_AMOUNT); + qraffle.depositInQuRaffle(users[0], QRAFFLE_DEFAULT_QRAFFLE_AMOUNT); + + qraffle.endEpoch(); + EXPECT_EQ(qraffle.getState()->getEpochQXMRRevenue(), expectedQXMRRevenue - div(expectedQXMRRevenue, 676ull) * 676); +} + +TEST(ContractQraffle, GetQuRaffleEntryAmountPerUser) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + uint32 entrySubmittedCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + registerCount++; + } + + // Test 1: Query entry amount for users who haven't submitted any + for (const auto& user : users) + { + auto result = qraffle.getQuRaffleEntryAmountPerUser(user); + EXPECT_EQ(result.returnCode, QRAFFLE_USER_NOT_FOUND); + EXPECT_EQ(result.entryAmount, 0); + } + + // Submit entry amounts for some users + std::vector submittedAmounts; + for (size_t i = 0; i < users.size() / 2; ++i) + { + uint64 amount = random(1000000, 1000000000); + auto result = qraffle.submitEntryAmount(users[i], amount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + submittedAmounts.push_back(amount); + entrySubmittedCount++; + } + + // Test 2: Query entry amount for users who have submitted amounts + for (size_t i = 0; i < submittedAmounts.size(); ++i) + { + auto result = qraffle.getQuRaffleEntryAmountPerUser(users[i]); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(result.entryAmount, submittedAmounts[i]); + } + + // Test 3: Query entry amount for users who haven't submitted amounts + for (size_t i = submittedAmounts.size(); i < users.size(); ++i) + { + auto result = qraffle.getQuRaffleEntryAmountPerUser(users[i]); + EXPECT_EQ(result.returnCode, QRAFFLE_USER_NOT_FOUND); + EXPECT_EQ(result.entryAmount, 0); + } + + // Test 4: Update entry amount and verify + uint64 newAmount = random(1000000, 1000000000); + auto result = qraffle.submitEntryAmount(users[0], newAmount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + + auto updatedResult = qraffle.getQuRaffleEntryAmountPerUser(users[0]); + EXPECT_EQ(updatedResult.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(updatedResult.entryAmount, newAmount); + + // Test 5: Query for non-existent user + id nonExistentUser = getUser(99999); + auto nonExistentResult = qraffle.getQuRaffleEntryAmountPerUser(nonExistentUser); + EXPECT_EQ(nonExistentResult.returnCode, QRAFFLE_USER_NOT_FOUND); + EXPECT_EQ(nonExistentResult.entryAmount, 0); + + // Test 6: Query for unregistered user + id unregisteredUser = getUser(88888); + increaseEnergy(unregisteredUser, QRAFFLE_REGISTER_AMOUNT); + auto unregisteredResult = qraffle.getQuRaffleEntryAmountPerUser(unregisteredUser); + EXPECT_EQ(unregisteredResult.returnCode, QRAFFLE_USER_NOT_FOUND); + EXPECT_EQ(unregisteredResult.entryAmount, 0); +} + +TEST(ContractQraffle, GetQuRaffleEntryAverageAmount) +{ + ContractTestingQraffle qraffle; + + auto users = getRandomUsers(1000, 1000); + uint32 registerCount = 5; + uint32 entrySubmittedCount = 0; + + // Register users first + for (const auto& user : users) + { + increaseEnergy(user, QRAFFLE_REGISTER_AMOUNT); + auto result = qraffle.registerInSystem(user, QRAFFLE_REGISTER_AMOUNT, 0); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + registerCount++; + } + + // Test 1: Query average when no users have submitted entry amounts + auto result = qraffle.getQuRaffleEntryAverageAmount(); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(result.entryAverageAmount, 0); + + // Submit entry amounts for some users + std::vector submittedAmounts; + uint64 totalAmount = 0; + for (size_t i = 0; i < users.size() / 2; ++i) + { + uint64 amount = random(1000000, 1000000000); + increaseEnergy(users[i], amount); + auto result = qraffle.submitEntryAmount(users[i], amount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + submittedAmounts.push_back(amount); + totalAmount += amount; + entrySubmittedCount++; + } + + // Test 2: Query average with submitted amounts + auto averageResult = qraffle.getQuRaffleEntryAverageAmount(); + EXPECT_EQ(averageResult.returnCode, QRAFFLE_SUCCESS); + + // Calculate expected average + uint64 expectedAverage = 0; + if (submittedAmounts.size() > 0) + { + expectedAverage = totalAmount / submittedAmounts.size(); + } + EXPECT_EQ(averageResult.entryAverageAmount, expectedAverage); + + // Test 3: Add more users and verify average updates + std::vector additionalAmounts; + uint64 additionalTotal = 0; + for (size_t i = users.size() / 2; i < users.size(); ++i) + { + uint64 amount = random(1000000, 1000000000); + auto result = qraffle.submitEntryAmount(users[i], amount); + EXPECT_EQ(result.returnCode, QRAFFLE_SUCCESS); + additionalAmounts.push_back(amount); + additionalTotal += amount; + entrySubmittedCount++; + } + + // Calculate new expected average + uint64 newTotalAmount = totalAmount + additionalTotal; + uint64 newExpectedAverage = newTotalAmount / (submittedAmounts.size() + additionalAmounts.size()); + + auto updatedAverageResult = qraffle.getQuRaffleEntryAverageAmount(); + EXPECT_EQ(updatedAverageResult.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(updatedAverageResult.entryAverageAmount, newExpectedAverage); + + // Test 4: Update existing user's entry amount and verify average + uint64 updatedAmount = random(1000000, 1000000000); + auto updateResult = qraffle.submitEntryAmount(users[0], updatedAmount); + EXPECT_EQ(updateResult.returnCode, QRAFFLE_SUCCESS); + + // Recalculate expected average with updated amount + uint64 recalculatedTotal = newTotalAmount - submittedAmounts[0] + updatedAmount; + uint64 recalculatedAverage = recalculatedTotal / (submittedAmounts.size() + additionalAmounts.size()); + + auto recalculatedAverageResult = qraffle.getQuRaffleEntryAverageAmount(); + EXPECT_EQ(recalculatedAverageResult.returnCode, QRAFFLE_SUCCESS); + EXPECT_EQ(recalculatedAverageResult.entryAverageAmount, recalculatedAverage); +} \ No newline at end of file diff --git a/test/contract_qrp.cpp b/test/contract_qrp.cpp new file mode 100644 index 000000000..29795dec9 --- /dev/null +++ b/test/contract_qrp.cpp @@ -0,0 +1,263 @@ +#define NO_UEFI + +#include "contract_testing.h" + +// Procedure/function indices (must match REGISTER_USER_FUNCTIONS_AND_PROCEDURES in `src/contracts/QReservePool.h`). +constexpr uint16 QRP_PROC_GET_RESERVE = 1; +constexpr uint16 QRP_PROC_ADD_ALLOWED_SC = 2; +constexpr uint16 QRP_PROC_REMOVE_ALLOWED_SC = 3; +constexpr uint16 QRP_PROC_SEND_RESERVE = 4; + +constexpr uint16 QRP_FUNC_GET_AVAILABLE_RESERVE = 1; +constexpr uint16 QRP_FUNC_GET_ALLOWED_SC = 2; + +static const id QRP_CONTRACT_ID(QRP_CONTRACT_INDEX, 0, 0, 0); +static const id QRP_DEFAULT_SC_ID(QRP_QTF_INDEX, 0, 0, 0); + +class QRPChecker : public QRP +{ +public: + const id& team() const { return teamAddress; } + const id& owner() const { return ownerAddress; } + bool hasAllowedSC(const id& sc) const { return allowedSmartContracts.contains(sc); } + uint64 allowedCount() const { return allowedSmartContracts.population(); } +}; + +class ContractTestingQRP : protected ContractTesting +{ +public: + ContractTestingQRP() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QRP); + callSystemProcedure(QRP_CONTRACT_INDEX, INITIALIZE); + } + + QRPChecker* state() { return reinterpret_cast(contractStates[QRP_CONTRACT_INDEX]); } + + uint64 balanceOf(const id& account) const { return static_cast(getBalance(account)); } + uint64 balanceQrp() const { return balanceOf(QRP_CONTRACT_ID); } + void fund(const id& account, uint64 amount) { increaseEnergy(account, amount); } + void fundQrp(uint64 amount) { fund(QRP_CONTRACT_ID, amount); } + + QRP::WithdrawReserve_output withdrawReserveReserve(const id& invocator, uint64 revenue, sint64 attachedAmount = 0) + { + QRP::WithdrawReserve_input input{revenue}; + QRP::WithdrawReserve_output output{}; + invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_GET_RESERVE, input, output, invocator, attachedAmount); + return output; + } + + QRP::AddAllowedSC_output addAllowedSC(const id& invocator, uint64 scIndex) + { + QRP::AddAllowedSC_input input{scIndex}; + QRP::AddAllowedSC_output output{}; + invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_ADD_ALLOWED_SC, input, output, invocator, 0); + return output; + } + + QRP::RemoveAllowedSC_output removeAllowedSC(const id& invocator, uint64 scIndex) + { + QRP::RemoveAllowedSC_input input{scIndex}; + QRP::RemoveAllowedSC_output output{}; + invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_REMOVE_ALLOWED_SC, input, output, invocator, 0); + return output; + } + + QRP::SendReserve_output sendReserve(const id& invocator, uint64 scIndex, uint64 amount) + { + QRP::SendReserve_input input{scIndex, amount}; + QRP::SendReserve_output output{}; + invokeUserProcedure(QRP_CONTRACT_INDEX, QRP_PROC_SEND_RESERVE, input, output, invocator, 0); + return output; + } + + QRP::GetAvailableReserve_output getAvailableReserve() const + { + QRP::GetAvailableReserve_input input{}; + QRP::GetAvailableReserve_output output{}; + callFunction(QRP_CONTRACT_INDEX, QRP_FUNC_GET_AVAILABLE_RESERVE, input, output); + return output; + } + + QRP::GetAllowedSC_output getAllowedSC() const + { + QRP::GetAllowedSC_input input{}; + QRP::GetAllowedSC_output output{}; + callFunction(QRP_CONTRACT_INDEX, QRP_FUNC_GET_ALLOWED_SC, input, output); + return output; + } +}; + +static bool containsAllowedSC(const QRP::GetAllowedSC_output& allowed, const id& sc) +{ + for (uint64 i = 0; i < QRP_ALLOWED_SC_NUM; ++i) + { + if (allowed.allowedSC.get(i) == sc) + { + return true; + } + } + return false; +} + +TEST(ContractQReservePool, WithdrawReserveEnforcesAuthorizationAndBalance) +{ + ContractTestingQRP qrp; + const id unauthorized = id::randomValue(); + qrp.fund(unauthorized, 0); + qrp.fund(QRP_DEFAULT_SC_ID, 0); + + QRP::WithdrawReserve_output denied = qrp.withdrawReserveReserve(unauthorized, 100); + EXPECT_EQ(denied.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); + EXPECT_EQ(denied.allocatedRevenue, 0ull); + + qrp.fundQrp(1000); + EXPECT_EQ(qrp.balanceQrp(), 1000); + + QRP::WithdrawReserve_output granted = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 600); + EXPECT_EQ(granted.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_EQ(granted.allocatedRevenue, 600ull); + EXPECT_EQ(qrp.balanceQrp(), 400); + EXPECT_EQ(qrp.balanceOf(QRP_DEFAULT_SC_ID), 600); + + QRP::WithdrawReserve_output insufficient = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 500); + EXPECT_EQ(insufficient.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE)); + EXPECT_EQ(insufficient.allocatedRevenue, 0ull); + EXPECT_EQ(qrp.balanceQrp(), 400); + EXPECT_EQ(qrp.balanceOf(QRP_DEFAULT_SC_ID), 600); +} + +TEST(ContractQReservePool, WithdrawReserve_ZeroAndExactRemaining) +{ + ContractTestingQRP qrp; + qrp.fund(QRP_DEFAULT_SC_ID, 0); + + qrp.fundQrp(1000); + EXPECT_EQ(qrp.balanceQrp(), 1000); + + // Zero request should not move funds. + const QRP::WithdrawReserve_output zero = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 0); + EXPECT_EQ(zero.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_EQ(zero.allocatedRevenue, 0ull); + EXPECT_EQ(qrp.balanceQrp(), 1000); + + // Exact remaining should succeed and drain the reserve. + const QRP::WithdrawReserve_output exact = qrp.withdrawReserveReserve(QRP_DEFAULT_SC_ID, 1000); + EXPECT_EQ(exact.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_EQ(exact.allocatedRevenue, 1000ull); + EXPECT_EQ(qrp.balanceQrp(), 0); + EXPECT_EQ(qrp.balanceOf(QRP_DEFAULT_SC_ID), 1000); +} + +TEST(ContractQReservePool, OwnerAddsAndRemovesSmartContracts) +{ + ContractTestingQRP qrp; + QRPChecker* state = qrp.state(); + constexpr uint64 newScIndex = 77; + const id newScId(newScIndex, 0, 0, 0); + const id outsider(200, 0, 0, 0); + qrp.fund(newScId, 0); + qrp.fund(outsider, 0); + qrp.fund(state->owner(), 0); + + QRP::AddAllowedSC_output deniedAdd = qrp.addAllowedSC(outsider, newScIndex); + EXPECT_EQ(deniedAdd.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); + EXPECT_FALSE(state->hasAllowedSC(newScId)); + + QRP::AddAllowedSC_output approvedAdd = qrp.addAllowedSC(state->owner(), newScIndex); + EXPECT_EQ(approvedAdd.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_TRUE(state->hasAllowedSC(newScId)); + + QRP::GetAllowedSC_output allowed = qrp.getAllowedSC(); + EXPECT_TRUE(containsAllowedSC(allowed, newScId)); + + QRP::RemoveAllowedSC_output deniedRemove = qrp.removeAllowedSC(outsider, newScIndex); + EXPECT_EQ(deniedRemove.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); + EXPECT_TRUE(state->hasAllowedSC(newScId)); + + QRP::RemoveAllowedSC_output approvedRemove = qrp.removeAllowedSC(state->owner(), newScIndex); + EXPECT_EQ(approvedRemove.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_FALSE(state->hasAllowedSC(newScId)); +} + +TEST(ContractQReservePool, OwnerAddRemove_IdempotencyAndBounds) +{ + ContractTestingQRP qrp; + QRPChecker* state = qrp.state(); + qrp.fund(state->owner(), 0); + + constexpr uint64 newScIndex = 88; + const id newScId(newScIndex, 0, 0, 0); + qrp.fund(newScId, 0); + + EXPECT_FALSE(state->hasAllowedSC(newScId)); + + // This test focuses on idempotency (repeat add/remove) while keeping authorization valid. + // Add twice: first should succeed, second should not change membership (return code may be SUCCESS or specific). + const auto add1 = qrp.addAllowedSC(state->owner(), newScIndex); + EXPECT_EQ(add1.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_TRUE(state->hasAllowedSC(newScId)); + + const auto add2 = qrp.addAllowedSC(state->owner(), newScIndex); + EXPECT_TRUE(state->hasAllowedSC(newScId)); + + // Remove twice: first should succeed, second should keep it removed (return code may be SUCCESS or specific). + const auto rem1 = qrp.removeAllowedSC(state->owner(), newScIndex); + EXPECT_EQ(rem1.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_FALSE(state->hasAllowedSC(newScId)); + + const auto rem2 = qrp.removeAllowedSC(state->owner(), newScIndex); + EXPECT_FALSE(state->hasAllowedSC(newScId)); +} + +TEST(ContractQReservePool, SendReserve_AllValidAndInvalidScenarios) +{ + ContractTestingQRP qrp; + QRPChecker* state = qrp.state(); + const id outsider(900, 0, 0, 0); + static constexpr uint64 recipientScIndex = 77; + const id recipientSc(recipientScIndex, 0, 0, 0); + static constexpr uint64 unknownScIndex = 999; + const id unknownSc(unknownScIndex, 0, 0, 0); + + qrp.fund(state->owner(), 0); + qrp.fund(outsider, 0); + qrp.fund(recipientSc, 0); + qrp.fund(unknownSc, 0); + + // ACCESS_DENIED: only owner can invoke SendReserve. + const QRP::SendReserve_output& denied = qrp.sendReserve(outsider, QRP_QTF_INDEX, 1); + EXPECT_EQ(denied.returnCode, QRP::toReturnCode(QRP::EReturnCode::ACCESS_DENIED)); + + // SC_NOT_FOUND: owner invokes, but target SC is not whitelisted. + const QRP::SendReserve_output& notFound = qrp.sendReserve(state->owner(), unknownScIndex, 1); + EXPECT_EQ(notFound.returnCode, QRP::toReturnCode(QRP::EReturnCode::SC_NOT_FOUND)); + + // Add a new allowed SC to validate insufficient/success flows with a non-default recipient. + const QRP::AddAllowedSC_output& addSc = qrp.addAllowedSC(state->owner(), recipientScIndex); + EXPECT_EQ(addSc.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_TRUE(state->hasAllowedSC(recipientSc)); + + // INSUFFICIENT_RESERVE: reserve is 0. + const QRP::SendReserve_output& insufficientZeroReserve = qrp.sendReserve(state->owner(), recipientScIndex, 1); + EXPECT_EQ(insufficientZeroReserve.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE)); + EXPECT_EQ(qrp.balanceQrp(), 0); + EXPECT_EQ(qrp.balanceOf(recipientSc), 0); + + qrp.fundQrp(1000); + EXPECT_EQ(qrp.balanceQrp(), 1000); + + // INSUFFICIENT_RESERVE: requested amount is above available reserve. + const QRP::SendReserve_output& insufficientAboveReserve = qrp.sendReserve(state->owner(), recipientScIndex, 1001); + EXPECT_EQ(insufficientAboveReserve.returnCode, QRP::toReturnCode(QRP::EReturnCode::INSUFFICIENT_RESERVE)); + EXPECT_EQ(qrp.balanceQrp(), 1000); + EXPECT_EQ(qrp.balanceOf(recipientSc), 0); + + // SUCCESS: exact available reserve should be transferable. + const QRP::SendReserve_output& successExact = qrp.sendReserve(state->owner(), recipientScIndex, 1000); + EXPECT_EQ(successExact.returnCode, QRP::toReturnCode(QRP::EReturnCode::SUCCESS)); + EXPECT_EQ(qrp.balanceQrp(), 0); + EXPECT_EQ(qrp.balanceOf(recipientSc), 1000); +} diff --git a/test/contract_qrwa.cpp b/test/contract_qrwa.cpp new file mode 100644 index 000000000..80ff77851 --- /dev/null +++ b/test/contract_qrwa.cpp @@ -0,0 +1,1852 @@ +#define NO_UEFI + +#include "contract_testing.h" +#include "test_util.h" + +#define ENABLE_BALANCE_DEBUG 0 + +// Pseudo IDs (for testing only) + +// QMINE_ISSUER is is also the ADMIN_ADDRESS +static const id QMINE_ISSUER = ID( + _Q, _M, _I, _N, _E, _Q, _Q, _X, _Y, _B, _E, _G, _B, _H, _N, _S, + _U, _P, _O, _U, _Y, _D, _I, _Q, _K, _Z, _P, _C, _B, _P, _Q, _I, + _I, _H, _U, _U, _Z, _M, _C, _P, _L, _B, _P, _C, _C, _A, _I, _A, + _R, _V, _Z, _B, _T, _Y, _K, _G +); +static const id ADMIN_ADDRESS = QMINE_ISSUER; + +// temporary holder for the initial 150M QMINE supply +static const id TREASURY_HOLDER = id::randomValue(); + +// Addresses for governance-set fees +static const id FEE_ADDR_E = id::randomValue(); // Electricity fees address +static const id FEE_ADDR_M = id::randomValue(); // Maintenance fees address +static const id FEE_ADDR_R = id::randomValue(); // Reinvestment fees address + +// pseudo test address for QMINE developer +static const id QMINE_DEV_ADDR_TEST = ID( + _Z, _O, _X, _X, _I, _D, _C, _Z, _I, _M, _G, _C, _E, _C, _C, _F, + _A, _X, _D, _D, _C, _M, _B, _B, _X, _C, _D, _A, _Q, _J, _I, _H, + _G, _O, _O, _A, _T, _A, _F, _P, _S, _B, _F, _I, _O, _F, _O, _Y, + _E, _C, _F, _K, _U, _F, _P, _B +); + +// Test accounts for holders and users +static const id HOLDER_A = id::randomValue(); +static const id HOLDER_B = id::randomValue(); +static const id HOLDER_C = id::randomValue(); +static const id USER_D = id::randomValue(); // no-share user +static const id DESTINATION_ADDR = id::randomValue(); // dest for asset releases + +// Test QMINE Asset (using the random issuer for testing only) +static const Asset QMINE_ASSET = { QMINE_ISSUER, 297666170193ULL }; + +// Fees for dependent contracts +static constexpr uint64 QX_ISSUE_ASSET_FEE = 1000000000ull; +static constexpr uint64 QX_TRANSFER_FEE = 100ull; // Fee for transfering assets back to QX +static constexpr uint64 QX_MGT_TRANSFER_FEE = 0ull; // Fee for QX::TransferShareManagementRights +static constexpr sint64 QUTIL_STM1_FEE = 10LL; // QUTIL SendToManyV1 fee (QUTIL_STM1_INVOCATION_FEE) + + +enum qRWAFunctionIds +{ + QRWA_FUNC_GET_GOV_PARAMS = 1, + QRWA_FUNC_GET_GOV_POLL = 2, + QRWA_FUNC_GET_ASSET_RELEASE_POLL = 3, + QRWA_FUNC_GET_TREASURY_BALANCE = 4, + QRWA_FUNC_GET_DIVIDEND_BALANCES = 5, + QRWA_FUNC_GET_TOTAL_DISTRIBUTED = 6 +}; + +enum qRWAProcedureIds +{ + QRWA_PROC_DONATE_TO_TREASURY = 3, + QRWA_PROC_VOTE_GOV_PARAMS = 4, + QRWA_PROC_CREATE_ASSET_RELEASE_POLL = 5, + QRWA_PROC_VOTE_ASSET_RELEASE = 6, + QRWA_PROC_DEPOSIT_GENERAL_ASSET = 7, + QRWA_PROC_REVOKE_ASSET = 8, +}; + +enum QxProcedureIds +{ + QX_PROC_ISSUE_ASSET = 1, + QX_PROC_TRANSFER_SHARES = 2, + QX_PROC_TRANSFER_MANAGEMENT = 9 +}; + +enum QutilProcedureIds +{ + QUTIL_PROC_SEND_TO_MANY_V1 = 1 +}; + + +class ContractTestingQRWA : protected ContractTesting +{ + // Grant access to protected/private members for setup + friend struct QRWA; + +public: + ContractTestingQRWA() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QRWA); + callSystemProcedure(QRWA_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QUTIL); + callSystemProcedure(QUTIL_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QSWAP); + callSystemProcedure(QSWAP_CONTRACT_INDEX, INITIALIZE); + + // Custom Initialization for qRWA State + // (Overrides defaults from INITIALIZE() for testing purposes) + QRWA* state = getState(); + + // Fee addresses + // Note: We want to check these Fee Addresses separately, + // we use different addresses instead of same address as the Admin Address + state->mCurrentGovParams.electricityAddress = FEE_ADDR_E; + state->mCurrentGovParams.maintenanceAddress = FEE_ADDR_M; + state->mCurrentGovParams.reinvestmentAddress = FEE_ADDR_R; + } + + QRWA* getState() + { + return (QRWA*)contractStates[QRWA_CONTRACT_INDEX]; + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(QRWA_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QRWA_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + void endTick(bool expectSuccess = true) + { + callSystemProcedure(QRWA_CONTRACT_INDEX, END_TICK, expectSuccess); + } + + // manually reset the last payout time for testing. + void resetPayoutTime() + { + getState()->mLastPayoutTime = { 0, 0, 0, 0, 0, 0, 0 }; + } + + // QX/QUTIL Contract Wrappers + + void issueAsset(const id& issuer, uint64 assetName, sint64 shares) + { + QX::IssueAsset_input input{ assetName, shares, 0, 0 }; + QX::IssueAsset_output output; + increaseEnergy(issuer, QX_ISSUE_ASSET_FEE); + invokeUserProcedure(QX_CONTRACT_INDEX, QX_PROC_ISSUE_ASSET, input, output, issuer, QX_ISSUE_ASSET_FEE); + } + + // Transfers asset ownership and possession on QX. + void transferAsset(const id& from, const id& to, const Asset& asset, sint64 shares) + { + QX::TransferShareOwnershipAndPossession_input input{ asset.issuer, to, asset.assetName, shares }; + QX::TransferShareOwnershipAndPossession_output output; + increaseEnergy(from, QX_TRANSFER_FEE); + invokeUserProcedure(QX_CONTRACT_INDEX, QX_PROC_TRANSFER_SHARES, input, output, from, QX_TRANSFER_FEE); + } + + // Transfers management rights of an asset to another contract + void transferManagementRights(const id& from, const Asset& asset, sint64 shares, uint32 toContract) + { + QX::TransferShareManagementRights_input input{ asset, shares, toContract }; + QX::TransferShareManagementRights_output output; + increaseEnergy(from, QX_MGT_TRANSFER_FEE); + invokeUserProcedure(QX_CONTRACT_INDEX, QX_PROC_TRANSFER_MANAGEMENT, input, output, from, QX_MGT_TRANSFER_FEE); + } + + // Simulates a dividend payout from QLI pool using QUTIL::SendToManyV1. + void sendToMany(const id& from, const id& to, sint64 amount) + { + QUTIL::SendToManyV1_input input = {}; + input.dst0 = to; + input.amt0 = amount; + QUTIL::SendToManyV1_output output; + increaseEnergy(from, amount + QUTIL_STM1_FEE); + invokeUserProcedure(QUTIL_CONTRACT_INDEX, QUTIL_PROC_SEND_TO_MANY_V1, input, output, from, amount + QUTIL_STM1_FEE); + } + + // QRWA Procedure Wrappers + + uint64 donateToTreasury(const id& from, uint64 amount) + { + QRWA::DonateToTreasury_input input{ amount }; + QRWA::DonateToTreasury_output output; + invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_DONATE_TO_TREASURY, input, output, from, 0); + return output.status; + } + + uint64 voteGovParams(const id& from, const QRWA::QRWAGovParams& params) + { + QRWA::VoteGovParams_input input{ params }; + QRWA::VoteGovParams_output output; + invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_VOTE_GOV_PARAMS, input, output, from, 0); + return output.status; + } + + QRWA::CreateAssetReleasePoll_output createAssetReleasePoll(const id& from, const QRWA::CreateAssetReleasePoll_input& input) + { + QRWA::CreateAssetReleasePoll_output output; + memset(&output, 0, sizeof(output)); + invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_CREATE_ASSET_RELEASE_POLL, input, output, from, 0); + return output; + } + + uint64 voteAssetRelease(const id& from, uint64 pollId, uint64 option) + { + QRWA::VoteAssetRelease_input input{ pollId, option }; + QRWA::VoteAssetRelease_output output; + invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_VOTE_ASSET_RELEASE, input, output, from, 0); + return output.status; + } + + uint64 depositGeneralAsset(const id& from, const Asset& asset, uint64 amount) + { + QRWA::DepositGeneralAsset_input input{ asset, amount }; + QRWA::DepositGeneralAsset_output output; + invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_DEPOSIT_GENERAL_ASSET, input, output, from, 0); + return output.status; + } + + QRWA::RevokeAssetManagementRights_output revokeAssetManagementRights(const id& from, const Asset& asset, sint64 numberOfShares) + { + QRWA::RevokeAssetManagementRights_input input; + input.asset = asset; + input.numberOfShares = numberOfShares; + + QRWA::RevokeAssetManagementRights_output output; + memset(&output, 0, sizeof(output)); + + invokeUserProcedure(QRWA_CONTRACT_INDEX, QRWA_PROC_REVOKE_ASSET, input, output, from, QRWA_RELEASE_MANAGEMENT_FEE); + return output; + } + + // QRWA Wrappers + + QRWA::QRWAGovParams getGovParams() + { + QRWA::GetGovParams_input input; + QRWA::GetGovParams_output output; + callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_GOV_PARAMS, input, output); + return output.params; + } + + QRWA::GetGovPoll_output getGovPoll(uint64 pollId) + { + QRWA::GetGovPoll_input input{ pollId }; + QRWA::GetGovPoll_output output; + callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_GOV_POLL, input, output); + return output; + } + + QRWA::GetAssetReleasePoll_output getAssetReleasePoll(uint64 pollId) + { + QRWA::GetAssetReleasePoll_input input{ pollId }; + QRWA::GetAssetReleasePoll_output output; + callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_ASSET_RELEASE_POLL, input, output); + return output; + } + + uint64 getTreasuryBalance() + { + QRWA::GetTreasuryBalance_input input; + QRWA::GetTreasuryBalance_output output; + callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_TREASURY_BALANCE, input, output); + return output.balance; + } + + QRWA::GetDividendBalances_output getDividendBalances() + { + QRWA::GetDividendBalances_input input; + QRWA::GetDividendBalances_output output; + callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_DIVIDEND_BALANCES, input, output); + return output; + } + + QRWA::GetTotalDistributed_output getTotalDistributed() + { + QRWA::GetTotalDistributed_input input; + QRWA::GetTotalDistributed_output output; + callFunction(QRWA_CONTRACT_INDEX, QRWA_FUNC_GET_TOTAL_DISTRIBUTED, input, output); + return output; + } + + void issueContractSharesHelper(unsigned int contractIndex, std::vector>& shares) + { + issueContractShares(contractIndex, shares); + } + + void createQswapPool(const id& source, uint64 assetName, sint64 fee) + { + QSWAP::CreatePool_input input{ assetName }; + QSWAP::CreatePool_output output; + invokeUserProcedure(QSWAP_CONTRACT_INDEX, 3, input, output, source, fee); + } + + void getQswapFees(QSWAP::Fees_output& output) + { + QSWAP::Fees_input input; + callFunction(QSWAP_CONTRACT_INDEX, 1, input, output); + } + + void runQswapEndTick() + { + callSystemProcedure(QSWAP_CONTRACT_INDEX, END_TICK); + } + +}; + +TEST(ContractQRWA, QswapDividend_PoolB) +{ + ContractTestingQRWA qrwa; + + // Create QRWA Shareholders + const id QRWA_SH1 = id::randomValue(); + increaseEnergy(QRWA_SH1, 100000000); + + std::vector> qrwaShares{ + {QRWA_SH1, NUMBER_OF_COMPUTORS} + }; + + qrwa.issueContractSharesHelper(QRWA_CONTRACT_INDEX, qrwaShares); + + //create QMINE Shareholders + const id QMINE_HOLDER = id::randomValue(); + increaseEnergy(QMINE_HOLDER, 100000000); + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); + qrwa.transferAsset(QMINE_ISSUER, QMINE_HOLDER, QMINE_ASSET, 1000000); + + + // Create QSWAP Shares and deposit them to QRWA + // QRWA owns 100 shares. Random holder owns the rest (576) + const id QSWAP_OTHER_HOLDER = id::randomValue(); + std::vector> qswapShares{ + {id(QRWA_CONTRACT_INDEX, 0, 0, 0), 100}, + {QSWAP_OTHER_HOLDER, 576} + }; + qrwa.issueContractSharesHelper(QSWAP_CONTRACT_INDEX, qswapShares); + + // now generate Revenue in QSWAP + + const id TRADER = id::randomValue(); + const id ASSET_ISSUER = id::randomValue(); + const uint64 ASSET_NAME = assetNameFromString("TSTCOIN"); + + increaseEnergy(TRADER, 10000000000); + increaseEnergy(ASSET_ISSUER, 10000000000); + + QSWAP::Fees_output qswapFees; + qrwa.getQswapFees(qswapFees); + + // issue asset on QX + qrwa.issueAsset(ASSET_ISSUER, ASSET_NAME, 1000000000); + + // Create Pool on QSWAP + // This generates 'poolCreationFee' for QSWAP shareholders, generating substantial revenue + qrwa.createQswapPool(ASSET_ISSUER, ASSET_NAME, qswapFees.poolCreationFee); + + // We skip AddLiquidity/Swap expectations as the pool creation fee + // alone is sufficient to test dividend routing + + uint64 totalShareholderRevenue = qswapFees.poolCreationFee; + + // Dividend Distribution + + // Get QRWA dividend balances BEFORE + auto qrwaDivsBefore = qrwa.getDividendBalances(); + EXPECT_EQ(qrwaDivsBefore.revenuePoolA, 0); + EXPECT_EQ(qrwaDivsBefore.revenuePoolB, 0); + + // Run END_TICK for QSWAP to distribute dividends + qrwa.runQswapEndTick(); + + // Calculate expected dividend for QRWA (100 shares) + // (TotalRevenue / 676) * 100 + uint64 expectedDividend = totalShareholderRevenue / NUMBER_OF_COMPUTORS * 100; + + // Get QRWA Dividend Balances AFTER + auto qrwaDivsAfter = qrwa.getDividendBalances(); + + // Verify Dividend Routing + // Pool A should be 0 (Only QUTIL transfers go here) + EXPECT_EQ(qrwaDivsAfter.revenuePoolA, 0); + + // Pool B should contain the dividend from QSWAP + EXPECT_EQ(qrwaDivsAfter.revenuePoolB, expectedDividend); +} + + +TEST(ContractQRWA, Initialization) +{ + ContractTestingQRWA qrwa; + + // Check gov params (set in test constructor) + auto params = qrwa.getGovParams(); + EXPECT_EQ(params.mAdminAddress, ADMIN_ADDRESS); + EXPECT_EQ(params.qmineDevAddress, QMINE_DEV_ADDR_TEST); + EXPECT_EQ(params.electricityAddress, FEE_ADDR_E); + EXPECT_EQ(params.maintenanceAddress, FEE_ADDR_M); + EXPECT_EQ(params.reinvestmentAddress, FEE_ADDR_R); + EXPECT_EQ(params.electricityPercent, 350); + EXPECT_EQ(params.maintenancePercent, 50); + EXPECT_EQ(params.reinvestmentPercent, 100); + + // Check pools and balances via public functions + EXPECT_EQ(qrwa.getTreasuryBalance(), 0); + auto divBalances = qrwa.getDividendBalances(); + EXPECT_EQ(divBalances.revenuePoolA, 0); + EXPECT_EQ(divBalances.revenuePoolB, 0); + EXPECT_EQ(divBalances.qmineDividendPool, 0); + EXPECT_EQ(divBalances.qrwaDividendPool, 0); + + auto distTotals = qrwa.getTotalDistributed(); + EXPECT_EQ(distTotals.totalQmineDistributed, 0); + EXPECT_EQ(distTotals.totalQRWADistributed, 0); +} + + +TEST(ContractQRWA, RevenueAccounting_POST_INCOMING_TRANSFER) +{ + ContractTestingQRWA qrwa; + + // Pool A from SC QUTIL + // We simulate this by calling QUTIL's SendToMany + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); + + auto divBalances = qrwa.getDividendBalances(); + EXPECT_EQ(divBalances.revenuePoolA, 1000000); + // We cannot test pool B as the test environment does not support standard transfer + // as noted in contract_testex.cpp + EXPECT_EQ(divBalances.revenuePoolB, 0); +} + +TEST(ContractQRWA, Governance_VoteGovParams_And_EndEpochCount) +{ + ContractTestingQRWA qrwa; + + // Issue QMINE, distribute, and run BEGIN_EPOCH + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 400000); // 40% + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, + { HOLDER_A, QX_CONTRACT_INDEX }), 400000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 600000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // 30% + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, + { HOLDER_B, QX_CONTRACT_INDEX }), 300000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 300000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 200000); // 20% + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, + { HOLDER_C, QX_CONTRACT_INDEX }), 200000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 100000); + + increaseEnergy(HOLDER_A, 1000000); + increaseEnergy(HOLDER_B, 1000000); + increaseEnergy(HOLDER_C, 1000000); + increaseEnergy(USER_D, 1000000); + + qrwa.beginEpoch(); + // Quorum should be 2/3 of 900,000 = 600,000 + + // Not a holder + EXPECT_EQ(qrwa.voteGovParams(USER_D, {}), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); + + // Invalid params (Admin NULL_ID) + QRWA::QRWAGovParams invalidParams = qrwa.getGovParams(); + invalidParams.mAdminAddress = NULL_ID; + EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, invalidParams), QRWA_STATUS_FAILURE_INVALID_INPUT); + + // Create new poll and vote for it + QRWA::QRWAGovParams paramsA = qrwa.getGovParams(); + paramsA.electricityPercent = 100; // Change one param + + EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, paramsA), QRWA_STATUS_SUCCESS); // Poll 0 + EXPECT_EQ(qrwa.voteGovParams(HOLDER_B, paramsA), QRWA_STATUS_SUCCESS); // Vote for Poll 0 + + // Change vote + QRWA::QRWAGovParams paramsB = qrwa.getGovParams(); + paramsB.maintenancePercent = 100; // Change another param + + EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, paramsB), QRWA_STATUS_SUCCESS); // Poll 1 + + // Mid-epoch sale + qrwa.transferAsset(HOLDER_B, USER_D, QMINE_ASSET, 150000); // B's balance is now 150k + EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, + { USER_D, QX_CONTRACT_INDEX }), 150000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, + { HOLDER_B, QX_CONTRACT_INDEX }), 150000); + + + // Accountant at END_EPOCH + qrwa.endEpoch(); + + // Check results: + // Poll 0 (ParamsA): HOLDER_B voted. Begin=300k, End=150k. VotingPower = 150k. + // Poll 1 (ParamsB): HOLDER_A voted. Begin=400k, End=400k. VotingPower = 400k. + // Total power = 900k. Quorum = 600k. + + // Poll 0 (ParamsA) failed. + auto poll0 = qrwa.getGovPoll(0); + EXPECT_EQ(poll0.status, QRWA_STATUS_SUCCESS); + EXPECT_EQ(poll0.proposal.score, 150000); + EXPECT_EQ(poll0.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); + + // Poll 1 (ParamsB) failed. + auto poll1 = qrwa.getGovPoll(1); + EXPECT_EQ(poll1.status, QRWA_STATUS_SUCCESS); + EXPECT_EQ(poll1.proposal.score, 400000); + EXPECT_EQ(poll1.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); + + // Params should be unchanged (still 50 from init) + EXPECT_EQ(qrwa.getGovParams().maintenancePercent, 50); + + // New Epoch: Test successful vote + qrwa.beginEpoch(); // New snapshot total: A(400k) + B(150k) + C(200k) = 750k. Quorum = 500k. + + // All holders vote for ParamsB + EXPECT_EQ(qrwa.voteGovParams(HOLDER_A, paramsB), QRWA_STATUS_SUCCESS); // Creates Poll 2 + EXPECT_EQ(qrwa.voteGovParams(HOLDER_B, paramsB), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteGovParams(HOLDER_C, paramsB), QRWA_STATUS_SUCCESS); + + qrwa.endEpoch(); + + // Check results: + // Poll 2 (ParamsB): A(400k) + B(150k) + C(200k) = 750k vote power. + // Vote passes. + auto poll2 = qrwa.getGovPoll(2); + EXPECT_EQ(poll2.status, QRWA_STATUS_SUCCESS); + EXPECT_EQ(poll2.proposal.score, 750000); + EXPECT_EQ(poll2.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); + + // Verify params were updated + EXPECT_EQ(qrwa.getGovParams().maintenancePercent, 100); +} + +TEST(ContractQRWA, Governance_AssetReleasePolls) +{ + ContractTestingQRWA qrwa; + + increaseEnergy(HOLDER_A, 1000000); + increaseEnergy(HOLDER_B, 1000000); + increaseEnergy(USER_D, 1000000); + increaseEnergy(DESTINATION_ADDR, 1000000); + + // Issue QMINE, distribute, and run BEGIN_EPOCH + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000 + 1000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1001000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 700000); // 70% + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, + { HOLDER_A, QX_CONTRACT_INDEX }), 700000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 301000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // 30% + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, + { HOLDER_B, QX_CONTRACT_INDEX }), 300000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000); + // QMINE_ISSUER (ADMIN_ADDRESS) now holds 1000 + + // Give SC 1000 QMINE for its treasury + qrwa.transferManagementRights(QMINE_ISSUER, QMINE_ASSET, 1000, QRWA_CONTRACT_INDEX); + EXPECT_EQ(qrwa.donateToTreasury(QMINE_ISSUER, 1000), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.getTreasuryBalance(), 1000); + + qrwa.beginEpoch(); + + // Not Admin + QRWA::CreateAssetReleasePoll_input pollInput = {}; + pollInput.proposalName = id::randomValue(); + pollInput.asset = QMINE_ASSET; + pollInput.amount = 100; + pollInput.destination = DESTINATION_ADDR; + + auto pollOut = qrwa.createAssetReleasePoll(HOLDER_A, pollInput); // HOLDER_A is not admin + EXPECT_EQ(pollOut.status, QRWA_STATUS_FAILURE_NOT_AUTHORIZED); + + // Admin creates poll + pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); + EXPECT_EQ(pollOut.status, QRWA_STATUS_SUCCESS); + EXPECT_EQ(pollOut.proposalId, 0); + + // Not a holder + EXPECT_EQ(qrwa.voteAssetRelease(USER_D, 0, 1), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); + + // Holders vote + EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, 0, 1), QRWA_STATUS_SUCCESS); // 700k YES + EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_B, 0, 0), QRWA_STATUS_SUCCESS); // 300k NO + + // Add revenue to Pool A so the contract can pay the release fee + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); + EXPECT_EQ(qrwa.getDividendBalances().revenuePoolA, 1000000); + + // Count at end epoch (Pass) + qrwa.endEpoch(); + + auto poll = qrwa.getAssetReleasePoll(0); + EXPECT_EQ(poll.status, QRWA_STATUS_SUCCESS); + EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); // Should pass now + EXPECT_EQ(poll.proposal.votesYes, 700000); + EXPECT_EQ(poll.proposal.votesNo, 300000); + + // Verify balances + EXPECT_EQ(qrwa.getTreasuryBalance(), 900); // 1000 - 100 + EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }, + { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 100); // Should be 100 now + + // Count at end epoch (Fail Vote) + qrwa.beginEpoch(); + pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); // Poll 1 + EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, 1, 0), QRWA_STATUS_SUCCESS); // 700k NO + EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_B, 1, 1), QRWA_STATUS_SUCCESS); // 300k YES + qrwa.endEpoch(); + + poll = qrwa.getAssetReleasePoll(1); + EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); + EXPECT_EQ(qrwa.getTreasuryBalance(), 900); // Unchanged + + // Count at end epoch (Fail Execution - Insufficient) + qrwa.beginEpoch(); + pollInput.amount = 1000; // Try to release 1000 (only 900 left) + pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); // Poll 2 + EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, 2, 1), QRWA_STATUS_SUCCESS); // 700k YES + qrwa.endEpoch(); + + poll = qrwa.getAssetReleasePoll(2); + EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_PASSED_FAILED_EXECUTION); + EXPECT_EQ(qrwa.getTreasuryBalance(), 900); // Unchanged +} + +TEST(ContractQRWA, Governance_AssetRelease_FailAndRevoke) +{ + ContractTestingQRWA qrwa; + + const sint64 initialEnergy = 1000000000; + increaseEnergy(HOLDER_A, initialEnergy); + increaseEnergy(HOLDER_B, initialEnergy); + increaseEnergy(ADMIN_ADDRESS, initialEnergy + QX_ISSUE_ASSET_FEE); + increaseEnergy(DESTINATION_ADDR, initialEnergy); + + const sint64 treasuryAmount = 1000; + const sint64 voterShares = 1000000; + const sint64 releaseAmount = 500; + + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, voterShares + treasuryAmount); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 700000); + qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); + + // Give qRWA management rights over the treasury shares + qrwa.transferManagementRights(QMINE_ISSUER, QMINE_ASSET, treasuryAmount, QRWA_CONTRACT_INDEX); + + // Verify management rights were transferred + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QRWA_CONTRACT_INDEX }, + { QMINE_ISSUER, QRWA_CONTRACT_INDEX }), treasuryAmount); + + // Donate the shares to the treasury + EXPECT_EQ(qrwa.donateToTreasury(QMINE_ISSUER, treasuryAmount), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.getTreasuryBalance(), treasuryAmount); + + // Verify Revenue Pool A (for fees) is empty. + auto divBalances = qrwa.getDividendBalances(); + EXPECT_EQ(divBalances.revenuePoolA, 0); + + qrwa.beginEpoch(); + // Total voting power = 1,000,000 (HOLDER_A + HOLDER_B) + // Quorum = (1,000,000 * 2 / 3) + 1 = 666,667 + + QRWA::CreateAssetReleasePoll_input pollInput = {}; + pollInput.proposalName = id::randomValue(); + pollInput.asset = QMINE_ASSET; + pollInput.amount = releaseAmount; + pollInput.destination = DESTINATION_ADDR; + + // create poll + auto pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); + EXPECT_EQ(pollOut.status, QRWA_STATUS_SUCCESS); + uint64 pollId = pollOut.proposalId; + + // HOLDER_A votes YES, passing the poll (700k > 666k quorum) + EXPECT_EQ(qrwa.voteAssetRelease(HOLDER_A, pollId, 1), QRWA_STATUS_SUCCESS); + + qrwa.endEpoch(); + + // Check poll status + // It should have passed the vote but failed execution (due to lack of 100 QUs fee for QX management transfer) + auto poll = qrwa.getAssetReleasePoll(pollId); + EXPECT_EQ(poll.proposal.status, QRWA_POLL_STATUS_PASSED_FAILED_EXECUTION); + EXPECT_EQ(poll.proposal.votesYes, 700000); + + // Check SC asset state + // Asserts the INTERNAL counter is now decreased + EXPECT_EQ(qrwa.getTreasuryBalance(), treasuryAmount - releaseAmount); // 1000 - 500 = 500 + + // the SC balance is decreased + sint64 scOwnedBalance = numberOfShares(QMINE_ASSET, + { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }, + { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }); + EXPECT_EQ(scOwnedBalance, treasuryAmount - releaseAmount); // 1000 - 500 = 500 + + // DESTINATION_ADDR should now owns the shares, but they are MANAGED by qRWA + sint64 destManagedByQrwa = numberOfShares(QMINE_ASSET, + { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }, + { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }); + EXPECT_EQ(destManagedByQrwa, releaseAmount); // 500 shares are stuck + + // DESTINATION_ADDR should have 0 shares managed by QX + sint64 destManagedByQx = numberOfShares(QMINE_ASSET, + { DESTINATION_ADDR, QX_CONTRACT_INDEX }, + { DESTINATION_ADDR, QX_CONTRACT_INDEX }); + EXPECT_EQ(destManagedByQx, 0); + + // Test Revoke + qrwa.beginEpoch(); + + // Fund DESTINATION_ADDR with the fee for the revoke procedure + increaseEnergy(DESTINATION_ADDR, QRWA_RELEASE_MANAGEMENT_FEE); + sint64 destBalanceBeforeRevoke = getBalance(DESTINATION_ADDR); + + // DESTINATION_ADDR calls revokeAssetManagementRights + auto revokeOut = qrwa.revokeAssetManagementRights(DESTINATION_ADDR, QMINE_ASSET, releaseAmount); + + // check the outcome + EXPECT_EQ(revokeOut.status, QRWA_STATUS_SUCCESS); + EXPECT_EQ(revokeOut.transferredNumberOfShares, releaseAmount); + + // check final on-chain asset state + // DESTINATION_ADDR should be no longer have shares managed by qRWA + destManagedByQrwa = numberOfShares(QMINE_ASSET, + { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }, + { DESTINATION_ADDR, QRWA_CONTRACT_INDEX }); + EXPECT_EQ(destManagedByQrwa, 0); + + // DESTINATION_ADDR's shares should now be managed by QX + destManagedByQx = numberOfShares(QMINE_ASSET, + { DESTINATION_ADDR, QX_CONTRACT_INDEX }, + { DESTINATION_ADDR, QX_CONTRACT_INDEX }); + EXPECT_EQ(destManagedByQx, releaseAmount); + + // check if the fee was paid by the user + sint64 destBalanceAfterRevoke = getBalance(DESTINATION_ADDR); + EXPECT_EQ(destBalanceAfterRevoke, destBalanceBeforeRevoke - QRWA_RELEASE_MANAGEMENT_FEE); + + // Critical check: + // Verify that the fee sent to the SC was NOT permanently added to Pool B. + // The POST_INCOMING_TRANSFER adds 100 QU to Pool B. + // The procedure executes, spends 100 QU to QX, and logic must subtract 100 from Pool B. + // Net result for Pool B must be 0. + auto finalDivBalances = qrwa.getDividendBalances(); + EXPECT_EQ(finalDivBalances.revenuePoolB, 0); +} + +TEST(ContractQRWA, Treasury_Donation) +{ + ContractTestingQRWA qrwa; + + // Issue QMINE to the temporary treasury holder + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 150000000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 150000000); + + qrwa.transferAsset(QMINE_ISSUER, TREASURY_HOLDER, QMINE_ASSET, 150000000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { TREASURY_HOLDER, QX_CONTRACT_INDEX }, + { TREASURY_HOLDER, QX_CONTRACT_INDEX }), 150000000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 0); + + increaseEnergy(TREASURY_HOLDER, 1000000); + + // Fail (No Management Rights) + EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, 1000), QRWA_STATUS_FAILURE_INSUFFICIENT_BALANCE); + + // Success (With Management Rights) + // Give SC management rights + qrwa.transferManagementRights(TREASURY_HOLDER, QMINE_ASSET, 150000000, QRWA_CONTRACT_INDEX); + + // Verify rights + sint64 managedBalance = numberOfShares(QMINE_ASSET, + { TREASURY_HOLDER, QRWA_CONTRACT_INDEX }, + { TREASURY_HOLDER, QRWA_CONTRACT_INDEX }); + EXPECT_EQ(managedBalance, 150000000); + + // Donate + EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, 150000000), QRWA_STATUS_SUCCESS); + + // Verify treasury balance in SC + EXPECT_EQ(qrwa.getTreasuryBalance(), 150000000); + + // Verify SC now owns the shares + sint64 scOwnedBalance = numberOfShares(QMINE_ASSET, + { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }, + { id(QRWA_CONTRACT_INDEX, 0, 0, 0), QRWA_CONTRACT_INDEX }); + EXPECT_EQ(scOwnedBalance, 150000000); +} + +TEST(ContractQRWA, Payout_FullDistribution) +{ + ContractTestingQRWA qrwa; + + // Issue QMINE, distribute, and run BEGIN_EPOCH + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000000); + + increaseEnergy(HOLDER_A, 1000000); + increaseEnergy(HOLDER_B, 1000000); + increaseEnergy(HOLDER_C, 1000000); + increaseEnergy(USER_D, 1000000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 200000); // Holder A + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, + { HOLDER_A, QX_CONTRACT_INDEX }), 200000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 800000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // Holder B + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, + { HOLDER_B, QX_CONTRACT_INDEX }), 300000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 500000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 100000); // Holder C + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, + { HOLDER_C, QX_CONTRACT_INDEX }), 100000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 400000); + + qrwa.beginEpoch(); + // mTotalQmineBeginEpoch = 1,000,000 + + // Mid-epoch transfers + qrwa.transferAsset(HOLDER_A, USER_D, QMINE_ASSET, 50000); // Holder A ends with 150k + EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, + { USER_D, QX_CONTRACT_INDEX }), 50000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, + { HOLDER_A, QX_CONTRACT_INDEX }), 150000); + + qrwa.transferAsset(HOLDER_C, USER_D, QMINE_ASSET, 100000); // Holder C ends with 0 + EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, + { USER_D, QX_CONTRACT_INDEX }), 150000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, + { HOLDER_C, QX_CONTRACT_INDEX }), 0); + + // Deposit revenue + // Pool A (from SC) + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); + + // Pool B (from User) - Untestable. We will proceed using only Pool A. + + qrwa.endEpoch(); + + // Set time to payout day + etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 7; // A Friday + etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; + + // Use helper to reset payout time + qrwa.resetPayoutTime(); // Reset time to allow payout + + // Call END_TICK to trigger DistributeRewards + qrwa.endTick(); + + // Verification + // Fees: Pool A = 1M + // Elec (35%) = 350,000 + // Maint (5%) = 50,000 + // Reinv (10%) = 100,000 + // Total Fees = 500,000 + EXPECT_EQ(getBalance(FEE_ADDR_E), 350000); + EXPECT_EQ(getBalance(FEE_ADDR_M), 50000); + EXPECT_EQ(getBalance(FEE_ADDR_R), 100000); + + // Distribution Pool + // Y_revenue = 1,000,000 - 500,000 = 500,000 + // totalDistribution = 500,000 (Y) + 0 (B) = 500,000 + // mQmineDividendPool = 500k * 90% = 450,000 + // mQRWADividendPool = 500k * 10% = 50,000 + + // qRWA Payout (50,000 QUs) + uint64 qrwaPerShare = 50000 / NUMBER_OF_COMPUTORS; // 73 + auto distTotals = qrwa.getTotalDistributed(); + EXPECT_EQ(distTotals.totalQRWADistributed, qrwaPerShare * NUMBER_OF_COMPUTORS); // 73 * 676 = 49328 + + // QMINE Payout (450,000 QUs) + // mPayoutTotalQmineBegin = 1,000,000 + + // Eligible Balances: + // H1: min(200k, 150k) = 150,000 + // H2: min(300k, 300k) = 300,000 + // H3: min(100k, 0) = 0 + // Issuer: min(400k, 400k) = 400,000 + // Total Eligible = 850,000 + + // Payouts: + // H1 Payout: (150,000 * 450,000) / 1,000,000 = 67,500 + // H2 Payout: (300,000 * 450,000) / 1,000,000 = 135,000 + // H3 Payout: 0 + // Issuer Payout: (400,000 * 450,000) / 1,000,000 = 180,000 + // Total Eligible Paid = 67,500 + 135,000 + 180,000 = 382,500 + // QMINE_DEV Payout (Remainder) = 450,000 - 382,500 = 67,500 + + EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 67500); + EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 135000); + EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 0); + EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 67500); + + // Re-check balances + EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 135000); + + // Check pools are empty (or contain only dust from integer division) + auto divBalances = qrwa.getDividendBalances(); + EXPECT_EQ(divBalances.revenuePoolA, 0); + EXPECT_EQ(divBalances.revenuePoolB, 0); + EXPECT_EQ(divBalances.qmineDividendPool, 0); + EXPECT_EQ(divBalances.qrwaDividendPool, 50000 - (qrwaPerShare * NUMBER_OF_COMPUTORS)); // Dust +} + +TEST(ContractQRWA, Payout_SnapshotLogic) +{ + ContractTestingQRWA qrwa; + + // Give energy to all participants + increaseEnergy(QMINE_ISSUER, 1000000000); + increaseEnergy(HOLDER_A, 1000000); + increaseEnergy(HOLDER_B, 1000000); + increaseEnergy(HOLDER_C, 1000000); + increaseEnergy(USER_D, 1000000); + + // Issue 3500 QMINE + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 3500); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, { QMINE_ISSUER, QX_CONTRACT_INDEX }), 3500); + + // Epoch 1 Setup: Distribute initial shares + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 1000); + qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 1000); + qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 1000); + // QMINE_ISSUER keeps 500 + + qrwa.beginEpoch(); + // Snapshot (Begin Epoch 1): + // Total: 3500 (A, B, C, Issuer) + // A: 1000 + // B: 1000 + // C: 1000 + // D: 0 + // Issuer: 500 + + // Epoch 1 Mid-Epoch Transfers + qrwa.transferAsset(HOLDER_A, USER_D, QMINE_ASSET, 500); // A: 500, D: 500 + qrwa.transferAsset(HOLDER_B, USER_D, QMINE_ASSET, 1000); // B: 0, D: 1500 + qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 500); // C: 1500, Issuer: 0 + + // Deposit 1M QUs into Pool A + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); + + qrwa.endEpoch(); + // Payout Snapshots (Epoch 1): + // mPayoutTotalQmineBegin: 3500 + // Eligible: + // A: min(1000, 500) = 500 + // B: min(1000, 0) = 0 + // C: min(1000, 1500) = 1000 + // D: (not in begin map) = 0 + // Issuer: min(500, 0) = 0 + // Total Eligible: 1500 + + // Payout Calculation (Epoch 1): + // Pool A: 1,000,000 -> Fees (50%) = 500,000 -> Y_revenue = 500,000 + // mQmineDividendPool (90%): 450,000 + // mQRWADividendPool (10%): 50,000 + + // Payouts: + // A: (500 * 450,000) / 3,500 = 64,285 + // B: 0 + // C: (1000 * 450,000) / 3,500 = 128,571 + // D: 0 + // Issuer: 0 + // totalEligiblePaid = 192,856 + // movedSharesPayout (QMINE_DEV) = 450,000 - 192,856 = 257,144 + + // Trigger Payout + etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 14; // Next Friday + etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; + qrwa.resetPayoutTime(); + qrwa.endTick(); + + // Verify Payout 1 + EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 64285); + EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 0); + EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 128571); + EXPECT_EQ(getBalance(USER_D), 1000000 + 0); + EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 257144); + + // Check C's balance again + EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 128571); + + + // Epoch 2 + qrwa.beginEpoch(); + // Snapshot (Begin Epoch 2): + // Total: 3500 + // A: 500, B: 0, C: 1500, D: 1500, Issuer: 0 + + // Epoch 2 Mid-Epoch Transfers + qrwa.transferAsset(USER_D, HOLDER_A, QMINE_ASSET, 500); // A: 1000, D: 1000 + qrwa.transferAsset(HOLDER_C, HOLDER_B, QMINE_ASSET, 1000); // C: 500, B: 1000 + + // Deposit 1M QUs into Pool A + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); + + qrwa.endEpoch(); + // Snapshot (End Epoch 2): + // A: 1000, B: 1000, C: 500, D: 1000, Issuer: 0 + // + // Payout Snapshots (Epoch 2): + // mPayoutTotalQmineBegin: 3500 + // Eligible: + // A: min(500, 1000) = 500 + // B: min(0, 1000) = 0 + // C: min(1500, 500) = 500 + // D: min(1500, 1000) = 1000 + // Total Eligible: 2000 + + // Payout Calculation (Epoch 2): + // Pool A: 1,000,000 -> Fees (50%) = 500,000 -> Y_revenue = 500,000 + // mQmineDividendPool (90%): 450,000 + // Payouts: + // A: (500 * 450,000) / 3,500 = 64,285 + // B: 0 + // C: (500 * 450,000) / 3,500 = 64,285 + // D: (1000 * 450,000) / 3,500 = 128,571 + // totalEligiblePaid = 257,141 + // movedSharesPayout (QMINE_DEV) = 450,000 - 257,141 = 192,859 + + // Trigger Payout 2 + etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 21; // Next Friday + etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; + qrwa.resetPayoutTime(); + qrwa.endTick(); + + // Verify Payout 2 (Cumulative) + // A: Base + payout1 + payout2 + EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 64285 + 64285); + // B: Base + payout1 + payout2 + EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 0 + 0); + // C: Base + payout1 + payout2 + EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 128571 + 64285); + // D: Base + payout1 + payout2 + EXPECT_EQ(getBalance(USER_D), 1000000 + 0 + 128571); + // QMINE dev: payout1 + payout2 + EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 257144 + 192859); +} + +TEST(ContractQRWA, Payout_FullDistribution2) +{ + ContractTestingQRWA qrwa; + + // Issue QMINE, distribute, and run BEGIN_EPOCH + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, 1000000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 1000000); + + increaseEnergy(HOLDER_A, 1000000); + increaseEnergy(HOLDER_B, 1000000); + increaseEnergy(HOLDER_C, 1000000); + increaseEnergy(USER_D, 1000000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, 200000); // Holder A + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, + { HOLDER_A, QX_CONTRACT_INDEX }), 200000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 800000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_B, QMINE_ASSET, 300000); // Holder B + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_B, QX_CONTRACT_INDEX }, + { HOLDER_B, QX_CONTRACT_INDEX }), 300000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 500000); + + qrwa.transferAsset(QMINE_ISSUER, HOLDER_C, QMINE_ASSET, 100000); // Holder C + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, + { HOLDER_C, QX_CONTRACT_INDEX }), 100000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { QMINE_ISSUER, QX_CONTRACT_INDEX }, + { QMINE_ISSUER, QX_CONTRACT_INDEX }), 400000); + + qrwa.beginEpoch(); + // mTotalQmineBeginEpoch = 1,000,000 (A:200k, B:300k, C:100k, Issuer:400k) + + // Mid-epoch transfers + qrwa.transferAsset(HOLDER_A, USER_D, QMINE_ASSET, 50000); // Holder A ends with 150k + EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, + { USER_D, QX_CONTRACT_INDEX }), 50000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_A, QX_CONTRACT_INDEX }, + { HOLDER_A, QX_CONTRACT_INDEX }), 150000); + + qrwa.transferAsset(HOLDER_C, USER_D, QMINE_ASSET, 100000); // Holder C ends with 0 + EXPECT_EQ(numberOfShares(QMINE_ASSET, { USER_D, QX_CONTRACT_INDEX }, + { USER_D, QX_CONTRACT_INDEX }), 150000); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { HOLDER_C, QX_CONTRACT_INDEX }, + { HOLDER_C, QX_CONTRACT_INDEX }), 0); + + // Deposit revenue + // Pool A (from SC) + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 3000000); // Increased revenue + + // Pool B (from User): Untestable. We will proceed using only Pool A. + + qrwa.endEpoch(); + + // Set time to payout day + etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 7; // A Friday + etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; + + // Use helper to reset payout time + qrwa.resetPayoutTime(); // Reset time to allow payout + + // Call END_TICK to trigger DistributeRewards + qrwa.endTick(); + + // Verification + // Fees: Pool A = 3M + // Elec (35%) = 1,050,000 + // Maint (5%) = 150,000 + // Reinv (10%) = 300,000 + // Total Fees = 1,500,000 + EXPECT_EQ(getBalance(FEE_ADDR_E), 1050000); + EXPECT_EQ(getBalance(FEE_ADDR_M), 150000); + EXPECT_EQ(getBalance(FEE_ADDR_R), 300000); + + // Distribution Pool + // Y_revenue = 3,000,000 - 1,500,000 = 1,500,000 + // totalDistribution = 1,500,000 (Y) + 0 (B) = 1,500,000 + // mQmineDividendPool = 1.5M * 90% = 1,350,000 + // mQRWADividendPool = 1.5M * 10% = 150,000 + + // qRWA Payout (150,000 QUs) + uint64 qrwaPerShare = 150000 / NUMBER_OF_COMPUTORS; // 150000 / 676 = 221 + auto distTotals = qrwa.getTotalDistributed(); + EXPECT_EQ(distTotals.totalQRWADistributed, qrwaPerShare * NUMBER_OF_COMPUTORS); // 221 * 676 = 149416 + + // QMINE Payout (1,350,000 QUs) + // mPayoutTotalQmineBegin = 1,000,000 (A:200k, B:300k, C:100k, Issuer:400k) + + // Eligible: + // H1: min(200k, 150k) = 150,000 + // H2: min(300k, 300k) = 300,000 + // H3: min(100k, 0) = 0 + // Issuer: min(400k, 400k) = 400,000 + // Total Eligible = 850,000 + + // Payouts: + // H1 Payout: (150,000 * 1,350,000) / 1,000,000 = 202,500 + // H2 Payout: (300,000 * 1,350,000) / 1,000,000 = 405,000 + // H3 Payout: 0 + // Issuer Payout: (400,000 * 1,350,000) / 1,000,000 = 540,000 + // Total Eligible Paid = 202,500 + 405,000 + 540,000 = 1,147,500 + // QMINE dev Payout (Remainder) = 1,350,000 - 1,147,500 = 202,500 + + EXPECT_EQ(getBalance(HOLDER_A), 1000000 + 202500); + EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 405000); + EXPECT_EQ(getBalance(HOLDER_C), 1000000 + 0); + EXPECT_EQ(getBalance(QMINE_DEV_ADDR_TEST), 202500); + + // Re-check B's balance + EXPECT_EQ(getBalance(HOLDER_B), 1000000 + 405000); + + + // Check pools are empty (or contain only dust from integer division) + auto divBalances = qrwa.getDividendBalances(); + EXPECT_EQ(divBalances.revenuePoolA, 0); + EXPECT_EQ(divBalances.revenuePoolB, 0); + EXPECT_EQ(divBalances.qmineDividendPool, 0); // QMINE dev gets the remainder + EXPECT_EQ(divBalances.qrwaDividendPool, 150000 - (qrwaPerShare * NUMBER_OF_COMPUTORS)); // Dust (584) +} + +TEST(ContractQRWA, FullScenario_DividendsAndGovernance) +{ + ContractTestingQRWA qrwa; + + /* --- SETUP --- */ + + etalonTick.year = 25; // 2025 + etalonTick.month = 11; // November + etalonTick.day = 7; // 7th (Friday) + etalonTick.hour = 12; + etalonTick.minute = 1; + etalonTick.second = 0; + etalonTick.millisecond = 0; + + // Helper to handle month rollovers for this test + auto advanceTime7Days = [&]() + { + etalonTick.day += 7; + // Simple logic for Nov/Dec 2025 + if (etalonTick.month == 11 && etalonTick.day > 30) { + etalonTick.day -= 30; + etalonTick.month++; + } + else if (etalonTick.month == 12 && etalonTick.day > 31) { + etalonTick.day -= 31; + etalonTick.month = 1; + etalonTick.year++; + } + }; + + // Constants + const sint64 TOTAL_SUPPLY = 1000000000000LL; // 1,000,000,000,000 = 1 Trillion + const sint64 TREASURY_INIT = 150000000000LL; // 150 Billion + const sint64 SHAREHOLDERS_TOTAL = 850000000000LL; // 850 Billion + const sint64 SHAREHOLDER_AMT = SHAREHOLDERS_TOTAL / 5; // 170 Billion each + const sint64 REVENUE_AMT = 10000000LL; // 10 Million QUs per epoch revenue + + // Known Pool Amounts derived from REVENUE_AMT and 50% total fees + // Revenue 10M -> Fees 5M -> Net 5M + const sint64 QMINE_POOL_AMT = 4500000LL; // 90% of 5M + const sint64 QRWA_POOL_AMT_BASE = 500000LL; // 10% of 5M + + const sint64 QRWA_TOTAL_SHARES = 676LL; + + // Track dust for qRWA pool to calculate accurate rates per epoch + sint64 currentQrwaDust = 0; + sint64 currentQXReleaseFee = 0; + + auto getQrwaRateForEpoch = [&](sint64 poolAmount) -> sint64 { + sint64 totalPool = poolAmount + currentQrwaDust; + sint64 rate = totalPool / QRWA_TOTAL_SHARES; + currentQrwaDust = totalPool % QRWA_TOTAL_SHARES; // Update dust for next epoch + return rate; + }; + + // Entities + const id S1 = id::randomValue(); // Hybrid: Holds QMINE + qRWA shares + const id S2 = id::randomValue(); // Control QMINE: Holds only QMINE + const id S3 = id::randomValue(); // QMINE only + const id S4 = id::randomValue(); // QMINE only + const id S5 = id::randomValue(); // QMINE only + const id Q1 = id::randomValue(); // Control qRWA: Holds only qRWA shares + const id Q2 = id::randomValue(); // qRWA only + + // Energy Funding + increaseEnergy(QMINE_ISSUER, QX_ISSUE_ASSET_FEE * 2 + 100000000); + increaseEnergy(TREASURY_HOLDER, 100000000); + increaseEnergy(S1, 100000000); + increaseEnergy(S2, 100000000); + increaseEnergy(S3, 100000000); + increaseEnergy(S4, 100000000); + increaseEnergy(S5, 100000000); + increaseEnergy(Q1, 100000000); + increaseEnergy(Q2, 100000000); + increaseEnergy(DESTINATION_ADDR, 1000000); + increaseEnergy(ADMIN_ADDRESS, 1000000); + + // Issue QMINE + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, TOTAL_SUPPLY); + + // Distribute to Treasury Holder + qrwa.transferAsset(QMINE_ISSUER, TREASURY_HOLDER, QMINE_ASSET, TREASURY_INIT); + + // Distribute to 5 Shareholders (170B each) + qrwa.transferAsset(QMINE_ISSUER, S1, QMINE_ASSET, SHAREHOLDER_AMT); + qrwa.transferAsset(QMINE_ISSUER, S2, QMINE_ASSET, SHAREHOLDER_AMT); + qrwa.transferAsset(QMINE_ISSUER, S3, QMINE_ASSET, SHAREHOLDER_AMT); + qrwa.transferAsset(QMINE_ISSUER, S4, QMINE_ASSET, SHAREHOLDER_AMT); + qrwa.transferAsset(QMINE_ISSUER, S5, QMINE_ASSET, SHAREHOLDER_AMT); + + // Issue and Distribute qrwa Contract Shares + std::vector> qrwaShares{ + {S1, 200}, + {Q1, 200}, + {Q2, 276} + }; + issueContractShares(QRWA_CONTRACT_INDEX, qrwaShares); + + // Snapshot balances + std::map prevBalances; + auto snapshotBalances = [&]() { + prevBalances[S1] = getBalance(S1); + prevBalances[S2] = getBalance(S2); + prevBalances[S3] = getBalance(S3); + prevBalances[S4] = getBalance(S4); + prevBalances[S5] = getBalance(S5); + prevBalances[Q1] = getBalance(Q1); + prevBalances[Q2] = getBalance(Q2); + prevBalances[DESTINATION_ADDR] = getBalance(DESTINATION_ADDR); + }; + snapshotBalances(); + + // Helper to calculate exact QMINE payout matching contract logic + // Payout = (EligibleBalance * DividendPool) / PayoutBase + auto calculateQminePayout = [&](sint64 balance, sint64 payoutBase, sint64 poolAmount) -> sint64 { + if (payoutBase == 0) return 0; + // Contract uses: div((uint128)balance * pool, totalEligible) + // We mimic that integer math here + uint128 res = (uint128)balance * (uint128)poolAmount; + res = res / (uint128)payoutBase; + return (sint64)res.low; + }; + + // Helper that uses the calculated rate for the current epoch + auto calculateQrwaPayout = [&](sint64 shares, sint64 currentRate) -> sint64 { + return shares * currentRate; + }; + +#if ENABLE_BALANCE_DEBUG + auto print_balances = [&]() + { + std::cout << "\n--- Current Balances ---" << std::endl; + std::cout << "S1: " << getBalance(S1) << std::endl; + std::cout << "S2: " << getBalance(S2) << std::endl; + std::cout << "S3: " << getBalance(S3) << std::endl; + std::cout << "S4: " << getBalance(S4) << std::endl; + std::cout << "S5: " << getBalance(S5) << std::endl; + std::cout << "Q1: " << getBalance(Q1) << std::endl; + std::cout << "Q2: " << getBalance(Q2) << std::endl; + std::cout << "Dest: " << getBalance(DESTINATION_ADDR) << std::endl; + std::cout << "Treasury: " << getBalance(TREASURY_HOLDER) << std::endl; + std::cout << "Dev: " << getBalance(QMINE_DEV_ADDR_TEST) << std::endl; + std::cout << "------------------------\n" << std::endl; + }; + + std::cout << "PRE-EPOCH 1\n"; + print_balances(); +#endif + // epoch 1 + qrwa.beginEpoch(); + + //Shareholders Exchange + qrwa.transferAsset(S1, S2, QMINE_ASSET, 10000000000LL); + qrwa.transferAsset(S3, S4, QMINE_ASSET, 5000000000LL); + + // Treasury Donation + qrwa.transferManagementRights(TREASURY_HOLDER, QMINE_ASSET, 10, QRWA_CONTRACT_INDEX); + EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, 10), QRWA_STATUS_SUCCESS); + + //Revenue + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + + qrwa.endEpoch(); + + // Checks Ep 1 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << "END-EPOCH 1\n"; + print_balances(); +#endif + + // Contract holds 10 shares. Base = Total Supply - 10 + sint64 payoutBaseEp1 = TOTAL_SUPPLY - 10; + sint64 qrwaRateEp1 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); // Standard pool for Ep 1 + + sint64 divS1 = calculateQminePayout(160000000000LL, payoutBaseEp1, QMINE_POOL_AMT); + sint64 divS2 = calculateQminePayout(170000000000LL, payoutBaseEp1, QMINE_POOL_AMT); + sint64 divS3 = calculateQminePayout(165000000000LL, payoutBaseEp1, QMINE_POOL_AMT); + sint64 divS4 = calculateQminePayout(170000000000LL, payoutBaseEp1, QMINE_POOL_AMT); + sint64 divS5 = calculateQminePayout(170000000000LL, payoutBaseEp1, QMINE_POOL_AMT); + + sint64 divQS1 = calculateQrwaPayout(200, qrwaRateEp1); + sint64 divQQ1 = calculateQrwaPayout(200, qrwaRateEp1); + sint64 divQQ2 = calculateQrwaPayout(276, qrwaRateEp1); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); + + snapshotBalances(); + +#if ENABLE_BALANCE_DEBUG + std::cout << "PRE-EPOCH 2\n"; + print_balances(); +#endif + + // epoch 2 + qrwa.beginEpoch(); + + // Treasury Donation (Remaining) + sint64 treasuryRemaining = TREASURY_INIT - 10; + qrwa.transferManagementRights(TREASURY_HOLDER, QMINE_ASSET, treasuryRemaining, QRWA_CONTRACT_INDEX); + EXPECT_EQ(qrwa.donateToTreasury(TREASURY_HOLDER, treasuryRemaining), QRWA_STATUS_SUCCESS); + + // Exchange + qrwa.transferAsset(S1, S2, QMINE_ASSET, 10000000000LL); + qrwa.transferAsset(S2, S3, QMINE_ASSET, 10000000000LL); + qrwa.transferAsset(S3, S4, QMINE_ASSET, 10000000000LL); + qrwa.transferAsset(S4, S5, QMINE_ASSET, 10000000000LL); + + // Revenue + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + + // Release Poll + QRWA::CreateAssetReleasePoll_input pollInput; + pollInput.proposalName = id::randomValue(); + pollInput.asset = QMINE_ASSET; + pollInput.amount = 1000; + pollInput.destination = DESTINATION_ADDR; + + auto pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); + uint64 pollIdEp2 = pollOut.proposalId; + + EXPECT_EQ(qrwa.voteAssetRelease(S1, pollIdEp2, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S2, pollIdEp2, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S3, pollIdEp2, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S4, pollIdEp2, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S5, pollIdEp2, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(Q1, pollIdEp2, 1), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); + + qrwa.endEpoch(); + + // Checks Ep 2 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << "END-EPOCH 2\n"; + print_balances(); +#endif + + auto pollResultEp2 = qrwa.getAssetReleasePoll(pollIdEp2); + EXPECT_EQ(pollResultEp2.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 1000); + + // Calculate Pools based on Revenue - 100 QU Fee + sint64 netRevenueEp2 = REVENUE_AMT - 100; + sint64 feeAmtEp2 = (netRevenueEp2 * 500) / 1000; // 50% fees + sint64 distributableEp2 = netRevenueEp2 - feeAmtEp2; + sint64 qminePoolEp2 = (distributableEp2 * 900) / 1000; + sint64 qrwaPoolEp2 = distributableEp2 - qminePoolEp2; + + // Correct Base: TOTAL_SUPPLY - 10 (Shares held by SC at START of epoch) + sint64 payoutBaseEp2 = TOTAL_SUPPLY - 10; + + sint64 qrwaRateEp2 = getQrwaRateForEpoch(qrwaPoolEp2); + + divS1 = calculateQminePayout(150000000000LL, payoutBaseEp2, qminePoolEp2); + divS2 = calculateQminePayout(180000000000LL, payoutBaseEp2, qminePoolEp2); + divS3 = calculateQminePayout(165000000000LL, payoutBaseEp2, qminePoolEp2); + divS4 = calculateQminePayout(175000000000LL, payoutBaseEp2, qminePoolEp2); + divS5 = calculateQminePayout(170000000000LL, payoutBaseEp2, qminePoolEp2); + + divQS1 = calculateQrwaPayout(200, qrwaRateEp2); + divQQ1 = calculateQrwaPayout(200, qrwaRateEp2); + divQQ2 = calculateQrwaPayout(276, qrwaRateEp2); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); + + snapshotBalances(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " PRE-EPOCH 3\n"; + print_balances(); +#endif + + // epoch 3 + qrwa.beginEpoch(); + + // Exchange + qrwa.transferAsset(S1, S2, QMINE_ASSET, 5000000000LL); + qrwa.transferAsset(S2, S3, QMINE_ASSET, 5000000000LL); + qrwa.transferAsset(S3, S4, QMINE_ASSET, 5000000000LL); + qrwa.transferAsset(S4, S5, QMINE_ASSET, 5000000000LL); + + // Revenue + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + + // Release Poll + pollInput.amount = 500; + pollInput.proposalName = id::randomValue(); + pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); + uint64 pollIdEp3 = pollOut.proposalId; + + EXPECT_EQ(qrwa.voteAssetRelease(S1, pollIdEp3, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S2, pollIdEp3, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S3, pollIdEp3, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S4, pollIdEp3, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S5, pollIdEp3, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(Q1, pollIdEp3, 1), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); + + // Gov Vote + QRWA::QRWAGovParams newParams = qrwa.getGovParams(); + newParams.electricityPercent = 300; + newParams.maintenancePercent = 100; + + newParams.mAdminAddress = ADMIN_ADDRESS; + newParams.qmineDevAddress = QMINE_DEV_ADDR_TEST; + newParams.electricityAddress = FEE_ADDR_E; + newParams.maintenanceAddress = FEE_ADDR_M; + newParams.reinvestmentAddress = FEE_ADDR_R; + + EXPECT_EQ(qrwa.voteGovParams(S1, newParams), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteGovParams(S2, newParams), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteGovParams(S3, newParams), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteGovParams(S4, newParams), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteGovParams(S5, newParams), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteGovParams(Q1, newParams), QRWA_STATUS_FAILURE_NOT_AUTHORIZED); + + qrwa.endEpoch(); + + // Checks Ep 3 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " END-EPOCH 3\n"; + print_balances(); +#endif + + auto pollResultEp3 = qrwa.getAssetReleasePoll(pollIdEp3); + EXPECT_EQ(pollResultEp3.proposal.status, QRWA_POLL_STATUS_PASSED_EXECUTED); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 1000 + 500); + + auto activeParams = qrwa.getGovParams(); + EXPECT_EQ(activeParams.electricityPercent, 300); + EXPECT_EQ(activeParams.maintenancePercent, 100); + + // Calculate Pools based on Revenue - 100 QU Fee + sint64 netRevenueEp3 = REVENUE_AMT - 100; + sint64 feeAmtEp3 = (netRevenueEp3 * 500) / 1000; // 50% fees still (params update next epoch) + sint64 distributableEp3 = netRevenueEp3 - feeAmtEp3; + sint64 qminePoolEp3 = (distributableEp3 * 900) / 1000; + sint64 qrwaPoolEp3 = distributableEp3 - qminePoolEp3; + + // Contract released 1000 + 500. Balance = 150B - 1500. + sint64 payoutBaseEp3 = TOTAL_SUPPLY - (TREASURY_INIT - 1500); + + sint64 qrwaRateEp3 = getQrwaRateForEpoch(qrwaPoolEp3); + + divS1 = calculateQminePayout(145000000000LL, payoutBaseEp3, qminePoolEp3); + divS2 = calculateQminePayout(180000000000LL, payoutBaseEp3, qminePoolEp3); + divS3 = calculateQminePayout(165000000000LL, payoutBaseEp3, qminePoolEp3); + divS4 = calculateQminePayout(175000000000LL, payoutBaseEp3, qminePoolEp3); + divS5 = calculateQminePayout(180000000000LL, payoutBaseEp3, qminePoolEp3); + + divQS1 = calculateQrwaPayout(200, qrwaRateEp3); + divQQ1 = calculateQrwaPayout(200, qrwaRateEp3); + divQQ2 = calculateQrwaPayout(276, qrwaRateEp3); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); + + snapshotBalances(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " PRE-EPOCH 4\n"; + print_balances(); +#endif + + // epoch 4 (no transfers) + qrwa.beginEpoch(); + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + qrwa.endEpoch(); + + // Checks Ep 4 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " END-EPOCH 4\n"; + print_balances(); +#endif + + // Payout base remains same as previous epoch (no new releases) + sint64 payoutBaseEp4 = payoutBaseEp3; + // Revenue is full 10M (no releases) + sint64 qminePoolEp4 = QMINE_POOL_AMT; + + sint64 qrwaRateEp4 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); + + divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); + divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); + divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); + divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); + divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); + + divQS1 = calculateQrwaPayout(200, qrwaRateEp4); + divQQ1 = calculateQrwaPayout(200, qrwaRateEp4); + divQQ2 = calculateQrwaPayout(276, qrwaRateEp4); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); + + snapshotBalances(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " PRE-EPOCH 5\n"; + print_balances(); +#endif + + // epoch 5 + qrwa.beginEpoch(); + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + + // Release Poll + pollInput.amount = 100; + pollInput.proposalName = id::randomValue(); + pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); + uint64 pollIdEp5 = pollOut.proposalId; + + // Vote NO (3/5 Majority) + EXPECT_EQ(qrwa.voteAssetRelease(S1, pollIdEp5, 0), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S2, pollIdEp5, 0), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S3, pollIdEp5, 0), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S4, pollIdEp5, 1), QRWA_STATUS_SUCCESS); + EXPECT_EQ(qrwa.voteAssetRelease(S5, pollIdEp5, 1), QRWA_STATUS_SUCCESS); + + qrwa.endEpoch(); + + // Checks Ep 5 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " END-EPOCH 5\n"; + print_balances(); +#endif + + auto pollResultEp5 = qrwa.getAssetReleasePoll(pollIdEp5); + EXPECT_EQ(pollResultEp5.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); + EXPECT_EQ(numberOfShares(QMINE_ASSET, { DESTINATION_ADDR, QX_CONTRACT_INDEX }), 1500); // Unchanged + + // Failed vote = No release = No fee = Full Revenue. Base unchanged. + sint64 qrwaRateEp5 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); + + divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); + divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); + divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); + divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); + divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); + + divQS1 = calculateQrwaPayout(200, qrwaRateEp5); + divQQ1 = calculateQrwaPayout(200, qrwaRateEp5); + divQQ2 = calculateQrwaPayout(276, qrwaRateEp5); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); + + snapshotBalances(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " PRE-EPOCH 6\n"; + print_balances(); +#endif + + // epoch 6 + qrwa.beginEpoch(); + + // Revenue + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + + // Create Gov Proposal + QRWA::QRWAGovParams failParams = qrwa.getGovParams(); + failParams.reinvestmentPercent = 200; + + // Only S1 votes (< 20% supply). Quorum fail + EXPECT_EQ(qrwa.voteGovParams(S1, failParams), QRWA_STATUS_SUCCESS); + + qrwa.endEpoch(); + + // Checks Ep 6 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " END-EPOCH 6\n"; + print_balances(); +#endif + + auto paramsEp6 = qrwa.getGovParams(); + EXPECT_EQ(paramsEp6.reinvestmentPercent, 100); + EXPECT_NE(paramsEp6.reinvestmentPercent, 200); + + sint64 qrwaRateEp6 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); + + divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); + divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); + divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); + divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); + divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); + + divQS1 = calculateQrwaPayout(200, qrwaRateEp6); + divQQ1 = calculateQrwaPayout(200, qrwaRateEp6); + divQQ2 = calculateQrwaPayout(276, qrwaRateEp6); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); + + snapshotBalances(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " PRE-EPOCH 7\n"; + print_balances(); +#endif + + // epoch 7 + qrwa.beginEpoch(); + + // Revenue + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), REVENUE_AMT); + + // Create poll, no votes + pollInput.amount = 100; + pollInput.proposalName = id::randomValue(); + pollOut = qrwa.createAssetReleasePoll(ADMIN_ADDRESS, pollInput); + uint64 pollIdEp7 = pollOut.proposalId; + + qrwa.endEpoch(); + + // Checks Ep 7 + advanceTime7Days(); + qrwa.resetPayoutTime(); + qrwa.endTick(); + +#if ENABLE_BALANCE_DEBUG + std::cout << " END-EPOCH 7\n"; + print_balances(); +#endif + + auto pollResultEp7 = qrwa.getAssetReleasePoll(pollIdEp7); + EXPECT_EQ(pollResultEp7.proposal.status, QRWA_POLL_STATUS_FAILED_VOTE); + + sint64 qrwaRateEp7 = getQrwaRateForEpoch(QRWA_POOL_AMT_BASE); + + divS1 = calculateQminePayout(145000000000LL, payoutBaseEp4, qminePoolEp4); + divS2 = calculateQminePayout(180000000000LL, payoutBaseEp4, qminePoolEp4); + divS3 = calculateQminePayout(165000000000LL, payoutBaseEp4, qminePoolEp4); + divS4 = calculateQminePayout(175000000000LL, payoutBaseEp4, qminePoolEp4); + divS5 = calculateQminePayout(185000000000LL, payoutBaseEp4, qminePoolEp4); + + divQS1 = calculateQrwaPayout(200, qrwaRateEp7); + divQQ1 = calculateQrwaPayout(200, qrwaRateEp7); + divQQ2 = calculateQrwaPayout(276, qrwaRateEp7); + + EXPECT_EQ(getBalance(S1), prevBalances[S1] + divS1 + divQS1); + EXPECT_EQ(getBalance(S2), prevBalances[S2] + divS2); + EXPECT_EQ(getBalance(S3), prevBalances[S3] + divS3); + EXPECT_EQ(getBalance(S4), prevBalances[S4] + divS4); + EXPECT_EQ(getBalance(S5), prevBalances[S5] + divS5); + EXPECT_EQ(getBalance(Q1), prevBalances[Q1] + divQQ1); + EXPECT_EQ(getBalance(Q2), prevBalances[Q2] + divQQ2); +} + +TEST(ContractQRWA, Payout_MultiContractManagement) +{ + ContractTestingQRWA qrwa; + + const sint64 totalShares = 1000000; + const sint64 qxManagedShares = 700000; + const sint64 qswapManagedShares = 300000; // 30% moved to QSWAP management + + // Issue QMINE and give to HOLDER_A + // Initially, all 1M shares are managed by QX (default for transfers via QX) + increaseEnergy(QMINE_ISSUER, 1000000000); + increaseEnergy(HOLDER_A, 1000000); // For fees + + qrwa.issueAsset(QMINE_ISSUER, QMINE_ASSET.assetName, totalShares); + qrwa.transferAsset(QMINE_ISSUER, HOLDER_A, QMINE_ASSET, totalShares); + + // Verify initial state managed by QX + EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), totalShares); + EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), 0); + + // Transfer management rights of 300k shares to QSWAP + // The user (HOLDER_A) remains the Possessor. + qrwa.transferManagementRights(HOLDER_A, QMINE_ASSET, qswapManagedShares, QSWAP_CONTRACT_INDEX); + + // Verify the split in management rights + // 700k should remain under QX + EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), qxManagedShares); + // 300k should now be under QSWAP + EXPECT_EQ(numberOfPossessedShares(QMINE_ASSET.assetName, QMINE_ASSET.issuer, HOLDER_A, HOLDER_A, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), qswapManagedShares); + + qrwa.beginEpoch(); + + // Generate Revenue + // pool A revenue: 1,000,000 QUs + // fees (50%): 500,000 + // net revenue: 500,000 + // QMINE pool (90%): 450,000 + qrwa.sendToMany(ADMIN_ADDRESS, id(QRWA_CONTRACT_INDEX, 0, 0, 0), 1000000); + + qrwa.endEpoch(); + + // trigger Payout + etalonTick.year = 25; etalonTick.month = 11; etalonTick.day = 7; // Friday + etalonTick.hour = 12; etalonTick.minute = 1; etalonTick.second = 0; + qrwa.resetPayoutTime(); + + // snapshot balances for check + sint64 balanceBefore = getBalance(HOLDER_A); + + qrwa.endTick(); + + // Calculate Expected Payout + // Payout = (UserTotalShares * PoolAmount) / TotalSupply + // UserTotalShares = 1,000,000 (regardless of manager) + // PoolAmount = 450,000 + // TotalSupply = 1,000,000 + // Expected = 450,000 + sint64 expectedPayout = (totalShares * 450000) / totalShares; + + sint64 balanceAfter = getBalance(HOLDER_A); + + // If qRWA only counted QX shares, the payout would be (700k/1M * 450k) = 315,000. + // If qRWA counts ALL shares, the payout is 450,000. + EXPECT_EQ(balanceAfter - balanceBefore, expectedPayout); + EXPECT_EQ(balanceAfter - balanceBefore, 450000); +} diff --git a/test/contract_qswap.cpp b/test/contract_qswap.cpp new file mode 100644 index 000000000..3291262dc --- /dev/null +++ b/test/contract_qswap.cpp @@ -0,0 +1,810 @@ +#define NO_UEFI + +#include "contract_testing.h" + +//#define PRINT_DETAILS 0 + +static constexpr uint64 QSWAP_ISSUE_ASSET_FEE = 1000000000ull; +static constexpr uint64 QSWAP_TRANSFER_ASSET_FEE = 10000000ull; +static constexpr uint64 QSWAP_CREATE_POOL_FEE = 1000000000ull; + +static const id QSWAP_CONTRACT_ID(QSWAP_CONTRACT_INDEX, 0, 0, 0); + +//constexpr uint32 SWAP_FEE_IDX = 1; +constexpr uint32 GET_POOL_BASIC_STATE_IDX = 2; +constexpr uint32 GET_LIQUIDITY_OF_IDX = 3; +constexpr uint32 QUOTE_EXACT_QU_INPUT_IDX = 4; +constexpr uint32 QUOTE_EXACT_QU_OUTPUT_IDX = 5; +constexpr uint32 QUOTE_EXACT_ASSET_INPUT_IDX = 6; +constexpr uint32 QUOTE_EXACT_ASSET_OUTPUT_IDX = 7; +constexpr uint32 INVEST_REWARDS_INFO_IDX = 8; +// +constexpr uint32 ISSUE_ASSET_IDX = 1; +constexpr uint32 TRANSFER_SHARE_OWNERSHIP_AND_POSSESSION_IDX = 2; +constexpr uint32 CREATE_POOL_IDX = 3; +constexpr uint32 ADD_LIQUIDITY_IDX = 4; +constexpr uint32 REMOVE_LIQUIDITY_IDX = 5; +constexpr uint32 SWAP_EXACT_QU_FOR_ASSET_IDX = 6; +constexpr uint32 SWAP_QU_FOR_EXACT_ASSET_IDX = 7; +constexpr uint32 SWAP_EXACT_ASSET_FOR_QU_IDX = 8; +constexpr uint32 SWAP_ASSET_FOR_EXACT_QU_IDX = 9; +constexpr uint32 SET_INVEST_REWARDS_INFO_IDX = 10; +constexpr uint32 TRANSFER_SHARE_MANAGEMENT_RIGHTS_IDX = 11; + + +class QswapChecker : public QSWAP +{ +// public: +// void checkCollectionConsistency() { +// } +}; + + +class ContractTestingQswap : protected ContractTesting +{ +public: + ContractTestingQswap() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QSWAP); + callSystemProcedure(QSWAP_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + QswapChecker* getState() + { + return (QswapChecker*)contractStates[QSWAP_CONTRACT_INDEX]; + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(QSWAP_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + bool loadState(const CHAR16* filename) + { + return load(filename, sizeof(QSWAP), contractStates[QSWAP_CONTRACT_INDEX]) == sizeof(QSWAP); + } + + QSWAP::InvestRewardsInfo_output investRewardsInfo() + { + QSWAP::InvestRewardsInfo_input input{}; + QSWAP::InvestRewardsInfo_output output; + callFunction(QSWAP_CONTRACT_INDEX, INVEST_REWARDS_INFO_IDX, input, output); + return output; + } + + bool setInvestRewardsInfo(const id& issuer, QSWAP::SetInvestRewardsInfo_input input) + { + QSWAP::SetInvestRewardsInfo_output output; + invokeUserProcedure(QSWAP_CONTRACT_INDEX, SET_INVEST_REWARDS_INFO_IDX, input, output, issuer, 0); + return output.success; + } + + sint64 issueAsset(const id& issuer, QSWAP::IssueAsset_input input) + { + QSWAP::IssueAsset_output output; + invokeUserProcedure(QSWAP_CONTRACT_INDEX, ISSUE_ASSET_IDX, input, output, issuer, QSWAP_ISSUE_ASSET_FEE); + return output.issuedNumberOfShares; + } + + sint64 transferAsset(const id& issuer, QSWAP::TransferShareOwnershipAndPossession_input input) + { + QSWAP::TransferShareOwnershipAndPossession_output output; + invokeUserProcedure(QSWAP_CONTRACT_INDEX, TRANSFER_SHARE_OWNERSHIP_AND_POSSESSION_IDX, input, output, issuer, QSWAP_TRANSFER_ASSET_FEE); + return output.transferredAmount; + } + + bool createPool(const id& issuer, uint64 assetName) + { + QSWAP::CreatePool_input input{assetName}; + QSWAP::CreatePool_output output; + invokeUserProcedure(QSWAP_CONTRACT_INDEX, CREATE_POOL_IDX, input, output, issuer, QSWAP_CREATE_POOL_FEE); + return output.success; + } + + QSWAP::GetPoolBasicState_output getPoolBasicState(const id& issuer, uint64 assetName) + { + QSWAP::GetPoolBasicState_input input{issuer, assetName}; + QSWAP::GetPoolBasicState_output output; + + callFunction(QSWAP_CONTRACT_INDEX, GET_POOL_BASIC_STATE_IDX, input, output); + return output; + } + + QSWAP::AddLiquidity_output addLiquidity(const id& issuer, QSWAP::AddLiquidity_input input, uint64 inputValue) + { + QSWAP::AddLiquidity_output output; + invokeUserProcedure( + QSWAP_CONTRACT_INDEX, + ADD_LIQUIDITY_IDX, + input, + output, + issuer, + inputValue + ); + return output; + } + + QSWAP::RemoveLiquidity_output removeLiquidity(const id& issuer, QSWAP::RemoveLiquidity_input input, uint64 inputValue) + { + QSWAP::RemoveLiquidity_output output; + invokeUserProcedure( + QSWAP_CONTRACT_INDEX, + REMOVE_LIQUIDITY_IDX, + input, + output, + issuer, + inputValue + ); + return output; + } + + QSWAP::GetLiquidityOf_output getLiquidityOf(QSWAP::GetLiquidityOf_input input) + { + QSWAP::GetLiquidityOf_output output; + callFunction(QSWAP_CONTRACT_INDEX, GET_LIQUIDITY_OF_IDX, input, output); + return output; + } + + QSWAP::SwapExactQuForAsset_output swapExactQuForAsset( const id& issuer, QSWAP::SwapExactQuForAsset_input input, uint64 inputValue) + { + QSWAP::SwapExactQuForAsset_output output; + invokeUserProcedure( + QSWAP_CONTRACT_INDEX, + SWAP_EXACT_QU_FOR_ASSET_IDX, + input, + output, + issuer, + inputValue + ); + + return output; + } + + QSWAP::SwapQuForExactAsset_output swapQuForExactAsset( const id& issuer, QSWAP::SwapQuForExactAsset_input input, uint64 inputValue) + { + QSWAP::SwapQuForExactAsset_output output; + invokeUserProcedure( + QSWAP_CONTRACT_INDEX, + SWAP_QU_FOR_EXACT_ASSET_IDX, + input, + output, + issuer, + inputValue + ); + + return output; + } + + QSWAP::SwapExactAssetForQu_output swapExactAssetForQu(const id& issuer, QSWAP::SwapExactAssetForQu_input input, uint64 inputValue) + { + QSWAP::SwapExactAssetForQu_output output; + invokeUserProcedure( + QSWAP_CONTRACT_INDEX, + SWAP_EXACT_ASSET_FOR_QU_IDX, + input, + output, + issuer, + inputValue + ); + + return output; + } + + QSWAP::SwapAssetForExactQu_output swapAssetForExactQu(const id& issuer, QSWAP::SwapAssetForExactQu_input input, uint64 inputValue) + { + QSWAP::SwapAssetForExactQu_output output; + invokeUserProcedure( + QSWAP_CONTRACT_INDEX, + SWAP_ASSET_FOR_EXACT_QU_IDX, + input, + output, + issuer, + inputValue + ); + + return output; + } + + QSWAP::TransferShareManagementRights_output transferShareManagementRights(const id& invocator, QSWAP::TransferShareManagementRights_input input, uint64 inputValue) + { + QSWAP::TransferShareManagementRights_output output; + invokeUserProcedure(QSWAP_CONTRACT_INDEX, TRANSFER_SHARE_MANAGEMENT_RIGHTS_IDX, input, output, invocator, inputValue); + return output; + } + + QSWAP::QuoteExactQuInput_output quoteExactQuInput(QSWAP::QuoteExactQuInput_input input) + { + QSWAP::QuoteExactQuInput_output output; + callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_QU_INPUT_IDX, input, output); + return output; + } + + QSWAP::QuoteExactQuOutput_output quoteExactQuOutput(QSWAP::QuoteExactQuOutput_input input) + { + QSWAP::QuoteExactQuOutput_output output; + callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_QU_OUTPUT_IDX, input, output); + return output; + } + + QSWAP::QuoteExactAssetInput_output quoteExactAssetInput(QSWAP::QuoteExactAssetInput_input input) + { + QSWAP::QuoteExactAssetInput_output output; + callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_ASSET_INPUT_IDX, input, output); + return output; + } + + QSWAP::QuoteExactAssetOutput_output quoteExactAssetOutput(QSWAP::QuoteExactAssetOutput_input input) + { + QSWAP::QuoteExactAssetOutput_output output; + callFunction(QSWAP_CONTRACT_INDEX, QUOTE_EXACT_ASSET_OUTPUT_IDX, input, output); + return output; + } +}; + +TEST(ContractSwap, InvestRewardsInfoTest) +{ + ContractTestingQswap qswap; + + { + QSWAP::InvestRewardsInfo_output info = qswap.investRewardsInfo(); + + auto expectIdentity = (const unsigned char*)"VJGRUFWJCUSNHCQJRWRRYXAUEJFCVHYPXWKTDLYKUACPVVYBGOLVCJSF"; + m256i expectPubkey; + getPublicKeyFromIdentity(expectIdentity, expectPubkey.m256i_u8); + EXPECT_EQ(info.investRewardsId, expectPubkey); + EXPECT_EQ(info.investRewardsFee, 3); + } + + { + id newInvestRewardsId(6,6,6,6); + QSWAP::SetInvestRewardsInfo_input input = {newInvestRewardsId}; + + id invalidIssuer(1,2,3,4); + + increaseEnergy(invalidIssuer, 100); + bool res1 = qswap.setInvestRewardsInfo(invalidIssuer, input); + // printf("res1: %d\n", res1); + EXPECT_FALSE(res1); + + auto investRewardsIdentity = (const unsigned char*)"VJGRUFWJCUSNHCQJRWRRYXAUEJFCVHYPXWKTDLYKUACPVVYBGOLVCJSF"; + m256i investRewardsPubkey; + getPublicKeyFromIdentity(investRewardsIdentity, investRewardsPubkey.m256i_u8); + + increaseEnergy(investRewardsPubkey, 100); + bool res2 = qswap.setInvestRewardsInfo(investRewardsPubkey, input); + // printf("res2: %d\n", res2); + EXPECT_TRUE(res2); + + QSWAP::InvestRewardsInfo_output info = qswap.investRewardsInfo(); + EXPECT_EQ(info.investRewardsId, newInvestRewardsId); + // printf("%d\n", info.investRewardsId == newInvestRewardsId); + } +} + +TEST(ContractSwap, QuoteTest) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 10000 * 1000; + + // issue an asset and create a pool, and init liquidity + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + + sint64 inputValue = 30*1000; + increaseEnergy(issuer, inputValue); + QSWAP::AddLiquidity_input alInput = { issuer, assetName, 30*1000, 0, 0 }; + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); + + QSWAP::QuoteExactQuInput_input qi_input = {issuer, assetName, 1000}; + QSWAP::QuoteExactQuInput_output qi_output = qswap.quoteExactQuInput(qi_input); + // printf("quote exact qu input: %lld\n", qi_output.assetAmountOut); + EXPECT_EQ(qi_output.assetAmountOut, 964); + + QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, 1000}; + QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input); + // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn); + EXPECT_EQ(qo_output.assetAmountIn, 1038); + + QSWAP::QuoteExactAssetInput_input ai_input = {issuer, assetName, 1000}; + QSWAP::QuoteExactAssetInput_output ai_output = qswap.quoteExactAssetInput(ai_input); + // printf("quote exact asset input: %lld\n", ai_output.quAmountOut); + EXPECT_EQ(ai_output.quAmountOut, 964); + + QSWAP::QuoteExactAssetOutput_input ao_input = {issuer, assetName, 1000}; + QSWAP::QuoteExactAssetOutput_output ao_output = qswap.quoteExactAssetOutput(ao_input); + // printf("quote exact asset output: %lld\n", ao_output.quAmountIn); + EXPECT_EQ(ao_output.quAmountIn, 1038); + } +} + +/* +0. normally issue asset +1. not enough qu for asset issue fee +2. issue duplicate asset +3. issue asset with invalid input params, such as numberOfShares: 0 +*/ +TEST(ContractSwap, IssueAssetAndTransferShareManagementRights) +{ + ContractTestingQswap qswap; + qswap.beginEpoch(); + + id issuer(1, 2, 3, 4); + + // 0. normally issue asset and transfer + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 1000000; + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), 0); + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), QSWAP_ISSUE_ASSET_FEE); + + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + sint64 transferAmount = 1000; + id newId(2,3,4,5); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, newId, newId, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), 0); + QSWAP::TransferShareOwnershipAndPossession_input ts_input = {issuer, assetName, newId, transferAmount}; + // printf("ts amount: %lld\n", transferAmount); + EXPECT_EQ(qswap.transferAsset(issuer, ts_input), transferAmount); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, newId, newId, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), transferAmount); + // printf("%lld\n", getBalance(QSWAP_CONTRACT_ID)); + increaseEnergy(issuer, 100); + uint64 qswapIdBalance = getBalance(QSWAP_CONTRACT_ID); + uint64 issuerBalance = getBalance(issuer); + QSWAP::TransferShareManagementRights_input tsr_input = {Asset{issuer, assetName}, transferAmount, QX_CONTRACT_INDEX}; + EXPECT_EQ(qswap.transferShareManagementRights(issuer, tsr_input, 100).transferredNumberOfShares, transferAmount); + EXPECT_EQ(getBalance(id(QX_CONTRACT_INDEX, 0, 0, 0)), 100); + EXPECT_EQ(getBalance(QSWAP_CONTRACT_ID), qswapIdBalance); + EXPECT_EQ(getBalance(issuer), issuerBalance - 100); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), transferAmount); + } + + // 1. not enough energy for asset issue fee + { + decreaseEnergy(spectrumIndex(issuer), getBalance(issuer)); + uint64 assetName = assetNameFromString("QSWAP1"); + sint64 numberOfShares = 1000000; + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), 0); + } + + // 2. issue duplicate asset, related to test.0 + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 1000000; + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), 0); + } + + // 3. issue asset with invalid input params, such as numberOfShares: 0 + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + uint64 assetName = assetNameFromString("QSWAP1"); + sint64 numberOfShares = 0; + QSWAP::IssueAsset_input input = {assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), 0); + } +} + +TEST(ContractSwap, SwapExactQuForAsset) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 10000 * 1000; + + // issue an asset and create a pool, and init liquidity + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + + sint64 inputValue = 200*1000; + increaseEnergy(issuer, inputValue); + QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); + // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); + } + + { + // swap in 100*1000 qu, get about 1000*50 asset + id user(2,3,4,5); + sint64 inputValue = 200*1000; + increaseEnergy(user, inputValue); + + QSWAP::QuoteExactQuInput_input qi_input = {issuer, assetName, inputValue}; + QSWAP::QuoteExactQuInput_output qi_output = qswap.quoteExactQuInput(qi_input); + // printf("quote_exact_qu_input, asset out: %lld\n", qi_output.assetAmountOut); + + QSWAP::SwapExactQuForAsset_input input = {issuer, assetName, 0}; + QSWAP::SwapExactQuForAsset_output output = qswap.swapExactQuForAsset(user, input, inputValue); + // printf("swap_exact_qu_for_asset, asset out: %lld\n", output.assetAmountOut); + + EXPECT_EQ(qi_output.assetAmountOut, output.assetAmountOut); // 49924 + + EXPECT_TRUE(output.assetAmountOut <= 50000); // 49924 if swapFee 0.3% + + QSWAP::GetPoolBasicState_output psOutput = qswap.getPoolBasicState(issuer, assetName); + // printf("%lld, %lld, %lld\n", psOutput.reservedAssetAmount, psOutput.reservedQuAmount, psOutput.totalLiquidity); + // swapFee is 200_000 * 0.3% = 600, shareholders 27%: 162, QX 5%: 30, invest&rewards 3%: 18, burn 1%: 6 = 216 + EXPECT_TRUE(psOutput.reservedQuAmount >= 399784); // 399784 = (400_000 - 216) + EXPECT_TRUE(psOutput.reservedAssetAmount >= 50000 ); // 50076 + EXPECT_EQ(psOutput.totalLiquidity, 141421); // liquidity stay the same + } +} + +TEST(ContractSwap, SwapQuForExactAsset) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 10000 * 1000; + + // issue an asset and create a pool, and init liquidity + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + + sint64 inputValue = 200*1000; + increaseEnergy(issuer, inputValue); + QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); + // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); + } + + { + id user(2,3,4,5); + sint64 inputValue = 1000 * 200; + sint64 expectQuAmountIn = 22289; + sint64 assetAmountOut = 10 * 1000; + increaseEnergy(user, inputValue); + + QSWAP::QuoteExactAssetOutput_input ao_input = {issuer, assetName, assetAmountOut}; + QSWAP::QuoteExactAssetOutput_output ao_output = qswap.quoteExactAssetOutput(ao_input); + // printf("quote_exact_asset_output, qu in %lld\n", ao_output.quAmountIn); + + QSWAP::SwapQuForExactAsset_input input = {issuer, assetName, assetAmountOut}; + QSWAP::SwapQuForExactAsset_output output = qswap.swapQuForExactAsset(user, input, inputValue); + + EXPECT_EQ(ao_output.quAmountIn, output.quAmountIn); // 22289 + + // EXPECT_EQ(output.quAmountIn, 22289); + // printf("swap_qu_for_exact_asset, asset in: %lld\n", output.quAmountIn); + } +} + +TEST(ContractSwap, SwapExactAssetForQu) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 10000 * 1000; + + // issue an asset and create a pool, and init liquidity + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + + sint64 inputValue = 200*1000; + increaseEnergy(issuer, inputValue); + QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); + // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); + } + + { + id user(1, 2,3,4); + sint64 inputValue = 0; + sint64 assetAmountIn = 100*1000; + sint64 expectQuAmountOut = 99700; + increaseEnergy(user, inputValue); + + QSWAP::QuoteExactAssetInput_input ai_input = {issuer, assetName, assetAmountIn}; + QSWAP::QuoteExactAssetInput_output ai_output = qswap.quoteExactAssetInput(ai_input); + // printf("quote exact asset input: %lld\n", ai_output.quAmountOut); + + QSWAP::SwapExactAssetForQu_input input = {issuer, assetName, assetAmountIn, 0}; + QSWAP::SwapExactAssetForQu_output output = qswap.swapExactAssetForQu(user, input, inputValue); + // printf("swap qu out: %lld\n", output.quAmountOut); + EXPECT_EQ(ai_output.quAmountOut, output.quAmountOut); // 99700 + } +} + +TEST(ContractSwap, SwapAssetForExactQu) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 10000 * 1000; + + // issue an asset and create a pool, and init liquidity + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + + sint64 inputValue = 200*1000; + increaseEnergy(issuer, inputValue); + QSWAP::AddLiquidity_input alInput = { issuer, assetName, 100*1000, 0, 0 }; + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, alInput, inputValue); + // printf("increase liquidity: %lld, %lld, %lld\n", output.userIncreaseLiquidity, output.assetAmount, output.quAmount); + + // QSWAP::GetPoolBasicState_output gp_output = qswap.getPoolBasicState(issuer, assetName); + // printf("%lld, %lld, %lld\n", gp_output.reservedQuAmount, gp_output.reservedAssetAmount, gp_output.totalLiquidity); + } + + { + id user(1,2,3,4); + sint64 inputValue = 0; + sint64 quAmountOut = 200*1000 - 1; + + QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, quAmountOut}; + QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input); + // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn); + EXPECT_EQ(qo_output.assetAmountIn, -1); + } + + { + id user(1,2,3,4); + sint64 inputValue = 0; + sint64 quAmountOut = 100*1000; + sint64 expectAssetAmountIn = 100604; + + QSWAP::QuoteExactQuOutput_input qo_input = {issuer, assetName, quAmountOut}; + QSWAP::QuoteExactQuOutput_output qo_output = qswap.quoteExactQuOutput(qo_input); + // printf("quote exact qu output: %lld\n", qo_output.assetAmountIn); + EXPECT_EQ(qo_output.assetAmountIn, expectAssetAmountIn); + + increaseEnergy(user, inputValue); + sint64 assetAmountInMax = 200*1000; + QSWAP::SwapAssetForExactQu_input input = {issuer, assetName, assetAmountInMax, quAmountOut}; + QSWAP::SwapAssetForExactQu_output output = qswap.swapAssetForExactQu(user, input, inputValue); + // printf("swap asset in: %lld\n", output.assetAmountIn); + EXPECT_EQ(qo_output.assetAmountIn, output.assetAmountIn); + } +} + +/* +0. check pool state before create +1. normal create pool, check pool existance, pool states +2. create duplicate pool +3. create pool with invalid asset +*/ +TEST(ContractSwap, CreatePool) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + sint64 numberOfShares = 1000000; + + // issue asset first + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = {assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + } + + // 0. check not exsit pool state before create + { + QSWAP::GetPoolBasicState_output output = qswap.getPoolBasicState(issuer, assetName); + EXPECT_FALSE(output.poolExists); + } + + // 1. normal create pool, check pool existance, pool states + { + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + + // initial pool state + QSWAP::GetPoolBasicState_output output = qswap.getPoolBasicState(issuer, assetName); + EXPECT_EQ(output.poolExists, true); + EXPECT_EQ(output.reservedQuAmount, 0); + EXPECT_EQ(output.reservedAssetAmount, 0); + EXPECT_EQ(output.totalLiquidity, 0); + } + + // 2. create duplicate pool + { + EXPECT_FALSE(qswap.createPool(issuer, assetName)); + } + + // 3. ceate pool with not issued asset + { + uint64 assetName2 = assetNameFromString("QswapX"); + EXPECT_FALSE(qswap.createPool(issuer, assetName2)); + } +} + +/* +add liquidity 2 times, and then remove +*/ +TEST(ContractSwap, LiqTest1) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + uint64 invalidAssetName = assetNameFromString("QSWAP1"); + sint64 numberOfShares = 1000*1000; + + // 0. issue an asset and create a pool + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + } + + // 1. add liquidity to a initial pool, first time + { + sint64 quStakeAmount = 200*1000; + sint64 inputValue = quStakeAmount; + sint64 assetStakeAmount = 100*1000; + increaseEnergy(issuer, quStakeAmount); + QSWAP::AddLiquidity_input addLiqInput = { + issuer, + assetName, + assetStakeAmount, + 0, + 0 + }; + + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, addLiqInput, inputValue); + // actually, 141421 liquidity add to the pool, but the first 1000 liquidity is retainedd by the pool rather than the staker + EXPECT_EQ(output.userIncreaseLiquidity, 140421); + EXPECT_EQ(output.quAmount, 200*1000); + EXPECT_EQ(output.assetAmount, 100*1000); + + QSWAP::GetPoolBasicState_output output2 = qswap.getPoolBasicState(issuer, assetName); + EXPECT_EQ(output2.poolExists, true); + EXPECT_EQ(output2.reservedQuAmount, 200*1000); + EXPECT_EQ(output2.reservedAssetAmount, 100*1000); + EXPECT_EQ(output2.totalLiquidity, 141421); + // printf("pool state: %lld, %lld, %lld\n", output2.reservedQuAmount, output2.reservedAssetAmount, output2.totalLiquidity); + + QSWAP::GetLiquidityOf_input getLiqInput = { + issuer, + assetName, + issuer + }; + QSWAP::GetLiquidityOf_output getLiqOutput = qswap.getLiquidityOf(getLiqInput); + EXPECT_EQ(getLiqOutput.liquidity, 140421); + + // 2. add liquidity second time + increaseEnergy(issuer, quStakeAmount); + addLiqInput = { + issuer, + assetName, + assetStakeAmount, + 0, + 0 + }; + + QSWAP::AddLiquidity_output output3 = qswap.addLiquidity(issuer, addLiqInput, inputValue); + EXPECT_EQ(output3.userIncreaseLiquidity, 141421); + EXPECT_EQ(output3.quAmount, 200*1000); + EXPECT_EQ(output3.assetAmount, 100*1000); + + getLiqOutput = qswap.getLiquidityOf(getLiqInput); + EXPECT_EQ(getLiqOutput.liquidity, 281842); // 140421 + 141421 + + QSWAP::RemoveLiquidity_input rmLiqInput = { + issuer, + assetName, + 141421, + 200*1000, // should lte 1000*200 + 100*1000, // should lte 1000*100 + }; + + // 3. remove liquidity + QSWAP::RemoveLiquidity_output rmLiqOutput = qswap.removeLiquidity(issuer, rmLiqInput, 0); + // printf("qu: %lld, asset: %lld\n", rmLiqOutput.quAmount, rmLiqOutput.assetAmount); + EXPECT_EQ(rmLiqOutput.quAmount, 1000 * 200); + EXPECT_EQ(rmLiqOutput.assetAmount, 1000 * 100); + + getLiqOutput = qswap.getLiquidityOf(getLiqInput); + // printf("liq: %lld\n", getLiqOutput.liquidity); + EXPECT_EQ(getLiqOutput.liquidity, 140421); // 281842 - 141421 + } +} + +// failed case +TEST(ContractSwap, LiqTest2) +{ + ContractTestingQswap qswap; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QSWAP0"); + uint64 invalidAssetName = assetNameFromString("QSWAP1"); + sint64 numberOfShares = 1000*1000; + + // 0. issue an asset and create a pool + { + increaseEnergy(issuer, QSWAP_ISSUE_ASSET_FEE); + QSWAP::IssueAsset_input input = { assetName, numberOfShares, 0, 0 }; + EXPECT_EQ(qswap.issueAsset(issuer, input), numberOfShares); + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QSWAP_CONTRACT_INDEX, QSWAP_CONTRACT_INDEX), numberOfShares); + + increaseEnergy(issuer, QSWAP_CREATE_POOL_FEE); + EXPECT_TRUE(qswap.createPool(issuer, assetName)); + } + + // add liquidity to invalid pool, + { + // decreaseEnergy(getBalance(issuer)); + uint64 quAmount = 1000; + increaseEnergy(issuer, quAmount); + QSWAP::AddLiquidity_input addLiqInput = { + issuer, + invalidAssetName, + 1000, + 0, + 0 + }; + + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, addLiqInput, 1000); + EXPECT_EQ(output.userIncreaseLiquidity, 0); + EXPECT_EQ(output.quAmount, 0); + EXPECT_EQ(output.assetAmount, 0); + } + + // add liquidity with asset more than holdings + { + increaseEnergy(issuer, 1000); + QSWAP::AddLiquidity_input addLiqInput = { + issuer, + assetName, + 1000*1000 + 100, // excced 1000*1000 + 0, + 0 + }; + + QSWAP::AddLiquidity_output output = qswap.addLiquidity(issuer, addLiqInput, 1000); + EXPECT_EQ(output.userIncreaseLiquidity, 0); + EXPECT_EQ(output.quAmount, 0); + EXPECT_EQ(output.assetAmount, 0); + } +} diff --git a/test/contract_qtf.cpp b/test/contract_qtf.cpp new file mode 100644 index 000000000..c6172a8b1 --- /dev/null +++ b/test/contract_qtf.cpp @@ -0,0 +1,3338 @@ +#define NO_UEFI + +#include "contract_testing.h" +#include +#include +#include + +// Procedure/function indices (must match REGISTER_USER_FUNCTIONS_AND_PROCEDURES in `src/contracts/QThirtyFour.h`). +constexpr uint16 QTF_PROCEDURE_BUY_TICKET = 1; +constexpr uint16 QTF_PROCEDURE_SET_PRICE = 2; +constexpr uint16 QTF_PROCEDURE_SET_SCHEDULE = 3; +constexpr uint16 QTF_PROCEDURE_SET_TARGET_JACKPOT = 4; +constexpr uint16 QTF_PROCEDURE_SET_DRAW_HOUR = 5; +constexpr uint16 QTF_PROCEDURE_SYNC_JACKPOT = 6; + +constexpr uint16 QTF_FUNCTION_GET_TICKET_PRICE = 1; +constexpr uint16 QTF_FUNCTION_GET_NEXT_EPOCH_DATA = 2; +constexpr uint16 QTF_FUNCTION_GET_WINNER_DATA = 3; +constexpr uint16 QTF_FUNCTION_GET_POOLS = 4; +constexpr uint16 QTF_FUNCTION_GET_SCHEDULE = 5; +constexpr uint16 QTF_FUNCTION_GET_DRAW_HOUR = 6; +constexpr uint16 QTF_FUNCTION_GET_STATE = 7; +constexpr uint16 QTF_FUNCTION_GET_FEES = 8; +constexpr uint16 QTF_FUNCTION_ESTIMATE_PRIZE_PAYOUTS = 9; +constexpr uint16 QTF_FUNCTION_GET_PLAYERS = 10; +constexpr uint16 QTF_FUNCTION_GET_WINNING_COMBINATIONS_HISTORY = 11; + +using QTFRandomValues = Array; + +namespace +{ + static void issueRlSharesTo(std::vector>& initialOwnerShares) + { + issueContractShares(RL_CONTRACT_INDEX, initialOwnerShares, false); + } + + static void primeQpiFunctionContext(QpiContextUserFunctionCall& qpi) + { + QTF::GetTicketPrice_input input{}; + qpi.call(QTF_FUNCTION_GET_TICKET_PRICE, &input, sizeof(input)); + } + + static void primeQpiProcedureContext(QpiContextUserProcedureCall& qpi, uint8 drawHour) + { + QTF::SetDrawHour_input input{}; + input.newDrawHour = drawHour; + qpi.call(QTF_PROCEDURE_SET_DRAW_HOUR, &input, sizeof(input)); + ASSERT_EQ(contractError[QTF_CONTRACT_INDEX], 0); + } + + static bool valuesEqual(const QTFRandomValues& a, const QTFRandomValues& b) + { + return memcmp(&a, &b, sizeof(a)) == 0; + } + + static bool isEmptyRandomValues(const QTFRandomValues& randomValues) + { + for (uint64 i = 0; i < randomValues.capacity(); ++i) + { + if (randomValues.get(i) != 0) + { + return false; + } + } + + return true; + } + + static void expectWinnerValuesValidAndUnique(const QTF::GetWinnerData_output& winnerData) + { + std::set unique; + for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) + { + const uint8 v = winnerData.winnerData.winnerValues.get(i); + EXPECT_GE(v, 1u) << "Winning value " << i << " should be >= 1"; + EXPECT_LE(v, QTF_MAX_RANDOM_VALUE) << "Winning value " << i << " should be <= 30"; + unique.insert(v); + } + EXPECT_EQ(unique.size(), static_cast(QTF_RANDOM_VALUES_COUNT)) << "All 4 winning numbers should be unique"; + EXPECT_GT(static_cast(winnerData.winnerData.epoch), 0u) << "Epoch should be recorded after draw"; + } + + static void computeBaselinePrizePools(uint64 revenue, const QTF::GetFees_output& fees, uint64& winnersBlock, uint64& k2Pool, uint64& k3Pool) + { + winnersBlock = (revenue * static_cast(fees.winnerFeePercent)) / 100ULL; + k2Pool = (winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000ULL; + k3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000ULL; + } +} // namespace + +constexpr uint8 QTF_ANY_DAY_SCHEDULE = 0xFF; + +// Test helper class exposing internal state +class QTFChecker : public QTF +{ +public: + uint64 getNumberOfPlayers() const { return numberOfPlayers; } + uint64 getTicketPriceInternal() const { return ticketPrice; } + uint64 getJackpot() const { return jackpot; } + uint64 getTargetJackpotInternal() const { return targetJackpot; } + uint32 getDrawHourInternal() const { return drawHour; } + bool getFrActive() const { return frActive; } + uint32 getFrRoundsSinceK4() const { return frRoundsSinceK4; } + uint32 getFrRoundsAtOrAboveTarget() const { return frRoundsAtOrAboveTarget; } + uint64 getWinningCombinationsWriteIndex() const { return winningCombinationsCount; } + const id& team() const { return teamAddress; } + + void setScheduleMask(uint8 newMask) { schedule = newMask; } + void setJackpot(uint64 value) { jackpot = value; } + void setTargetJackpotInternal(uint64 value) { targetJackpot = value; } + void setTicketPriceInternal(uint64 value) { ticketPrice = value; } + void setFrActive(bit value) { frActive = value; } + void setFrRoundsSinceK4(uint16 value) { frRoundsSinceK4 = value; } + void setFrRoundsAtOrAboveTarget(uint16 value) { frRoundsAtOrAboveTarget = value; } + void setOverflowAlphaBP(uint64 value) { overflowAlphaBP = value; } + + const PlayerData& getPlayer(uint64 index) const { return players.get(index); } + void addPlayerDirect(const id& playerId, const QTFRandomValues& randomValues) { players.set(numberOfPlayers++, {playerId, randomValues}); } + + // ---- Private method wrappers (private->protected in this TU) -------------- + ValidateNumbers_output callValidateNumbers(const QPI::QpiContextFunctionCall& qpi, const QTFRandomValues& numbers) const + { + ValidateNumbers_input input{}; + ValidateNumbers_output output{}; + ValidateNumbers_locals locals{}; + + input.numbers = numbers; + ValidateNumbers(qpi, *this, input, output, locals); + return output; + } + + GetRandomValues_output callGetRandomValues(const QPI::QpiContextFunctionCall& qpi, uint64 seed) const + { + GetRandomValues_input input{}; + GetRandomValues_output output{}; + GetRandomValues_locals locals{}; + + input.seed = seed; + GetRandomValues(qpi, *this, input, output, locals); + return output; + } + + CountMatches_output callCountMatches(const QPI::QpiContextFunctionCall& qpi, const QTFRandomValues& playerValues, + const QTFRandomValues& winningValues) const + { + CountMatches_input input{}; + CountMatches_output output{}; + CountMatches_locals locals{}; + + input.playerValues = playerValues; + input.winningValues = winningValues; + CountMatches(qpi, *this, input, output, locals); + return output; + } + + CheckContractBalance_output callCheckContractBalance(const QPI::QpiContextFunctionCall& qpi, uint64 expectedRevenue) const + { + CheckContractBalance_input input{}; + CheckContractBalance_output output{}; + CheckContractBalance_locals locals{}; + + input.expectedRevenue = expectedRevenue; + CheckContractBalance(qpi, *this, input, output, locals); + return output; + } + + PowerFixedPoint_output callPowerFixedPoint(const QPI::QpiContextFunctionCall& qpi, uint64 base, uint64 exp) const + { + PowerFixedPoint_input input{}; + PowerFixedPoint_output output{}; + PowerFixedPoint_locals locals{}; + + input.base = base; + input.exp = exp; + PowerFixedPoint(qpi, *this, input, output, locals); + return output; + } + + CalculateExpectedRoundsToK4_output callCalculateExpectedRoundsToK4(const QPI::QpiContextFunctionCall& qpi, uint64 N) const + { + CalculateExpectedRoundsToK4_input input{}; + CalculateExpectedRoundsToK4_output output{}; + CalculateExpectedRoundsToK4_locals locals{}; + + input.N = N; + CalculateExpectedRoundsToK4(qpi, *this, input, output, locals); + return output; + } + + CalcReserveTopUp_output callCalcReserveTopUp(const QPI::QpiContextFunctionCall& qpi, uint64 totalQRPBalance, uint64 needed, + uint64 perWinnerCapTotal, uint64 ticketPrice) const + { + CalcReserveTopUp_input input{}; + CalcReserveTopUp_output output{}; + CalcReserveTopUp_locals locals{}; + + input.totalQRPBalance = totalQRPBalance; + input.needed = needed; + input.perWinnerCapTotal = perWinnerCapTotal; + input.ticketPrice = ticketPrice; + CalcReserveTopUp(qpi, *this, input, output, locals); + return output; + } + + CalculatePrizePools_output callCalculatePrizePools(const QPI::QpiContextFunctionCall& qpi, uint64 revenue, bit applyFRRake) const + { + CalculatePrizePools_input input{}; + CalculatePrizePools_output output{}; + CalculatePrizePools_locals locals{}; + + input.revenue = revenue; + input.applyFRRake = applyFRRake; + CalculatePrizePools(qpi, *this, input, output, locals); + return output; + } + + CalculateBaseGain_output callCalculateBaseGain(const QPI::QpiContextFunctionCall& qpi, uint64 revenue, uint64 winnersBlock) const + { + CalculateBaseGain_input input{}; + CalculateBaseGain_output output{}; + CalculateBaseGain_locals locals{}; + + input.revenue = revenue; + input.winnersBlock = winnersBlock; + CalculateBaseGain(qpi, *this, input, output, locals); + return output; + } + + CalculateExtraRedirectBP_output callCalculateExtraRedirectBP(const QPI::QpiContextFunctionCall& qpi, uint64 N, uint64 delta, uint64 revenue, + uint64 baseGain) const + { + CalculateExtraRedirectBP_input input{}; + CalculateExtraRedirectBP_output output{}; + CalculateExtraRedirectBP_locals locals{}; + + input.N = N; + input.delta = delta; + input.revenue = revenue; + input.baseGain = baseGain; + CalculateExtraRedirectBP(qpi, *this, input, output, locals); + return output; + } + + void callReturnAllTickets(const QPI::QpiContextProcedureCall& qpi) + { + ReturnAllTickets_input input{}; + ReturnAllTickets_output output{}; + ReturnAllTickets_locals locals{}; + + ReturnAllTickets(qpi, *this, input, output, locals); + } + + ProcessTierPayout_output callProcessTierPayout(const QPI::QpiContextProcedureCall& qpi, uint64 floorPerWinner, uint64 winnerCount, + uint64 payoutPool, uint64 perWinnerCap, uint64 totalQRPBalance, uint64 ticketPrice) + { + ProcessTierPayout_input input{}; + ProcessTierPayout_output output{}; + ProcessTierPayout_locals locals{}; + + input.floorPerWinner = floorPerWinner; + input.winnerCount = winnerCount; + input.payoutPool = payoutPool; + input.perWinnerCap = perWinnerCap; + input.totalQRPBalance = totalQRPBalance; + input.ticketPrice = ticketPrice; + ProcessTierPayout(qpi, *this, input, output, locals); + return output; + } +}; + +class ContractTestingQTF : protected ContractTesting +{ +public: + ContractTestingQTF() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QRP); + INIT_CONTRACT(RL); + INIT_CONTRACT(QTF); + + // Initialize QRP first (QTF depends on it for reserve operations) + callSystemProcedure(QRP_CONTRACT_INDEX, INITIALIZE); + // Initialize RL (RandomLottery contract) + callSystemProcedure(RL_CONTRACT_INDEX, INITIALIZE); + // Initialize QTF + system.epoch = contractDescriptions[QTF_CONTRACT_INDEX].constructionEpoch; + callSystemProcedure(QTF_CONTRACT_INDEX, INITIALIZE); + } + + // Access internal contract state + QTFChecker* state() { return reinterpret_cast(contractStates[QTF_CONTRACT_INDEX]); } + + id qtfSelf() { return id(QTF_CONTRACT_INDEX, 0, 0, 0); } + id qrpSelf() { return id(QRP_CONTRACT_INDEX, 0, 0, 0); } + void addPlayerDirect(const id& playerId, const QTFRandomValues& randomValues) { state()->addPlayerDirect(playerId, randomValues); } + + // Public function wrappers + QTF::GetTicketPrice_output getTicketPrice() + { + QTF::GetTicketPrice_input input{}; + QTF::GetTicketPrice_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_TICKET_PRICE, input, output); + return output; + } + + QTF::GetNextEpochData_output getNextEpochData() + { + QTF::GetNextEpochData_input input{}; + QTF::GetNextEpochData_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_NEXT_EPOCH_DATA, input, output); + return output; + } + + QTF::GetWinnerData_output getWinnerData() + { + QTF::GetWinnerData_input input{}; + QTF::GetWinnerData_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_WINNER_DATA, input, output); + return output; + } + + QTF::GetPools_output getPools() + { + QTF::GetPools_input input{}; + QTF::GetPools_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_POOLS, input, output); + return output; + } + + QTF::GetSchedule_output getSchedule() + { + QTF::GetSchedule_input input{}; + QTF::GetSchedule_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_SCHEDULE, input, output); + return output; + } + + QTF::GetDrawHour_output getDrawHour() + { + QTF::GetDrawHour_input input{}; + QTF::GetDrawHour_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_DRAW_HOUR, input, output); + return output; + } + + QTF::GetState_output getStateInfo() + { + QTF::GetState_input input{}; + QTF::GetState_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_STATE, input, output); + return output; + } + + QTF::GetFees_output getFees() + { + QTF::GetFees_input input{}; + QTF::GetFees_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_FEES, input, output); + return output; + } + + QTF::EstimatePrizePayouts_output estimatePrizePayouts(uint64 k2WinnerCount, uint64 k3WinnerCount) + { + QTF::EstimatePrizePayouts_input input{}; + input.k2WinnerCount = k2WinnerCount; + input.k3WinnerCount = k3WinnerCount; + QTF::EstimatePrizePayouts_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_ESTIMATE_PRIZE_PAYOUTS, input, output); + return output; + } + + QTF::GetPlayers_output getPlayers() + { + QTF::GetPlayers_input input{}; + QTF::GetPlayers_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_PLAYERS, input, output); + return output; + } + + QTF::GetWinningCombinationsHistory_output getWinningCombinationsHistory() + { + QTF::GetWinningCombinationsHistory_input input{}; + QTF::GetWinningCombinationsHistory_output output{}; + callFunction(QTF_CONTRACT_INDEX, QTF_FUNCTION_GET_WINNING_COMBINATIONS_HISTORY, input, output); + return output; + } + + // Procedure wrappers + QTF::BuyTicket_output buyTicket(const id& user, uint64 reward, const QTFRandomValues& numbers) + { + QTF::BuyTicket_input input{}; + input.randomValues = numbers; + QTF::BuyTicket_output output{}; + if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_BUY_TICKET, input, output, user, reward)) + { + output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); + } + return output; + } + + QTF::SetPrice_output setPrice(const id& invocator, uint64 newPrice) + { + QTF::SetPrice_input input{}; + input.newPrice = newPrice; + QTF::SetPrice_output output{}; + if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_PRICE, input, output, invocator, 0)) + { + output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); + } + return output; + } + + QTF::SetSchedule_output setSchedule(const id& invocator, uint8 newSchedule) + { + QTF::SetSchedule_input input{}; + input.newSchedule = newSchedule; + QTF::SetSchedule_output output{}; + if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_SCHEDULE, input, output, invocator, 0)) + { + output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); + } + return output; + } + + QTF::SetTargetJackpot_output setTargetJackpot(const id& invocator, uint64 newTarget) + { + QTF::SetTargetJackpot_input input{}; + input.newTargetJackpot = newTarget; + QTF::SetTargetJackpot_output output{}; + if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_TARGET_JACKPOT, input, output, invocator, 0)) + { + output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); + } + return output; + } + + QTF::SetDrawHour_output setDrawHour(const id& invocator, uint8 newHour) + { + QTF::SetDrawHour_input input{}; + input.newDrawHour = newHour; + QTF::SetDrawHour_output output{}; + if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SET_DRAW_HOUR, input, output, invocator, 0)) + { + output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); + } + return output; + } + + QTF::SyncJackpot_output syncJackpot(const id& invocator) + { + QTF::SyncJackpot_input input{}; + QTF::SyncJackpot_output output{}; + if (!invokeUserProcedure(QTF_CONTRACT_INDEX, QTF_PROCEDURE_SYNC_JACKPOT, input, output, invocator, 0)) + { + output.returnCode = static_cast(QTF::EReturnCode::MAX_VALUE); + } + return output; + } + + // System procedure wrappers + void beginEpoch() { callSystemProcedure(QTF_CONTRACT_INDEX, BEGIN_EPOCH); } + void endEpoch() { callSystemProcedure(QTF_CONTRACT_INDEX, END_EPOCH); } + void beginTick() { callSystemProcedure(QTF_CONTRACT_INDEX, BEGIN_TICK); } + + void setDateTime(uint16 year, uint8 month, uint8 day, uint8 hour) + { + updateTime(); + utcTime.Year = year; + utcTime.Month = month; + utcTime.Day = day; + utcTime.Hour = hour; + utcTime.Minute = 0; + utcTime.Second = 0; + utcTime.Nanosecond = 0; + updateQpiTime(); + } + + void forceBeginTick() + { + system.tick = system.tick + (RL_TICK_UPDATE_PERIOD - (system.tick % RL_TICK_UPDATE_PERIOD)); + beginTick(); + } + + void beginEpochWithDate(uint16 year, uint8 month, uint8 day, uint8 hour = 12) + { + setDateTime(year, month, day, hour); + beginEpoch(); + } + + void beginEpochWithValidTime() { beginEpochWithDate(2025, 1, 20); } + + // Force schedule mask directly in state + void forceSchedule(uint8 scheduleMask) { state()->setScheduleMask(scheduleMask); } + + void forceFRDisabledForBaseline() + { + state()->setFrActive(false); + state()->setFrRoundsSinceK4(QTF_FR_POST_K4_WINDOW_ROUNDS); + state()->setOverflowAlphaBP(QTF_BASELINE_OVERFLOW_ALPHA_BP); + } + + void forceFREnabledWithinWindow(uint16 roundsSinceK4 = 1) + { + state()->setFrActive(true); + state()->setFrRoundsSinceK4(roundsSinceK4); + } + + void startAnyDayEpoch() + { + forceSchedule(QTF_ANY_DAY_SCHEDULE); + beginEpochWithValidTime(); + } + + // Trigger a tick that performs the draw (time is set to a scheduled day and hour). + void triggerDrawTick() + { + constexpr uint16 y = 2025; + constexpr uint8 m = 1; + constexpr uint8 d = 10; + setDateTime(y, m, d, 12); + __pauseLogMessage(); + forceBeginTick(); + } + + // Helper to create valid random values + QTFRandomValues makeValidNumbers(uint8 n1, uint8 n2, uint8 n3, uint8 n4) + { + QTFRandomValues values; + values.set(0, n1); + values.set(1, n2); + values.set(2, n3); + values.set(3, n4); + return values; + } + + // Fund user and buy a ticket + void fundAndBuyTicket(const id& user, uint64 ticketPrice, const QTFRandomValues& numbers) + { + increaseEnergy(user, ticketPrice * 2); + const QTF::BuyTicket_output out = buyTicket(user, ticketPrice, numbers); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + } + + // Set prevSpectrumDigest for deterministic random number generation + // This allows tests to predict winning numbers by fixing the RNG seed + void setPrevSpectrumDigest(const m256i& digest) { etalonTick.prevSpectrumDigest = digest; } + + void drawWithDigest(const m256i& digest) + { + setPrevSpectrumDigest(digest); + triggerDrawTick(); + } + + // Compute the winning numbers that would be generated for a given prevSpectrumDigest. + // Uses the contract GetRandomValues() implementation (so tests don't duplicate RNG logic). + // Returns values in generation order (not sorted). + QTFRandomValues computeWinningNumbersForDigest(const m256i& digest) + { + m256i hashResult; + KangarooTwelve((const uint8*)&digest, sizeof(m256i), (uint8*)&hashResult, sizeof(m256i)); + const uint64 seed = hashResult.m256i_u64[0]; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + const auto out = state()->callGetRandomValues(qpi, seed); + return out.values; + } + + struct WinningAndLosing + { + QTFRandomValues winning; + QTFRandomValues losing; + }; + + QTFRandomValues makeLosingNumbers(const QTFRandomValues& winningNumbers) + { + bool isWinning[31] = {}; + for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) + { + isWinning[winningNumbers.get(i)] = true; + } + + QTFRandomValues losingNumbers; + uint64 outIndex = 0; + for (uint8 candidate = 1; candidate <= QTF_MAX_RANDOM_VALUE && outIndex < QTF_RANDOM_VALUES_COUNT; ++candidate) + { + if (!isWinning[candidate]) + { + losingNumbers.set(outIndex++, candidate); + } + } + EXPECT_EQ(outIndex, static_cast(QTF_RANDOM_VALUES_COUNT)); + return losingNumbers; + } + + WinningAndLosing computeWinningAndLosing(const m256i& digest) + { + WinningAndLosing out = {computeWinningNumbersForDigest(digest), makeLosingNumbers(out.winning)}; + + return out; + } + + void buyRandomTickets(uint64 count, uint64 ticketPrice, const QTFRandomValues& numbers) + { + for (uint64 i = 0; i < count; ++i) + { + const id user = id::randomValue(); + fundAndBuyTicket(user, ticketPrice, numbers); + } + } + + // Create a ticket that matches exactly `matchCount` numbers with `winningNumbers`. + // `variant` makes it deterministic to generate multiple distinct tickets for the same winning set. + // Guarantees values are unique and in [1..30]. + QTFRandomValues makeNumbersWithExactMatches(const QTFRandomValues& winningNumbers, uint8 matchCount, uint8 variant = 0) + { + EXPECT_LE(matchCount, static_cast(QTF_RANDOM_VALUES_COUNT)); + + bool isWinning[31] = {}; + bool used[31] = {}; + for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) + { + const uint8 v = winningNumbers.get(i); + EXPECT_GE(v, 1u); + EXPECT_LE(v, QTF_MAX_RANDOM_VALUE); + EXPECT_FALSE(isWinning[v]) << "winningNumbers must be unique"; + isWinning[v] = true; + } + + QTFRandomValues ticket{}; + uint64 outIndex = 0; + + // Take `matchCount` winning numbers as the matches (variant-dependent, wrap around 4). + for (uint8 i = 0; i < matchCount; ++i) + { + const uint8 v = winningNumbers.get((variant + i) % QTF_RANDOM_VALUES_COUNT); + used[v] = true; + ticket.set(outIndex++, v); + } + + // Fill the remaining positions with non-winning numbers. + const uint8 start = static_cast((variant * 7) % QTF_MAX_RANDOM_VALUE + 1); + for (uint8 step = 0; step < QTF_MAX_RANDOM_VALUE && outIndex < QTF_RANDOM_VALUES_COUNT; ++step) + { + const uint8 candidate = static_cast(((start - 1 + step) % QTF_MAX_RANDOM_VALUE) + 1); + if (!isWinning[candidate] && !used[candidate]) + { + used[candidate] = true; + ticket.set(outIndex++, candidate); + } + } + + EXPECT_EQ(outIndex, static_cast(QTF_RANDOM_VALUES_COUNT)); + + // Verify exact overlap count and uniqueness (debug safety for tests). + uint64 overlap = 0; + std::set uniqueValues; + for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) + { + const uint8 v = ticket.get(i); + EXPECT_GE(v, 1u); + EXPECT_LE(v, QTF_MAX_RANDOM_VALUE); + uniqueValues.insert(v); + if (isWinning[v]) + { + ++overlap; + } + } + EXPECT_EQ(uniqueValues.size(), static_cast(QTF_RANDOM_VALUES_COUNT)); + EXPECT_EQ(overlap, static_cast(matchCount)); + + return ticket; + } + + QTFRandomValues makeK2Numbers(const QTFRandomValues& winningNumbers, uint8 variant = 0) + { + return makeNumbersWithExactMatches(winningNumbers, 2, variant); + } + QTFRandomValues makeK3Numbers(const QTFRandomValues& winningNumbers, uint8 variant = 0) + { + return makeNumbersWithExactMatches(winningNumbers, 3, variant); + } +}; + +// ============================================================================ +// PRIVATE METHOD TESTS +// ============================================================================ + +TEST(ContractQThirtyFour_Private, CountMatches_CountsOverlappingNumbers) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + // Include values > 8 to cover the full [1..30] bitmask range. + const QTFRandomValues player = ctl.makeValidNumbers(1, 16, 29, 30); + const QTFRandomValues winning = ctl.makeValidNumbers(16, 29, 2, 3); + const auto out = ctl.state()->callCountMatches(qpi, player, winning); + EXPECT_EQ(out.matches, 2); +} + +TEST(ContractQThirtyFour_Private, ValidateNumbers_WorksForValidDuplicateAndRangeErrors) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const QTFRandomValues ok = ctl.makeValidNumbers(1, 2, 3, 4); + EXPECT_TRUE(ctl.state()->callValidateNumbers(qpi, ok).isValid); + + QTFRandomValues dup = ctl.makeValidNumbers(1, 2, 3, 4); + dup.set(3, 2); + EXPECT_FALSE(ctl.state()->callValidateNumbers(qpi, dup).isValid); + + QTFRandomValues outOfRange = ctl.makeValidNumbers(1, 2, 3, 4); + outOfRange.set(2, 31); + EXPECT_FALSE(ctl.state()->callValidateNumbers(qpi, outOfRange).isValid); +} + +TEST(ContractQThirtyFour_Private, GetRandomValues_IsDeterministicAndUniqueInRange) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const uint64 seed = 0x123456789ABCDEF0ULL; + const auto out1 = ctl.state()->callGetRandomValues(qpi, seed); + const auto out2 = ctl.state()->callGetRandomValues(qpi, seed); + EXPECT_TRUE(valuesEqual(out1.values, out2.values)); + + std::set seen; + for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) + { + const uint8 v = out1.values.get(i); + EXPECT_GE(v, 1); + EXPECT_LE(v, QTF_MAX_RANDOM_VALUE); + seen.insert(v); + EXPECT_EQ(out1.values.get(i), out2.values.get(i)); + } + EXPECT_EQ(seen.size(), static_cast(QTF_RANDOM_VALUES_COUNT)); +} + +TEST(ContractQThirtyFour_Private, CheckContractBalance_UsesIncomingMinusOutgoing) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const uint64 balance = 123456; + increaseEnergy(ctl.qtfSelf(), balance); + + const auto outExact = ctl.state()->callCheckContractBalance(qpi, balance); + EXPECT_TRUE(outExact.hasEnough); + EXPECT_EQ(outExact.actualBalance, balance); + + const auto outTooHigh = ctl.state()->callCheckContractBalance(qpi, balance + 1); + EXPECT_FALSE(outTooHigh.hasEnough); + EXPECT_EQ(outTooHigh.actualBalance, balance); +} + +TEST(ContractQThirtyFour_Private, PowerFixedPoint_ComputesFastExponentiationInFixedPoint) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + // 0.5^2 = 0.25 + const auto out025 = ctl.state()->callPowerFixedPoint(qpi, QTF_FIXED_POINT_SCALE / 2, 2); + EXPECT_EQ(out025.result, QTF_FIXED_POINT_SCALE / 4); + + // 2.0^3 = 8.0 + const auto out8 = ctl.state()->callPowerFixedPoint(qpi, 2 * QTF_FIXED_POINT_SCALE, 3); + EXPECT_EQ(out8.result, 8 * QTF_FIXED_POINT_SCALE); +} + +TEST(ContractQThirtyFour_Private, CalculateExpectedRoundsToK4_HandlesEdgeCaseAndMonotonicity) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const auto out0 = ctl.state()->callCalculateExpectedRoundsToK4(qpi, 0); + EXPECT_EQ(out0.expectedRounds, UINT64_MAX); + + const auto out1 = ctl.state()->callCalculateExpectedRoundsToK4(qpi, 1); + const auto out100 = ctl.state()->callCalculateExpectedRoundsToK4(qpi, 100); + EXPECT_GT(out1.expectedRounds, 0ULL); + EXPECT_GT(out100.expectedRounds, 0ULL); + EXPECT_LE(out1.expectedRounds, QTF_FIXED_POINT_SCALE); + EXPECT_LE(out100.expectedRounds, QTF_FIXED_POINT_SCALE); + EXPECT_GT(out1.expectedRounds, out100.expectedRounds); +} + +TEST(ContractQThirtyFour_Private, CalcReserveTopUp_RespectsSoftFloorPerRoundAndPerWinnerCaps) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const uint64 P = 1000000ULL; + + // Below soft floor => nothing can be topped up. + { + const uint64 softFloor = smul(P, QTF_RESERVE_SOFT_FLOOR_MULT); + const auto out = ctl.state()->callCalcReserveTopUp(qpi, softFloor - 1, 1000ULL, 1000000000ULL, P); + EXPECT_EQ(out.topUpAmount, 0ULL); + } + + // Soft floor binds availableAboveFloor and per-round is 10% of total. + { + const auto out = ctl.state()->callCalcReserveTopUp(qpi, 25000000ULL, 10000000ULL, 1000000000ULL, P); + EXPECT_EQ(out.topUpAmount, 2500000ULL); + } + + // Per-winner cap binds. + { + const auto out = ctl.state()->callCalcReserveTopUp(qpi, 1000000000ULL, 50000000ULL, 1000000ULL, P); + EXPECT_EQ(out.topUpAmount, 1000000ULL); + } + + // Needed is below all caps. + { + const auto out = ctl.state()->callCalcReserveTopUp(qpi, 1000000000ULL, 12345ULL, 1000000000ULL, P); + EXPECT_EQ(out.topUpAmount, 12345ULL); + } +} + +TEST(ContractQThirtyFour_Private, CalculatePrizePools_MatchesFeeAndRakeMath) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const auto fees = ctl.getFees(); + ASSERT_NE(fees.winnerFeePercent, 0); + + const uint64 revenue = 1000000ULL; + const uint64 winnersBlockBeforeRake = (revenue * static_cast(fees.winnerFeePercent)) / 100ULL; + + { + const auto out = ctl.state()->callCalculatePrizePools(qpi, revenue, false); + EXPECT_EQ(out.winnersRake, 0ULL); + EXPECT_EQ(out.winnersBlock, winnersBlockBeforeRake); + EXPECT_EQ(out.k3Pool, (out.winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000ULL); + EXPECT_EQ(out.k2Pool, (out.winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000ULL); + } + + { + const auto out = ctl.state()->callCalculatePrizePools(qpi, revenue, true); + const uint64 expectedRake = (winnersBlockBeforeRake * QTF_FR_WINNERS_RAKE_BP) / 10000ULL; + EXPECT_EQ(out.winnersRake, expectedRake); + EXPECT_EQ(out.winnersBlock, winnersBlockBeforeRake - expectedRake); + EXPECT_EQ(out.k3Pool, (out.winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000ULL); + EXPECT_EQ(out.k2Pool, (out.winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000ULL); + } +} + +TEST(ContractQThirtyFour_Private, CalculateBaseGain_FollowsConfiguredRedirectsAndOverflowBias) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + const uint64 revenue = 1000000ULL; + const uint64 winnersBlock = 680000ULL; + + const auto out = ctl.state()->callCalculateBaseGain(qpi, revenue, winnersBlock); + EXPECT_EQ(out.baseGain, 118600ULL); +} + +TEST(ContractQThirtyFour_Private, CalculateExtraRedirectBP_ReturnsZeroOrClampsToMax) +{ + ContractTestingQTF ctl; + + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + + // Early exits + EXPECT_EQ(ctl.state()->callCalculateExtraRedirectBP(qpi, 0, 1, 1, 0).extraBP, 0ULL); + EXPECT_EQ(ctl.state()->callCalculateExtraRedirectBP(qpi, 1, 0, 1, 0).extraBP, 0ULL); + EXPECT_EQ(ctl.state()->callCalculateExtraRedirectBP(qpi, 1, 1, 0, 0).extraBP, 0ULL); + + // Clamp to max under large deficit. + { + const uint64 revenue = 1000000ULL; + const uint64 delta = revenue * 1000ULL; + const auto out = ctl.state()->callCalculateExtraRedirectBP(qpi, 100, delta, revenue, 0); + EXPECT_EQ(out.extraBP, QTF_FR_EXTRA_MAX_BP); + } + + // Base gain already covers required gain -> zero. + { + const auto out = ctl.state()->callCalculateExtraRedirectBP(qpi, 100, 1000ULL, 1000000ULL, 2000ULL); + EXPECT_EQ(out.extraBP, 0ULL); + } +} + +TEST(ContractQThirtyFour_Private, ProcessTierPayout_ComputesPayoutAndOptionalTopUp) +{ + ContractTestingQTF ctl; + + const id originator = id::randomValue(); + QpiContextUserProcedureCall qpi(QTF_CONTRACT_INDEX, originator, 0); + primeQpiProcedureContext(qpi, static_cast(ctl.state()->getDrawHourInternal())); + + // No winners -> all overflow. + { + const auto out = ctl.state()->callProcessTierPayout(qpi, 50, 0, 123, 100, 0, 1000000ULL); + EXPECT_EQ(out.perWinnerPayout, 0ULL); + EXPECT_EQ(out.overflow, 123ULL); + EXPECT_EQ(out.topUpReceived, 0ULL); + } + + // Top-up from QRP to meet floor. + { + const uint64 qrpBalanceBefore = 1000000000ULL; + increaseEnergy(ctl.qrpSelf(), qrpBalanceBefore); + + const uint64 qtfBalanceBefore = getBalance(ctl.qtfSelf()); + const uint64 qrpBalanceBeforeActual = getBalance(ctl.qrpSelf()); + + const auto out = ctl.state()->callProcessTierPayout(qpi, 50, 2, 10, 100, qrpBalanceBeforeActual, 1000000ULL); + EXPECT_EQ(out.perWinnerPayout, 50ULL); + EXPECT_EQ(out.overflow, 0ULL); + EXPECT_EQ(out.topUpReceived, 90ULL); + + EXPECT_EQ(getBalance(ctl.qtfSelf()), qtfBalanceBefore + 90); + EXPECT_EQ(getBalance(ctl.qrpSelf()), qrpBalanceBeforeActual - 90); + } + + // Per-winner cap applies and leaves overflow. + { + const uint64 P = 1000000ULL; + const uint64 cap = smul(P, QTF_TOPUP_PER_WINNER_CAP_MULT); + const auto out = ctl.state()->callProcessTierPayout(qpi, div(P, 2), 1, sadd(cap, 1234ULL), cap, 0, P); + EXPECT_EQ(out.perWinnerPayout, cap); + EXPECT_EQ(out.topUpReceived, 0ULL); + EXPECT_EQ(out.overflow, 1234ULL); + } +} + +TEST(ContractQThirtyFour_Private, ReturnAllTickets_RefundsEachPlayerAndClearsViaSettleEpochRevenueZeroBranch) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const id originator = id::randomValue(); + QpiContextUserProcedureCall qpi(QTF_CONTRACT_INDEX, originator, 0); + primeQpiProcedureContext(qpi, static_cast(ctl.state()->getDrawHourInternal())); + + // Setup a few players and refund them. + const uint64 ticketPrice = 10; + ctl.state()->setTicketPriceInternal(ticketPrice); + + const id p1 = id::randomValue(); + const id p2 = id::randomValue(); + const QTFRandomValues n1 = ctl.makeValidNumbers(1, 2, 3, 4); + const QTFRandomValues n2 = ctl.makeValidNumbers(5, 6, 7, 8); + ctl.addPlayerDirect(p1, n1); + ctl.addPlayerDirect(p2, n2); + + increaseEnergy(ctl.qtfSelf(), ticketPrice * 2); + const uint64 balBeforeContract = getBalance(ctl.qtfSelf()); + const uint64 balBeforeP1 = getBalance(p1); + const uint64 balBeforeP2 = getBalance(p2); + + ctl.state()->callReturnAllTickets(qpi); + + EXPECT_EQ(getBalance(p1), balBeforeP1 + ticketPrice); + EXPECT_EQ(getBalance(p2), balBeforeP2 + ticketPrice); + EXPECT_EQ(getBalance(ctl.qtfSelf()), balBeforeContract - (ticketPrice * 2)); + + // Now exercise SettleEpoch revenue==0 branch, which must clear players. + ctl.state()->setTicketPriceInternal(0); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 2ULL); + ctl.triggerDrawTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0ULL); +} + +// ============================================================================ +// BUY TICKET TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, BuyTicket_WhenSellingClosed_RefundsAndFails) +{ + ContractTestingQTF ctl; + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Selling is closed initially (before beginEpoch with valid time) + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 2); + const uint64 balBefore = getBalance(user); + + QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); + const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, nums); + + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::TICKET_SELLING_CLOSED)); + EXPECT_EQ(getBalance(user), balBefore); // Refunded + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, BuyTicket_TooLowPrice_RefundsAndFails) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 5); + const uint64 balBefore = getBalance(user); + + QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); + + // Test with price too low - should fail and refund + const QTF::BuyTicket_output outLow = ctl.buyTicket(user, ticketPrice - 1, nums); + EXPECT_EQ(outLow.returnCode, static_cast(QTF::EReturnCode::INVALID_TICKET_PRICE)); + EXPECT_EQ(getBalance(user), balBefore); // Fully refunded + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, BuyTicket_ZeroPrice_RefundsAndFails) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 2); + const uint64 balBefore = getBalance(user); + + const QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); + + const QTF::BuyTicket_output out = ctl.buyTicket(user, 0, nums); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::INVALID_TICKET_PRICE)); + EXPECT_EQ(getBalance(user), balBefore); // Fully refunded (0) + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, BuyTicket_OverpaidPrice_AcceptsAndReturnsExcess) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + const uint64 overpayment = ticketPrice * 2; // Pay double + increaseEnergy(user, overpayment * 2); + const uint64 balBefore = getBalance(user); + + QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); + + // Test with overpayment - should accept ticket and return excess + const QTF::BuyTicket_output outHigh = ctl.buyTicket(user, overpayment, nums); + EXPECT_EQ(outHigh.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + // Should have paid exactly ticketPrice, excess returned + const uint64 excess = overpayment - ticketPrice; + EXPECT_EQ(getBalance(user), balBefore - ticketPrice) << "User should pay exactly ticket price, excess returned"; + + // Ticket should be registered + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); +} + +TEST(ContractQThirtyFour, BuyTicket_OverpaidInvalidNumbers_RefundsFull_NoLeak) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + const uint64 overpayment = ticketPrice * 2; + increaseEnergy(user, overpayment * 2); + const uint64 balBefore = getBalance(user); + + // Invalid: out of range + const QTFRandomValues invalidNums = ctl.makeValidNumbers(1, 2, 3, 31); + const QTF::BuyTicket_output out = ctl.buyTicket(user, overpayment, invalidNums); + + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); + EXPECT_EQ(getBalance(user), balBefore) << "Full invocationReward must be refunded once"; + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, BuyTicket_InvalidNumbers_OutOfRange_Fails) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 10); + const uint64 balBefore = getBalance(user); + + // Number 0 is invalid (valid range is 1-30) + QTFRandomValues numsWithZero; + numsWithZero.set(0, 0); + numsWithZero.set(1, 2); + numsWithZero.set(2, 3); + numsWithZero.set(3, 4); + + const QTF::BuyTicket_output out1 = ctl.buyTicket(user, ticketPrice, numsWithZero); + EXPECT_EQ(out1.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); + EXPECT_EQ(getBalance(user), balBefore); + + // Number 31 is invalid (valid range is 1-30) + QTFRandomValues numsOver30; + numsOver30.set(0, 1); + numsOver30.set(1, 2); + numsOver30.set(2, 3); + numsOver30.set(3, 31); + + const QTF::BuyTicket_output out2 = ctl.buyTicket(user, ticketPrice, numsOver30); + EXPECT_EQ(out2.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); + EXPECT_EQ(getBalance(user), balBefore); + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, BuyTicket_DuplicateNumbers_Fails) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 5); + const uint64 balBefore = getBalance(user); + + // Duplicate number 5 + QTFRandomValues dupNums; + dupNums.set(0, 5); + dupNums.set(1, 5); + dupNums.set(2, 10); + dupNums.set(3, 15); + + const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, dupNums); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::INVALID_NUMBERS)); + EXPECT_EQ(getBalance(user), balBefore); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, BuyTicket_ValidPurchase_Success) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 2); + const uint64 balBefore = getBalance(user); + + QTFRandomValues nums = ctl.makeValidNumbers(5, 10, 15, 20); + + const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, nums); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + EXPECT_EQ(getBalance(user), balBefore - ticketPrice); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); + + // Verify player data stored correctly + const QTF::PlayerData& player = ctl.state()->getPlayer(0); + EXPECT_EQ(player.player, user); + EXPECT_EQ(player.randomValues.get(0), 5); + EXPECT_EQ(player.randomValues.get(1), 10); + EXPECT_EQ(player.randomValues.get(2), 15); + EXPECT_EQ(player.randomValues.get(3), 20); +} + +TEST(ContractQThirtyFour, BuyTicket_MultiplePlayers_Success) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Add 10 different players + for (int i = 0; i < 10; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = ctl.makeValidNumbers(static_cast(1 + i), static_cast(11 + i), 21, 30); + + ctl.fundAndBuyTicket(user, ticketPrice, nums); + } + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 10u); +} + +TEST(ContractQThirtyFour, BuyTicket_MaxPlayersReached_Fails) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Fill up to max players (1024) + for (uint64 i = 0; i < QTF_MAX_NUMBER_OF_PLAYERS; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i % 27) + 1), static_cast(((i + 1) % 27) + 1), + static_cast(((i + 2) % 27) + 1), static_cast(((i + 3) % 27) + 1)); + + // Only fund and buy; we expect all to succeed until max + increaseEnergy(user, ticketPrice * 2); + const QTF::BuyTicket_output out = ctl.buyTicket(user, ticketPrice, nums); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + } + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), QTF_MAX_NUMBER_OF_PLAYERS); + + // Try one more - should fail + const id extraUser = id::randomValue(); + increaseEnergy(extraUser, ticketPrice * 2); + const uint64 balBefore = getBalance(extraUser); + QTFRandomValues nums = ctl.makeValidNumbers(1, 2, 3, 4); + + const QTF::BuyTicket_output out = ctl.buyTicket(extraUser, ticketPrice, nums); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::MAX_PLAYERS_REACHED)); + EXPECT_EQ(getBalance(extraUser), balBefore); // Refunded +} + +TEST(ContractQThirtyFour, BuyTicket_SamePlayerMultipleTickets_Allowed) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 10); + + // Same player buys multiple tickets with different numbers + QTFRandomValues nums1 = ctl.makeValidNumbers(1, 2, 3, 4); + QTFRandomValues nums2 = ctl.makeValidNumbers(5, 6, 7, 8); + QTFRandomValues nums3 = ctl.makeValidNumbers(9, 10, 11, 12); + + EXPECT_EQ(ctl.buyTicket(user, ticketPrice, nums1).returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + EXPECT_EQ(ctl.buyTicket(user, ticketPrice, nums2).returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + EXPECT_EQ(ctl.buyTicket(user, ticketPrice, nums3).returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 3u); +} + +// ============================================================================ +// CONFIGURATION CHANGE TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, SetPrice_AccessControl) +{ + ContractTestingQTF ctl; + + const uint64 oldPrice = ctl.state()->getTicketPriceInternal(); + const uint64 newPrice = oldPrice * 2; + + // Random user should be denied + const id randomUser = id::randomValue(); + increaseEnergy(randomUser, 1); + const QTF::SetPrice_output outDenied = ctl.setPrice(randomUser, newPrice); + EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); + + // Price unchanged + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); +} + +TEST(ContractQThirtyFour, SetPrice_ZeroNotAllowed) +{ + ContractTestingQTF ctl; + increaseEnergy(ctl.state()->team(), 1); + + const uint64 oldPrice = ctl.state()->getTicketPriceInternal(); + const QTF::SetPrice_output outInvalid = ctl.setPrice(ctl.state()->team(), 0); + EXPECT_EQ(outInvalid.returnCode, static_cast(QTF::EReturnCode::INVALID_TICKET_PRICE)); + + // Price unchanged + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); +} + +TEST(ContractQThirtyFour, SetPrice_AppliesAfterEndEpoch) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + increaseEnergy(ctl.state()->team(), 1); + + const uint64 oldPrice = ctl.state()->getTicketPriceInternal(); + const uint64 newPrice = oldPrice * 3; + + const QTF::SetPrice_output outOk = ctl.setPrice(ctl.state()->team(), newPrice); + EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + // Queued in NextEpochData + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newTicketPrice, newPrice); + + // Old price still active + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); + + // Apply after END_EPOCH + ctl.endEpoch(); + ctl.beginEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); + + // NextEpochData cleared + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newTicketPrice, 0u); +} + +TEST(ContractQThirtyFour, SetSchedule_AccessControl) +{ + ContractTestingQTF ctl; + + const id randomUser = id::randomValue(); + increaseEnergy(randomUser, 1); + const QTF::SetSchedule_output outDenied = ctl.setSchedule(randomUser, QTF_ANY_DAY_SCHEDULE); + EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); +} + +TEST(ContractQThirtyFour, SetSchedule_ZeroNotAllowed) +{ + ContractTestingQTF ctl; + increaseEnergy(ctl.state()->team(), 1); + + const QTF::SetSchedule_output outInvalid = ctl.setSchedule(ctl.state()->team(), 0); + EXPECT_EQ(outInvalid.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); +} + +TEST(ContractQThirtyFour, SetSchedule_AppliesAfterEndEpoch) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + increaseEnergy(ctl.state()->team(), 1); + + const uint8 newSchedule = 0x7F; // All days + + const QTF::SetSchedule_output outOk = ctl.setSchedule(ctl.state()->team(), newSchedule); + EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + // Queued + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newSchedule, newSchedule); + + // Apply + ctl.endEpoch(); + ctl.beginEpoch(); + EXPECT_EQ(ctl.getSchedule().schedule, newSchedule); +} + +TEST(ContractQThirtyFour, SetTargetJackpot_AccessControl) +{ + ContractTestingQTF ctl; + + const id randomUser = id::randomValue(); + increaseEnergy(randomUser, 1); + const QTF::SetTargetJackpot_output outDenied = ctl.setTargetJackpot(randomUser, 2000000000ULL); + EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); +} + +TEST(ContractQThirtyFour, SetTargetJackpot_ZeroNotAllowed) +{ + ContractTestingQTF ctl; + increaseEnergy(ctl.state()->team(), 1); + + const QTF::SetTargetJackpot_output outInvalid = ctl.setTargetJackpot(ctl.state()->team(), 0); + EXPECT_EQ(outInvalid.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); +} + +TEST(ContractQThirtyFour, SetTargetJackpot_AppliesAfterEndEpoch) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + increaseEnergy(ctl.state()->team(), 1); + + const uint64 newTarget = 5000000000ULL; + + const QTF::SetTargetJackpot_output outOk = ctl.setTargetJackpot(ctl.state()->team(), newTarget); + EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + // Queued + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newTargetJackpot, newTarget); + + // Apply + ctl.endEpoch(); + ctl.beginEpoch(); + EXPECT_EQ(ctl.state()->getTargetJackpotInternal(), newTarget); +} + +TEST(ContractQThirtyFour, SetDrawHour_AccessControl) +{ + ContractTestingQTF ctl; + + const id randomUser = id::randomValue(); + increaseEnergy(randomUser, 1); + const QTF::SetDrawHour_output outDenied = ctl.setDrawHour(randomUser, 15); + EXPECT_EQ(outDenied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); +} + +TEST(ContractQThirtyFour, SetDrawHour_InvalidValues) +{ + ContractTestingQTF ctl; + increaseEnergy(ctl.state()->team(), 2); + + // 0 is invalid + const QTF::SetDrawHour_output out0 = ctl.setDrawHour(ctl.state()->team(), 0); + EXPECT_EQ(out0.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); + + // 24+ is invalid + const QTF::SetDrawHour_output out24 = ctl.setDrawHour(ctl.state()->team(), 24); + EXPECT_EQ(out24.returnCode, static_cast(QTF::EReturnCode::INVALID_VALUE)); +} + +TEST(ContractQThirtyFour, SetDrawHour_AppliesAfterEndEpoch) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + increaseEnergy(ctl.state()->team(), 1); + + const uint8 newHour = 18; + + const QTF::SetDrawHour_output outOk = ctl.setDrawHour(ctl.state()->team(), newHour); + EXPECT_EQ(outOk.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + // Queued + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newDrawHour, newHour); + + // Apply + ctl.endEpoch(); + ctl.beginEpoch(); + EXPECT_EQ(ctl.getDrawHour().drawHour, newHour); +} + +TEST(ContractQThirtyFour, SyncJackpot_AccessControlAndUpdatesFromContractBalance) +{ + ContractTestingQTF ctl; + static constexpr uint64 newJackpotValue = 12345ULL; + ctl.state()->setJackpot(newJackpotValue); + + const id outsider = id::randomValue(); + increaseEnergy(outsider, 1); + + const QTF::SyncJackpot_output denied = ctl.syncJackpot(outsider); + EXPECT_EQ(denied.returnCode, static_cast(QTF::EReturnCode::ACCESS_DENIED)); + EXPECT_EQ(ctl.state()->getJackpot(), newJackpotValue); + + static constexpr uint64 expectedBalance = 777777ULL; + increaseEnergy(ctl.qtfSelf(), expectedBalance); + increaseEnergy(ctl.state()->team(), 1); + + const QTF::SyncJackpot_output synced = ctl.syncJackpot(ctl.state()->team()); + EXPECT_EQ(synced.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + EXPECT_EQ(ctl.state()->getJackpot(), expectedBalance); +} + +TEST(ContractQThirtyFour, GetPlayers_NoTickets_ReturnsEmptyArray) +{ + ContractTestingQTF ctl; + + static const QTF::PlayerData emptyPlayerData = {}; + + const QTF::GetPlayers_output players = ctl.getPlayers(); + EXPECT_EQ(players.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + EXPECT_EQ(memcmp(&players.players.get(0), &emptyPlayerData, sizeof(QTF::PlayerData)), 0); +} + +TEST(ContractQThirtyFour, GetPlayers_ReturnsPurchasedTickets) +{ + ContractTestingQTF ctl; + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user1 = id::randomValue(); + const id user2 = id::randomValue(); + const QTFRandomValues nums1 = ctl.makeValidNumbers(1, 4, 7, 10); + const QTFRandomValues nums2 = ctl.makeValidNumbers(2, 5, 8, 11); + + ctl.fundAndBuyTicket(user1, ticketPrice, nums1); + ctl.fundAndBuyTicket(user2, ticketPrice, nums2); + + const QTF::GetPlayers_output players = ctl.getPlayers(); + EXPECT_EQ(players.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + const QTF::PlayerData player0 = players.players.get(0); + const QTF::PlayerData player1 = players.players.get(1); + EXPECT_EQ(player0.player, user1); + EXPECT_TRUE(valuesEqual(player0.randomValues, nums1)); + EXPECT_EQ(player1.player, user2); + EXPECT_TRUE(valuesEqual(player1.randomValues, nums2)); + EXPECT_TRUE(isZero(players.players.get(2).player)); + EXPECT_TRUE(isZero(players.players.get(3).player)); +} + +TEST(ContractQThirtyFour, WinningCombinationsHistory_StoresWinningCombinationAfterDraw) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, ctl.makeValidNumbers(1, 2, 3, 4)); + + m256i digest = {}; + digest.m256i_u64[0] = 0x1122334455667788ULL; + ctl.drawWithDigest(digest); + + const QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); + const QTF::GetWinningCombinationsHistory_output history = ctl.getWinningCombinationsHistory(); + + EXPECT_EQ(history.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + EXPECT_TRUE(valuesEqual(history.history.get(0).values, winnerData.winnerData.winnerValues)); + EXPECT_EQ(ctl.state()->getWinningCombinationsWriteIndex(), 1ULL); +} + +TEST(ContractQThirtyFour, WinningCombinationsHistory_WrapAroundKeepsLatestAtLastWrittenSlot) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + static constexpr uint64 rounds = QTF_WINNING_COMBINATIONS_HISTORY_SIZE + 3ULL; + QTFRandomValues lastWinningValues{}; + + for (uint64 i = 0; i < rounds; ++i) + { + ctl.beginEpochWithValidTime(); + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, ctl.makeValidNumbers(1, 2, 3, 4)); + + m256i digest = {}; + digest.m256i_u64[0] = 0xABCDEF0000000000ULL + i; + ctl.drawWithDigest(digest); + + lastWinningValues = ctl.getWinnerData().winnerData.winnerValues; + } + + const QTF::GetWinningCombinationsHistory_output history = ctl.getWinningCombinationsHistory(); + EXPECT_EQ(history.returnCode, static_cast(QTF::EReturnCode::SUCCESS)); + + for (uint64 i = 0; i < history.history.capacity(); ++i) + { + EXPECT_FALSE(isEmptyRandomValues(history.history.get(i).values)); + } + + const uint64 writeIndex = ctl.state()->getWinningCombinationsWriteIndex(); + EXPECT_EQ(writeIndex, rounds % QTF_WINNING_COMBINATIONS_HISTORY_SIZE); + + const uint64 lastWrittenSlot = (writeIndex + QTF_WINNING_COMBINATIONS_HISTORY_SIZE - 1ULL) % QTF_WINNING_COMBINATIONS_HISTORY_SIZE; + EXPECT_TRUE(valuesEqual(history.history.get(lastWrittenSlot).values, lastWinningValues)); +} + +// ============================================================================ +// STATE AND POOLS TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, GetState_NoneThenSelling) +{ + ContractTestingQTF ctl; + + // Initially not selling + EXPECT_EQ(ctl.getStateInfo().currentState, static_cast(QTF::EState::STATE_NONE)); + + // After epoch start with valid time it should sell + ctl.beginEpochWithValidTime(); + EXPECT_EQ(ctl.getStateInfo().currentState, static_cast(QTF::EState::STATE_SELLING)); +} + +TEST(ContractQThirtyFour, GetPools_ReserveReflectsQRPAvailable) +{ + ContractTestingQTF ctl; + + const QTF::GetPools_output poolsBefore = ctl.getPools(); + const uint64 before = poolsBefore.pools.reserve; + + constexpr uint64 qrpFunding = 10'000'000'000ULL; + increaseEnergy(ctl.qrpSelf(), qrpFunding); + + const QTF::GetPools_output poolsAfter = ctl.getPools(); + const uint64 after = poolsAfter.pools.reserve; + + EXPECT_GE(after, before); + EXPECT_GT(after, 0u); + EXPECT_LE(after, before + qrpFunding); +} + +// ============================================================================ +// SETTLEMENT AND PAYOUT TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, Settlement_WithPlayers_FeesDistributed) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + ctl.forceFRDisabledForBaseline(); + + // Fix RNG so we can deterministically avoid winners (and especially k=4). + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x1010101010101010ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const QTF::GetFees_output fees = ctl.getFees(); + constexpr uint64 numPlayers = 10; + + // Ensure RL shares exist so distribution path is exercised deterministically. + const id shareholder1 = id::randomValue(); + const id shareholder2 = id::randomValue(); + constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 3; + constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; + std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; + issueRlSharesTo(rlShares); + + // Verify FR is not active initially (baseline mode) + EXPECT_EQ(ctl.state()->getFrActive(), false); + + // Add players + ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); + + const uint64 totalRevenue = ticketPrice * numPlayers; + const uint64 devBalBefore = getBalance(ctl.state()->team()); + const uint64 sh1Before = getBalance(shareholder1); + const uint64 sh2Before = getBalance(shareholder2); + const uint64 rlBefore = getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)); + const uint64 contractBalBefore = getBalance(ctl.qtfSelf()); + + EXPECT_EQ(contractBalBefore, totalRevenue); + + ctl.drawWithDigest(testDigest); + + EXPECT_EQ(ctl.state()->getFrActive(), false); + + // In baseline mode (FR not active), dev receives full 10% of revenue + // No redirects are applied + const uint64 expectedDevFee = (totalRevenue * fees.teamFeePercent) / 100; + EXPECT_EQ(getBalance(ctl.state()->team()), devBalBefore + expectedDevFee) + << "In baseline mode, dev should receive full " << static_cast(fees.teamFeePercent) << "% of revenue"; + + // Distribution is paid to RL shareholders with flooring to dividendPerShare and payback remainder to RL contract. + const uint64 expectedDistFee = (totalRevenue * fees.distributionFeePercent) / 100; + const uint64 dividendPerShare = expectedDistFee / NUMBER_OF_COMPUTORS; + const uint64 expectedSh1Gain = static_cast(shares1) * dividendPerShare; + const uint64 expectedSh2Gain = static_cast(shares2) * dividendPerShare; + const uint64 expectedPayback = expectedDistFee - (dividendPerShare * NUMBER_OF_COMPUTORS); + EXPECT_EQ(getBalance(shareholder1), sh1Before + expectedSh1Gain); + EXPECT_EQ(getBalance(shareholder2), sh2Before + expectedSh2Gain); + EXPECT_EQ(getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)), rlBefore + expectedPayback); + + // No winners -> winnersOverflow == winnersBlock. In baseline: 50/50 split reserve/jackpot. + const uint64 winnersBlock = (totalRevenue * fees.winnerFeePercent) / 100; + const uint64 reserveAdd = (winnersBlock * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; + const uint64 expectedJackpotAdd = winnersBlock - reserveAdd; + EXPECT_EQ(ctl.state()->getJackpot(), expectedJackpotAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), expectedJackpotAdd) << "Contract balance should match carry (jackpot) after settlement"; + + // Players cleared + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, Settlement_NoPlayers_NoChanges) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const uint64 jackpotBefore = ctl.state()->getJackpot(); + const QTF::GetWinnerData_output winnersBefore = ctl.getWinnerData(); + + ctl.triggerDrawTick(); + + // No changes when no players + EXPECT_EQ(ctl.state()->getJackpot(), jackpotBefore); + const QTF::GetWinnerData_output winnersAfter = ctl.getWinnerData(); + EXPECT_EQ(winnersAfter.winnerData.winnerCounter, winnersBefore.winnerData.winnerCounter); +} + +TEST(ContractQThirtyFour, Settlement_InsufficientBalance_ClearsPlayersAndAbortsSettlement) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x3030303030303030ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + constexpr uint64 numPlayers = 2; + + ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numPlayers); + + // Drain the contract so CheckContractBalance() fails in SettleEpoch. + const uint64 totalRevenue = ticketPrice * numPlayers; + const int qtfIndex = spectrumIndex(ctl.qtfSelf()); + ASSERT_GE(qtfIndex, 0); + ASSERT_TRUE(decreaseEnergy(qtfIndex, totalRevenue)); + EXPECT_EQ(getBalance(ctl.qtfSelf()), 0); + + ctl.drawWithDigest(testDigest); + + // Even if refunds can't be paid (because we drained balance), the contract must clear the epoch state. + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0ULL); +} + +TEST(ContractQThirtyFour, Settlement_WithPlayers_FeesDistributed_FRMode) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + // Fix RNG so we can deterministically avoid winners (and especially k=4). + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x2020202020202020ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Activate FR mode + ctl.state()->setJackpot(100000000ULL); // Below target + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); + ctl.forceFREnabledWithinWindow(5); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const QTF::GetFees_output fees = ctl.getFees(); + constexpr uint64 numPlayers = 10; + + // Verify FR is active + EXPECT_EQ(ctl.state()->getFrActive(), true); + + // Add players + ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); + + const uint64 totalRevenue = ticketPrice * numPlayers; + const uint64 devBalBefore = getBalance(ctl.state()->team()); + const uint64 contractBalBefore = getBalance(ctl.qtfSelf()); + const uint64 jackpotBefore = ctl.state()->getJackpot(); + const uint64 roundsSinceK4Before = ctl.state()->getFrRoundsSinceK4(); + + EXPECT_EQ(contractBalBefore, totalRevenue); + + ctl.drawWithDigest(testDigest); + + // In FR mode, dev receives less than full 10% of revenue + // Base redirect: 1% of revenue (QTF_FR_DEV_REDIRECT_BP = 100 basis points) + // Possible extra redirect depending on deficit + const uint64 baseDevRedirect = (totalRevenue * QTF_FR_DEV_REDIRECT_BP) / 10000; + + // Full dev fee from revenue split (10%) + const uint64 fullDevFee = (totalRevenue * fees.teamFeePercent) / 100; + + // Actual dev payout = fullDevFee - redirects + // Expected: fullDevFee - at least baseDevRedirect + const uint64 maxExpectedDevPayout = fullDevFee - baseDevRedirect; + + const uint64 actualDevPayout = getBalance(ctl.state()->team()) - devBalBefore; + + // Dev should receive less than full fee (due to redirects to jackpot) + EXPECT_LT(actualDevPayout, fullDevFee) << "In FR mode, dev payout should be reduced by redirects"; + + // Dev should receive at most fullDevFee - baseDevRedirect + EXPECT_LE(actualDevPayout, maxExpectedDevPayout) << "Dev payout should be reduced by at least base redirect (1%)"; + + // Jackpot should have grown (receives redirects) + EXPECT_GT(ctl.state()->getJackpot(), jackpotBefore) << "Jackpot should grow from dev/dist redirects in FR mode"; + + // No k=4 can happen (we buy losing tickets), so counter increments. + EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), roundsSinceK4Before + 1); + + // Players cleared + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, Settlement_JackpotGrowsFromOverflow) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + ctl.forceFRDisabledForBaseline(); + + // Fix RNG so we can deterministically create "no winners" tickets. + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xBADC0FFEE0DDF00DULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const uint64 jackpotBefore = ctl.state()->getJackpot(); + constexpr uint64 numPlayers = 20; + + // Add players + for (uint64 i = 0; i < numPlayers; ++i) + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + + // Calculate expected jackpot growth in baseline mode (FR not active) + const uint64 revenue = ticketPrice * numPlayers; + const QTF::GetFees_output fees = ctl.getFees(); + + // winnersBlock = revenue * winnerFeePercent / 100 (68%) + const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; + + // With no winners, the entire winners block becomes overflow (k2+k3 pools also roll into overflow). + const uint64 winnersOverflow = winnersBlock; + + // In baseline mode: 50% of overflow goes to jackpot, 50% to reserve. + const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; + const uint64 overflowToJackpot = winnersOverflow - reserveAdd; + + // Minimum expected jackpot growth (assuming no k2/k3 winners, all overflow goes to jackpot) + const uint64 minExpectedGrowth = overflowToJackpot; + + ctl.drawWithDigest(testDigest); + + // Verify jackpot growth + const uint64 jackpotAfter = ctl.state()->getJackpot(); + const uint64 actualGrowth = jackpotAfter - jackpotBefore; + + // Deterministic: losing tickets guarantee no winners, so growth should match exactly. + EXPECT_EQ(actualGrowth, minExpectedGrowth) << "Actual growth: " << actualGrowth << ", Expected: " << minExpectedGrowth + << ", Overflow to jackpot (50%): " << overflowToJackpot; + + // Verify the 50% overflow split is working correctly + const uint64 expected50Percent = winnersOverflow / 2; + EXPECT_GE(overflowToJackpot, expected50Percent - 1) << "50% overflow split verification"; + EXPECT_LE(overflowToJackpot, winnersOverflow) << "Overflow to jackpot should not exceed total overflow"; +} + +TEST(ContractQThirtyFour, Settlement_RoundsSinceK4_Increments) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x1111222233334444ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Run several rounds without k=4 win + for (int round = 0; round < 3; ++round) + { + ctl.beginEpochWithValidTime(); + + ctl.buyRandomTickets(5, ticketPrice, nums.losing); + + const uint64 roundsBefore = ctl.state()->getFrRoundsSinceK4(); + ctl.drawWithDigest(testDigest); + + // Deterministic: no ticket matches any winning number, so k=4 cannot occur. + EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), roundsBefore + 1); + } +} + +// ============================================================================ +// FAST-RECOVERY (FR) TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, FR_Activation_WhenBelowTarget) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + // Set jackpot below target to trigger FR + ctl.state()->setJackpot(100000000ULL); // 100M + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target + ctl.state()->setFrRoundsSinceK4(5); // Within post-k4 window + + EXPECT_EQ(ctl.state()->getFrActive(), false); + + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Add players and settle + for (int i = 0; i < 10; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i % 25) + 1), static_cast((i % 25) + 2), + static_cast((i % 25) + 3), static_cast((i % 25) + 4)); + ctl.fundAndBuyTicket(user, ticketPrice, nums); + } + + ctl.triggerDrawTick(); + + // FR should be active since jackpot < target and within window + EXPECT_EQ(ctl.state()->getFrActive(), true); +} + +TEST(ContractQThirtyFour, FR_Deactivation_AfterHysteresis) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x3030303030303030ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Set jackpot at target + ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT); + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); + ctl.state()->setFrActive(true); + ctl.state()->setFrRoundsAtOrAboveTarget(0); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Run rounds at or above target (hysteresis requirement) + for (int round = 0; round < QTF_FR_HYSTERESIS_ROUNDS; ++round) + { + ctl.beginEpochWithValidTime(); + + ctl.buyRandomTickets(5, ticketPrice, nums.losing); + + // Keep jackpot at target (add back what might be paid out) + ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT); + ctl.drawWithDigest(testDigest); + } + + // After 3 rounds at target, FR should deactivate + EXPECT_GE(ctl.state()->getFrRoundsAtOrAboveTarget(), QTF_FR_HYSTERESIS_ROUNDS); + EXPECT_EQ(ctl.state()->getFrActive(), false); +} + +TEST(ContractQThirtyFour, FR_OverflowBias_95PercentToJackpot) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + // Fix RNG so we can deterministically create "no winners" tickets. + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xCAFEBABEDEADBEEFULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Activate FR + ctl.state()->setJackpot(100000000ULL); // Below target + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); + ctl.state()->setFrActive(true); + ctl.state()->setFrRoundsSinceK4(5); + + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const uint64 jackpotBefore = ctl.state()->getJackpot(); + constexpr uint64 numPlayers = 50; + + // Add many players to generate significant overflow + ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); + + // Calculate expected jackpot growth + const uint64 revenue = ticketPrice * numPlayers; + const QTF::GetFees_output fees = ctl.getFees(); + + // winnersBlock = revenue * winnerFeePercent / 100 (68%) + const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; + + // In FR mode: 5% rake from winnersBlock goes to jackpot + const uint64 winnersRake = (winnersBlock * QTF_FR_WINNERS_RAKE_BP) / 10000; + const uint64 winnersBlockAfterRake = winnersBlock - winnersRake; + + // With no winners, the entire winners block after rake becomes overflow (k2+k3 pools also roll into overflow). + const uint64 winnersOverflow = winnersBlockAfterRake; + + // In FR mode: 95% of overflow goes to jackpot, 5% to reserve + const uint64 reserveAdd = (winnersOverflow * QTF_FR_ALPHA_BP) / 10000; + const uint64 overflowToJackpot = winnersOverflow - reserveAdd; + + // Dev and Dist redirects in FR mode: base (1% each) + extra (deficit-driven) + // First calculate base gain to pass to extra redirect calculation + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + const auto baseGainOut = ctl.state()->callCalculateBaseGain(qpi, revenue, winnersBlock); + + // Calculate extra redirect based on deficit + const uint64 delta = ctl.state()->getTargetJackpotInternal() - jackpotBefore; + const auto extraOut = ctl.state()->callCalculateExtraRedirectBP(qpi, numPlayers, delta, revenue, baseGainOut.baseGain); + + // Total redirect BP = base + extra (split 50/50 between dev and dist) + const uint64 devExtraBP = extraOut.extraBP / 2; + const uint64 distExtraBP = extraOut.extraBP - devExtraBP; + const uint64 totalDevRedirectBP = QTF_FR_DEV_REDIRECT_BP + devExtraBP; + const uint64 totalDistRedirectBP = QTF_FR_DIST_REDIRECT_BP + distExtraBP; + + const uint64 devRedirect = (revenue * totalDevRedirectBP) / 10000; + const uint64 distRedirect = (revenue * totalDistRedirectBP) / 10000; + + // Expected jackpot growth (with both base and extra redirects, assuming no k2/k3 winners) + // totalJackpotContribution = overflowToJackpot + winnersRake + devRedirect + distRedirect + const uint64 expectedGrowth = overflowToJackpot + winnersRake + devRedirect + distRedirect; + + ctl.drawWithDigest(testDigest); + + // Verify that jackpot grew by the expected amount + const uint64 actualGrowth = ctl.state()->getJackpot() - jackpotBefore; + + // Deterministic: losing tickets guarantee no winners, so growth should match exactly. + EXPECT_EQ(actualGrowth, expectedGrowth) << "Actual growth: " << actualGrowth << ", Expected: " << expectedGrowth + << ", Overflow to jackpot (95%): " << overflowToJackpot << ", Winners rake: " << winnersRake + << ", Extra redirect BP: " << extraOut.extraBP; + + // Verify the 95% overflow bias is working correctly + // overflowToJackpot should be ~95% of winnersOverflow + const uint64 expected95Percent = (winnersOverflow * 95) / 100; + EXPECT_GE(overflowToJackpot, expected95Percent - 1) << "95% overflow bias verification"; + EXPECT_LE(overflowToJackpot, winnersOverflow) << "Overflow to jackpot should not exceed total overflow"; +} + +// ============================================================================ +// WINNER COUNTING AND TIER TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, WinnerData_RecordsWinners) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // At least one ticket is required, otherwise END_EPOCH returns early and winner values are not generated. + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, ctl.makeValidNumbers(1, 2, 3, 4)); + + ctl.triggerDrawTick(); + + const QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); + expectWinnerValuesValidAndUnique(winnerData); +} + +TEST(ContractQThirtyFour, WinnerData_ResetEachRound) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + // Round 1: force a deterministic k=2 winner so winnerCounter becomes > 0. + m256i digest1 = {}; + digest1.m256i_u64[0] = 0x13579BDF2468ACE0ULL; + const auto nums1 = ctl.computeWinningAndLosing(digest1); + + ctl.beginEpochWithValidTime(); + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + QTFRandomValues k2Numbers = ctl.makeK2Numbers(nums1.winning); + const id k2Winner = id::randomValue(); + ctl.fundAndBuyTicket(k2Winner, ticketPrice, k2Numbers); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); + + ctl.drawWithDigest(digest1); + + const QTF::GetWinnerData_output afterRound1 = ctl.getWinnerData(); + EXPECT_GT(afterRound1.winnerData.winnerCounter, 0u); + + // Round 2: force a deterministic "no winners" round, winnerCounter must reset to 0. + m256i digest2 = {}; + digest2.m256i_u64[0] = 0x0F0E0D0C0B0A0908ULL; + const auto nums2 = ctl.computeWinningAndLosing(digest2); + + ctl.beginEpochWithValidTime(); + ctl.buyRandomTickets(5, ticketPrice, nums2.losing); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 5u); + + ctl.drawWithDigest(digest2); + + const QTF::GetWinnerData_output afterRound2 = ctl.getWinnerData(); + EXPECT_EQ(afterRound2.winnerData.winnerCounter, 0u) << "Winner snapshot must reset each round"; +} + +// ============================================================================ +// EDGE CASE TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, BuyTicket_ValidNumberSelections_EdgeCases_Success) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + static constexpr uint8 cases[][4] = { + {1, 2, 29, 30}, // boundary + {15, 16, 17, 18}, // consecutive + {27, 28, 29, 30}, // highest + {1, 2, 3, 4}, // lowest + }; + + for (uint64 i = 0; i < (sizeof(cases) / sizeof(cases[0])); ++i) + { + const id user = id::randomValue(); + const QTFRandomValues nums = ctl.makeValidNumbers(cases[i][0], cases[i][1], cases[i][2], cases[i][3]); + ctl.fundAndBuyTicket(user, ticketPrice, nums); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), i + 1); + } +} + +// ============================================================================ +// MULTIPLE ROUNDS TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, MultipleRounds_JackpotAccumulates) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x0DDC0FFEE0DDF00DULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + uint64 prevJackpot = 0; + + // Run multiple rounds + for (int round = 0; round < 5; ++round) + { + ctl.beginEpochWithValidTime(); + + ctl.buyRandomTickets(10, ticketPrice, nums.losing); + ctl.drawWithDigest(testDigest); + + // Jackpot should increase each round (no k=4 winners in this test) + const uint64 currentJackpot = ctl.state()->getJackpot(); + EXPECT_GT(currentJackpot, prevJackpot) << "Round " << round << ": jackpot should grow"; + + // Track for next iteration + prevJackpot = currentJackpot; + } +} + +TEST(ContractQThirtyFour, MultipleRounds_StateResetsCorrectly) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + for (int round = 0; round < 3; ++round) + { + ctl.beginEpochWithValidTime(); + + // Add different number of players each round + const int playersThisRound = 5 + round * 3; + for (int i = 0; i < playersThisRound; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i + round) % 27 + 1), static_cast((i + round + 5) % 27 + 1), + static_cast((i + round + 10) % 27 + 1), static_cast((i + round + 15) % 27 + 1)); + ctl.fundAndBuyTicket(user, ticketPrice, nums); + } + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), static_cast(playersThisRound)); + + ctl.triggerDrawTick(); + + // Players should be cleared after each round + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); + } +} + +// ============================================================================ +// POST_INCOMING_TRANSFER TEST +// ============================================================================ + +TEST(ContractQThirtyFour, PostIncomingTransfer_StandardTransaction_Refunded) +{ + ContractTestingQTF ctl; + constexpr uint64 transferAmount = 123456789; + + const id sender = id::randomValue(); + increaseEnergy(sender, transferAmount); + EXPECT_EQ(getBalance(sender), transferAmount); + + const id contractAddress = ctl.qtfSelf(); + EXPECT_EQ(getBalance(contractAddress), 0); + + // Standard transaction should be refunded + notifyContractOfIncomingTransfer(sender, contractAddress, transferAmount, QPI::TransferType::standardTransaction); + + // Amount should be refunded to sender + EXPECT_EQ(getBalance(sender), transferAmount); + EXPECT_EQ(getBalance(contractAddress), 0); +} + +// ============================================================================ +// SCHEDULE AND TIME TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, Schedule_WednesdayAlwaysDraws_IgnoresScheduleMask) +{ + ContractTestingQTF ctl; + + // Exclude Wednesday from schedule mask (e.g., Monday only). + constexpr uint8 mondayOnly = 1 << MONDAY; + ctl.forceSchedule(mondayOnly); + + ctl.beginEpochWithValidTime(); + + const m256i testDigest = {}; + ctl.setPrevSpectrumDigest(testDigest); + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + for (int i = 0; i < 5; ++i) + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 5u); + + // Wednesday should always trigger a draw at/after draw hour, even if schedule mask does not include it. + const uint8 drawHour = ctl.state()->getDrawHourInternal(); + ctl.setDateTime(2025, 1, 15, drawHour); + ctl.forceBeginTick(); + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, Schedule_DrawOnlyOnScheduledDays) +{ + ContractTestingQTF ctl; + + // Set schedule to Wednesday only (default) + constexpr uint8 wednesdayOnly = 1 << WEDNESDAY; + ctl.forceSchedule(wednesdayOnly); + + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Add players + for (int i = 0; i < 5; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = + ctl.makeValidNumbers(static_cast(i + 1), static_cast(i + 5), static_cast(i + 10), static_cast(i + 15)); + ctl.fundAndBuyTicket(user, ticketPrice, nums); + } + + const uint64 playersBefore = ctl.state()->getNumberOfPlayers(); + EXPECT_EQ(playersBefore, 5u); + + // Tuesday 2025-01-14 is not scheduled - should NOT trigger draw + ctl.setDateTime(2025, 1, 14, 12); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), playersBefore); // Unchanged + + // Wednesday 2025-01-15 IS scheduled - should trigger draw + ctl.setDateTime(2025, 1, 15, 12); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); // Cleared after draw +} + +TEST(ContractQThirtyFour, Schedule_DrawAtMostOncePerDay_LastDrawDateStampGuards) +{ + ContractTestingQTF ctl; + + // Use a non-Wednesday scheduled day so selling is re-enabled after the draw. + constexpr uint8 thursdayOnly = 1 << THURSDAY; + ctl.forceSchedule(thursdayOnly); + + ctl.beginEpochWithValidTime(); + + const m256i testDigest = {}; + ctl.setPrevSpectrumDigest(testDigest); + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + + const uint8 drawHour = ctl.state()->getDrawHourInternal(); + + // First draw on Thursday. + ctl.setDateTime(2025, 1, 16, drawHour); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); + + const uint64 jackpotAfterFirst = ctl.state()->getJackpot(); + const QTF::GetWinnerData_output winnersAfterFirst = ctl.getWinnerData(); + + // Buy another ticket on the same date (selling should be open on non-Wednesday). + { + const id user2 = id::randomValue(); + ctl.fundAndBuyTicket(user2, ticketPrice, nums.losing); + } + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); + + // Second tick on the same date must NOT trigger another draw. + ctl.setDateTime(2025, 1, 16, drawHour); + ctl.forceBeginTick(); + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); + EXPECT_EQ(ctl.state()->getJackpot(), jackpotAfterFirst); + const QTF::GetWinnerData_output winnersAfterSecondAttempt = ctl.getWinnerData(); + for (uint64 i = 0; i < QTF_RANDOM_VALUES_COUNT; ++i) + { + EXPECT_EQ(winnersAfterSecondAttempt.winnerData.winnerValues.get(i), winnersAfterFirst.winnerData.winnerValues.get(i)); + } + EXPECT_EQ((uint64)winnersAfterSecondAttempt.winnerData.epoch, (uint64)winnersAfterFirst.winnerData.epoch); +} + +TEST(ContractQThirtyFour, DrawHour_NoDrawBeforeScheduledHour) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Add players + for (int i = 0; i < 5; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = + ctl.makeValidNumbers(static_cast(i + 1), static_cast(i + 5), static_cast(i + 10), static_cast(i + 15)); + ctl.fundAndBuyTicket(user, ticketPrice, nums); + } + + const uint8 drawHour = ctl.state()->getDrawHourInternal(); + const uint64 playersBefore = ctl.state()->getNumberOfPlayers(); + + // Before draw hour - should NOT trigger draw + ctl.setDateTime(2025, 1, 15, drawHour - 1); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), playersBefore); + + // At or after draw hour - should trigger draw + ctl.setDateTime(2025, 1, 15, drawHour); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); +} + +TEST(ContractQThirtyFour, DrawHour_WednesdayDrawClosesTicketSelling) +{ + ContractTestingQTF ctl; + + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + ctl.beginEpochWithValidTime(); + + const m256i testDigest = {}; + ctl.setPrevSpectrumDigest(testDigest); + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + + const uint8 drawHour = ctl.state()->getDrawHourInternal(); + ctl.setDateTime(2025, 1, 15, drawHour); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 0u); + + // After a Wednesday draw, selling must remain closed until next epoch. + const id lateBuyer = id::randomValue(); + increaseEnergy(lateBuyer, ticketPrice * 2); + const uint64 before = getBalance(lateBuyer); + const QTF::BuyTicket_output out = ctl.buyTicket(lateBuyer, ticketPrice, nums.losing); + EXPECT_EQ(out.returnCode, static_cast(QTF::EReturnCode::TICKET_SELLING_CLOSED)); + EXPECT_EQ(getBalance(lateBuyer), before); +} + +// ============================================================================ +// PROBABILITY AND COMBINATORICS VERIFICATION +// ============================================================================ + +TEST(ContractQThirtyFour, Combinatorics_P4Denominator) +{ + // Verify the P4 denominator constant matches combinatorics + // C(30,4) = 30! / (4! * 26!) = 27405 + constexpr uint64 numerator = QTF_MAX_RANDOM_VALUE * 29 * 28 * 27; + constexpr uint64 denominator = QTF_RANDOM_VALUES_COUNT * 3 * 2 * 1; + constexpr uint64 expected = numerator / denominator; + + EXPECT_EQ(expected, QTF_P4_DENOMINATOR); + EXPECT_EQ(QTF_P4_DENOMINATOR, 27405u); +} + +// ============================================================================ +// FEE CALCULATION VERIFICATION +// ============================================================================ + +TEST(ContractQThirtyFour, FeeCalculation_TotalEquals100Percent) +{ + ContractTestingQTF ctl; + const QTF::GetFees_output fees = ctl.getFees(); + + const uint32 total = fees.teamFeePercent + fees.distributionFeePercent + fees.winnerFeePercent + fees.burnPercent; + + EXPECT_EQ(total, 100u); +} + +// ============================================================================ +// PRIZE PAYOUT ESTIMATION +// ============================================================================ + +TEST(ContractQThirtyFour, EstimatePrizePayouts_NoTickets) +{ + ContractTestingQTF ctl; + + // No tickets sold, should return zero payouts + QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(1, 1); + + EXPECT_EQ(estimate.k2PayoutPerWinner, 0ull); + EXPECT_EQ(estimate.k3PayoutPerWinner, 0ull); + EXPECT_EQ(estimate.k2Pool, 0ull); + EXPECT_EQ(estimate.k3Pool, 0ull); + EXPECT_EQ(estimate.totalRevenue, 0ull); +} + +TEST(ContractQThirtyFour, EstimatePrizePayouts_WithTicketsSingleWinner) +{ + ContractTestingQTF ctl; + + ctl.startAnyDayEpoch(); + + // Buy 100 tickets + constexpr uint64 ticketPrice = 1000000ull; // 1M QU + constexpr uint64 numTickets = 100; + + const QTFRandomValues numbers = ctl.makeValidNumbers(1, 2, 3, 4); + ctl.buyRandomTickets(numTickets, ticketPrice, numbers); + + // Verify tickets were purchased + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numTickets); + + // Estimate for 1 k2 winner and 1 k3 winner + QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(1, 1); + + const uint64 expectedRevenue = ticketPrice * numTickets; + EXPECT_EQ(estimate.totalRevenue, expectedRevenue); + + // Check minimum floors and cap using constants from contract + constexpr uint64 expectedK2Floor = ticketPrice * QTF_K2_FLOOR_MULT / QTF_K2_FLOOR_DIV; + constexpr uint64 expectedK3Floor = ticketPrice * QTF_K3_FLOOR_MULT; + constexpr uint64 expectedCap = ticketPrice * QTF_TOPUP_PER_WINNER_CAP_MULT; + EXPECT_EQ(estimate.k2MinFloor, expectedK2Floor); + EXPECT_EQ(estimate.k3MinFloor, expectedK3Floor); + EXPECT_EQ(estimate.perWinnerCap, expectedCap); + + // Winners block using contract constants + const QTF::GetFees_output fees = ctl.getFees(); + uint64 winnersBlock = 0, k2PoolExpected = 0, k3PoolExpected = 0; + computeBaselinePrizePools(expectedRevenue, fees, winnersBlock, k2PoolExpected, k3PoolExpected); + + EXPECT_EQ(estimate.k2Pool, k2PoolExpected); + EXPECT_EQ(estimate.k3Pool, k3PoolExpected); + + // With 1 winner each: k2 payout equals pool (below cap), k3 payout is capped at 25*P + EXPECT_EQ(estimate.k2PayoutPerWinner, k2PoolExpected); // 19.04M < 25M cap + EXPECT_EQ(estimate.k3PayoutPerWinner, expectedCap); // 27.2M capped to 25M +} + +TEST(ContractQThirtyFour, EstimatePrizePayouts_WithMultipleWinners) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + // Buy 1000 tickets + const uint64 ticketPrice = 1000000ull; + const uint64 numTickets = 1000; + + const QTFRandomValues numbers = ctl.makeValidNumbers(5, 10, 15, 20); + ctl.buyRandomTickets(numTickets, ticketPrice, numbers); + + // Verify tickets were purchased + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numTickets); + + // Estimate for 10 k2 winners and 5 k3 winners + QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(10, 5); + + const uint64 expectedRevenue = ticketPrice * numTickets; + const QTF::GetFees_output fees = ctl.getFees(); + uint64 winnersBlock = 0, k2Pool = 0, k3Pool = 0; + computeBaselinePrizePools(expectedRevenue, fees, winnersBlock, k2Pool, k3Pool); + + // Verify pools + EXPECT_EQ(estimate.k2Pool, k2Pool); + EXPECT_EQ(estimate.k3Pool, k3Pool); + + // Verify per-winner payouts (should be pool / winner count, capped) + const uint64 k2ExpectedPerWinner = k2Pool / 10; + const uint64 k3ExpectedPerWinner = k3Pool / 5; + + EXPECT_EQ(estimate.k2PayoutPerWinner, std::min(k2ExpectedPerWinner, estimate.perWinnerCap)); + EXPECT_EQ(estimate.k3PayoutPerWinner, std::min(k3ExpectedPerWinner, estimate.perWinnerCap)); + + // Both should be above minimum floors + EXPECT_GE(estimate.k2PayoutPerWinner, estimate.k2MinFloor); + EXPECT_GE(estimate.k3PayoutPerWinner, estimate.k3MinFloor); +} + +TEST(ContractQThirtyFour, EstimatePrizePayouts_NoWinnersShowsPotential) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + // Buy 50 tickets + const uint64 ticketPrice = 1000000ull; + const uint64 numTickets = 50; + + const QTFRandomValues numbers = ctl.makeValidNumbers(7, 14, 21, 28); + ctl.buyRandomTickets(numTickets, ticketPrice, numbers); + + // Verify tickets were purchased + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numTickets); + + // Estimate with 0 winners (shows what a single winner would get) + QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(0, 0); + + const uint64 expectedRevenue = ticketPrice * numTickets; + const QTF::GetFees_output fees = ctl.getFees(); + uint64 winnersBlock = 0, k2Pool = 0, k3Pool = 0; + computeBaselinePrizePools(expectedRevenue, fees, winnersBlock, k2Pool, k3Pool); + + // When no winners specified, should show full pool (capped) + EXPECT_EQ(estimate.k2PayoutPerWinner, std::min(k2Pool, estimate.perWinnerCap)); + EXPECT_EQ(estimate.k3PayoutPerWinner, std::min(k3Pool, estimate.perWinnerCap)); +} + +// ============================================================================ +// DETERMINISTIC WINNER TESTING +// ============================================================================ +// Solution: By fixing prevSpectrumDigest, we can deterministically control winning numbers +// +// Background: +// Settlement generates winning numbers using: seed = K12(prevSpectrumDigest).u64._0 +// This seed is then used in GetRandomValues (QThirtyFour.h:1663-1698) to derive 4 numbers. +// +// Approach: +// 1. Create a fixed test prevSpectrumDigest (e.g., testDigest) +// 2. Compute expected winning numbers for that digest +// 3. Buy tickets with exact winning numbers (for k=4), partial matches (for k=2/k=3), etc. +// 4. Trigger settlement with drawWithDigest(testDigest) +// 5. Settlement will use our fixed digest, generating the pre-computed winning numbers +// 6. Verify actual payouts, jackpot depletion, FR resets, etc. +// +// This enables deterministic testing of: +// - Actual k=4 jackpot win payouts and jackpot depletion +// - Actual k=2/k=3 winner payouts with real matching logic +// - Actual FR reset behavior after k=4 win (frRoundsSinceK4 = 0) +// - Pool splitting among multiple winners +// - Revenue distribution and fee calculations with real winners + +TEST(ContractQThirtyFour, DeterministicWinner_K4JackpotWin_DepletesAndReseeds) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + // Ensure QRP has enough reserve to reseed to target. + increaseEnergy(ctl.qrpSelf(), QTF_DEFAULT_TARGET_JACKPOT + 1000000ULL); + const uint64 qrpBalanceBefore = static_cast(getBalance(ctl.qrpSelf())); + + // Create a deterministic prevSpectrumDigest + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x123456789ABCDEF0ULL; // Arbitrary seed + + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Setup: FR active with jackpot below target + const uint64 initialJackpot = 800000000ULL; // 800M QU + ctl.state()->setJackpot(initialJackpot); + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target + ctl.forceFREnabledWithinWindow(10); + // IMPORTANT: internal `state.jackpot` must be backed by actual contract balance, otherwise transfers will fail. + increaseEnergy(ctl.qtfSelf(), initialJackpot); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // User1: Buy ticket with EXACT winning numbers (k=4 winner) + const id k4Winner = id::randomValue(); + ctl.fundAndBuyTicket(k4Winner, ticketPrice, nums.winning); + + // User2: Buy ticket with 3 matching numbers (k=3 winner) + QTFRandomValues k3Numbers = ctl.makeK3Numbers(nums.winning); + const id k3Winner = id::randomValue(); + ctl.fundAndBuyTicket(k3Winner, ticketPrice, k3Numbers); + + // User3: Buy ticket with 2 matching numbers (k=2 winner) + QTFRandomValues k2Numbers = ctl.makeK2Numbers(nums.winning); + const id k2Winner = id::randomValue(); + ctl.fundAndBuyTicket(k2Winner, ticketPrice, k2Numbers); + + // User4: No match + const id loser = id::randomValue(); + QTFRandomValues loserNumbers = ctl.makeValidNumbers(1, 2, 3, 4); + ctl.fundAndBuyTicket(loser, ticketPrice, loserNumbers); + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 4ULL); + + // Verify state before settlement + const uint64 jackpotBefore = ctl.state()->getJackpot(); + const uint64 roundsSinceK4Before = ctl.state()->getFrRoundsSinceK4(); + EXPECT_EQ(jackpotBefore, initialJackpot); + EXPECT_EQ(roundsSinceK4Before, 10u); + + // Trigger settlement using our fixed prevSpectrumDigest + const uint64 k4WinnerBefore = getBalance(k4Winner); + ctl.drawWithDigest(testDigest); + const uint64 k4WinnerAfter = getBalance(k4Winner); + + // Verify k=4 jackpot win behavior: + const uint64 jackpotAfter = ctl.state()->getJackpot(); + EXPECT_GE(jackpotAfter, QTF_DEFAULT_TARGET_JACKPOT) << "Jackpot should be reseeded from QRP after k=4 win"; + EXPECT_LT(static_cast(getBalance(ctl.qrpSelf())), qrpBalanceBefore) << "QRP reserve should decrease due to reseed"; + + // FR counters reset + const uint64 roundsSinceK4After = ctl.state()->getFrRoundsSinceK4(); + EXPECT_EQ(roundsSinceK4After, 0u) << "frRoundsSinceK4 should reset to 0 after k=4 win"; + + const uint64 roundsAtTargetAfter = ctl.state()->getFrRoundsAtOrAboveTarget(); + EXPECT_EQ(roundsAtTargetAfter, 0u) << "frRoundsAtOrAboveTarget should reset to 0 after k=4 win"; + + // 3. Verify winner data contains our winning numbers + QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(0), nums.winning.get(0)); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(1), nums.winning.get(1)); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(2), nums.winning.get(2)); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(3), nums.winning.get(3)); + + // Verify k=4 winner received exact payout (jackpotBefore / countK4). + EXPECT_EQ(static_cast(k4WinnerAfter - k4WinnerBefore), initialJackpot); +} + +TEST(ContractQThirtyFour, DeterministicWinner_K4JackpotWin_MultipleWinners_SplitsEvenly) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + // Ensure QRP has enough reserve to reseed (so settlement completes without relying on carry math). + increaseEnergy(ctl.qrpSelf(), QTF_DEFAULT_TARGET_JACKPOT + 1000000ULL); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xA5A5A5A5A5A5A5A5ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + static constexpr uint64 initialJackpot = 900000000ULL; + ctl.state()->setJackpot(initialJackpot); + ctl.forceFREnabledWithinWindow(1); + increaseEnergy(ctl.qtfSelf(), initialJackpot); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + const id w1 = id::randomValue(); + const id w2 = id::randomValue(); + ctl.fundAndBuyTicket(w1, ticketPrice, nums.winning); + ctl.fundAndBuyTicket(w2, ticketPrice, nums.winning); + + const uint64 w1Before = getBalance(w1); + const uint64 w2Before = getBalance(w2); + + ctl.drawWithDigest(testDigest); + + const uint64 expectedPerWinner = initialJackpot / 2; + EXPECT_EQ(static_cast(getBalance(w1) - w1Before), expectedPerWinner); + EXPECT_EQ(static_cast(getBalance(w2) - w2Before), expectedPerWinner); +} + +TEST(ContractQThirtyFour, WinnerData_WonAmount_MatchesBalanceGain_ForK2K3K4) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x1122334455667788ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + static constexpr uint64 initialJackpot = 500000000ULL; + ctl.state()->setJackpot(initialJackpot); + increaseEnergy(ctl.qtfSelf(), initialJackpot); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + const id k4Winner = id::randomValue(); + const id k3Winner = id::randomValue(); + const id k2Winner = id::randomValue(); + const id loser = id::randomValue(); + + ctl.fundAndBuyTicket(k4Winner, ticketPrice, nums.winning); + ctl.fundAndBuyTicket(k3Winner, ticketPrice, ctl.makeK3Numbers(nums.winning, 0)); + ctl.fundAndBuyTicket(k2Winner, ticketPrice, ctl.makeK2Numbers(nums.winning, 0)); + ctl.fundAndBuyTicket(loser, ticketPrice, nums.losing); + + const uint64 k4Before = getBalance(k4Winner); + const uint64 k3Before = getBalance(k3Winner); + const uint64 k2Before = getBalance(k2Winner); + + ctl.drawWithDigest(testDigest); + + const uint64 k4Gain = static_cast(getBalance(k4Winner) - k4Before); + const uint64 k3Gain = static_cast(getBalance(k3Winner) - k3Before); + const uint64 k2Gain = static_cast(getBalance(k2Winner) - k2Before); + + const QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); + bool foundK4 = false; + bool foundK3 = false; + bool foundK2 = false; + for (uint64 i = 0; i < winnerData.winnerData.winnerCounter; ++i) + { + const QTF::WinnerPlayerData& winnerEntry = winnerData.winnerData.winners.get(i); + if (winnerEntry.player == k4Winner) + { + EXPECT_EQ(winnerEntry.wonAmount, k4Gain); + foundK4 = true; + } + if (winnerEntry.player == k3Winner) + { + EXPECT_EQ(winnerEntry.wonAmount, k3Gain); + foundK3 = true; + } + if (winnerEntry.player == k2Winner) + { + EXPECT_EQ(winnerEntry.wonAmount, k2Gain); + foundK2 = true; + } + } + + EXPECT_TRUE(foundK4); + EXPECT_TRUE(foundK3); + EXPECT_TRUE(foundK2); +} + +TEST(ContractQThirtyFour, DeterministicWinner_K4JackpotWin_ReseedLimitedByQRP) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + ctl.forceFRDisabledForBaseline(); + + // Fund QRP below target so reseed amount is limited by available reserve. + const uint64 qrpFunded = 200000000ULL; + increaseEnergy(ctl.qrpSelf(), qrpFunded); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x0A0B0C0D0E0F1011ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 initialJackpot = 800000000ULL; + ctl.state()->setJackpot(initialJackpot); + increaseEnergy(ctl.qtfSelf(), initialJackpot); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const id w1 = id::randomValue(); + ctl.fundAndBuyTicket(w1, ticketPrice, nums.winning); + + const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); + const uint64 w1Before = getBalance(w1); + + ctl.drawWithDigest(testDigest); + + EXPECT_EQ(static_cast(getBalance(w1) - w1Before), initialJackpot); + + // With a single winning ticket and baseline overflow split, winnersOverflow == winnersBlock, reserveAdd == winnersBlock/2, carryAdd == + // winnersBlock/2. + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 revenue = ticketPrice; + const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; + const uint64 reserveAdd = (winnersBlock * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; + const uint64 carryAdd = winnersBlock - reserveAdd; + + EXPECT_EQ(ctl.state()->getJackpot(), qrpFunded + carryAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore - qrpFunded + reserveAdd); +} + +// Test k=2 and k=3 payouts with deterministic winning numbers +TEST(ContractQThirtyFour, DeterministicWinner_K2K3Payouts_VerifyRevenueSplit) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + // This test validates baseline k2/k3 pool splitting (no FR rake). + // Force FR activation window to be expired so SettleEpoch cannot auto-enable FR. + ctl.forceFRDisabledForBaseline(); + + // Create deterministic prevSpectrumDigest + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xFEDCBA9876543210ULL; // Different seed + + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Create multiple k=2 and k=3 winners to test pool splitting + // 2 k=3 winners + QTFRandomValues k3Numbers1 = ctl.makeK3Numbers(nums.winning, 0); + const id k3Winner1 = id::randomValue(); + ctl.fundAndBuyTicket(k3Winner1, ticketPrice, k3Numbers1); + + QTFRandomValues k3Numbers2 = ctl.makeK3Numbers(nums.winning, 1); + const id k3Winner2 = id::randomValue(); + ctl.fundAndBuyTicket(k3Winner2, ticketPrice, k3Numbers2); + + // 3 k=2 winners + QTFRandomValues k2Numbers1 = ctl.makeK2Numbers(nums.winning, 0); + const id k2Winner1 = id::randomValue(); + ctl.fundAndBuyTicket(k2Winner1, ticketPrice, k2Numbers1); + + QTFRandomValues k2Numbers2 = ctl.makeK2Numbers(nums.winning, 1); + const id k2Winner2 = id::randomValue(); + ctl.fundAndBuyTicket(k2Winner2, ticketPrice, k2Numbers2); + + QTFRandomValues k2Numbers3 = ctl.makeK2Numbers(nums.winning, 2); + const id k2Winner3 = id::randomValue(); + ctl.fundAndBuyTicket(k2Winner3, ticketPrice, k2Numbers3); + + // 5 losers (no matches) + for (int i = 0; i < 5; ++i) + { + const id loser = id::randomValue(); + QTFRandomValues loserNumbers = ctl.makeValidNumbers(1, 2, 3, 4); + ctl.fundAndBuyTicket(loser, ticketPrice, loserNumbers); + } + + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 10ULL); + + // Calculate expected pools + const uint64 revenue = ticketPrice * 10; + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; // 68% + const uint64 expectedK2Pool = (winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000; // 28% of winners block + const uint64 expectedK3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000; // 40% of winners block + + // Get balances before settlement + const uint64 k3Winner1Before = getBalance(k3Winner1); + const uint64 k2Winner1Before = getBalance(k2Winner1); + + // Trigger settlement + ctl.drawWithDigest(testDigest); + + // Verify winner payouts + // k=3 pool split between 2 winners + const uint64 expectedK3PayoutPerWinner = expectedK3Pool / 2; + const uint64 k3Winner1After = getBalance(k3Winner1); + const uint64 k3Winner1Gained = k3Winner1After - k3Winner1Before; + EXPECT_EQ(static_cast(k3Winner1Gained), expectedK3PayoutPerWinner) << "k=3 winner should receive half of k3 pool"; + + // k=2 pool split between 3 winners + const uint64 expectedK2PayoutPerWinner = expectedK2Pool / 3; + const uint64 k2Winner1After = getBalance(k2Winner1); + const uint64 k2Winner1Gained = k2Winner1After - k2Winner1Before; + EXPECT_EQ(static_cast(k2Winner1Gained), expectedK2PayoutPerWinner) << "k=2 winner should receive one-third of k2 pool"; + + // Verify winning numbers in winner data + QTF::GetWinnerData_output winnerData = ctl.getWinnerData(); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(0), nums.winning.get(0)); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(1), nums.winning.get(1)); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(2), nums.winning.get(2)); + EXPECT_EQ(winnerData.winnerData.winnerValues.get(3), nums.winning.get(3)); + + // Jackpot should have grown (no k=4 winner) + EXPECT_GT(ctl.state()->getJackpot(), 0ULL); +} + +TEST(ContractQThirtyFour, EstimatePrizePayouts_FRMode_AppliesRakeToPools) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Enable FR so EstimatePrizePayouts applies the 5% winners rake. + ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT / 2); + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); + ctl.forceFREnabledWithinWindow(1); + + constexpr uint64 numPlayers = 100; + for (uint64 i = 0; i < numPlayers; ++i) + { + const id user = id::randomValue(); + QTFRandomValues nums = ctl.makeValidNumbers(static_cast((i % 26) + 1), static_cast((i % 26) + 2), + static_cast((i % 26) + 3), static_cast((i % 26) + 4)); + ctl.fundAndBuyTicket(user, ticketPrice, nums); + } + + const QTF::EstimatePrizePayouts_output estimate = ctl.estimatePrizePayouts(0, 0); + + const uint64 revenue = ticketPrice * numPlayers; + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; + const uint64 winnersRake = (winnersBlock * QTF_FR_WINNERS_RAKE_BP) / 10000; + const uint64 winnersBlockAfterRake = winnersBlock - winnersRake; + + const uint64 expectedK2Pool = (winnersBlockAfterRake * QTF_BASE_K2_SHARE_BP) / 10000; + const uint64 expectedK3Pool = (winnersBlockAfterRake * QTF_BASE_K3_SHARE_BP) / 10000; + + EXPECT_EQ(estimate.totalRevenue, revenue); + EXPECT_EQ(estimate.k2Pool, expectedK2Pool); + EXPECT_EQ(estimate.k3Pool, expectedK3Pool); +} + +// ============================================================================ +// RESERVE TOP-UP AND FLOOR GUARANTEE TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, Settlement_PerWinnerCap_AppliesToK3Winner_OverflowAccountsForRemainder) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + ctl.forceFRDisabledForBaseline(); + + // Ensure RL shares exist so distribution payouts leave the contract (otherwise most of distPayout can remain in QTF balance). + const id shareholder1 = id::randomValue(); + const id shareholder2 = id::randomValue(); + constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 3; + constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; + std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; + issueRlSharesTo(rlShares); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xD1CEB00BD1CEB00BULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 P = ctl.state()->getTicketPriceInternal(); + const uint64 perWinnerCap = smul(P, QTF_TOPUP_PER_WINNER_CAP_MULT); + + const id k3Winner = id::randomValue(); + ctl.fundAndBuyTicket(k3Winner, P, ctl.makeK3Numbers(nums.winning, 0)); + + constexpr uint64 numLosers = 100; + ctl.buyRandomTickets(numLosers, P, nums.losing); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), numLosers + 1); + + const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); + const uint64 k3Before = getBalance(k3Winner); + + ctl.drawWithDigest(testDigest); + + EXPECT_EQ(static_cast(getBalance(k3Winner) - k3Before), perWinnerCap); + + // Baseline settlement: with no k2 winners and exactly one k3 winner capped at 25*P, + // winnersOverflow ends up being winnersBlock - perWinnerCap. + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 revenue = smul(P, numLosers + 1); + const uint64 winnersBlock = div(smul(revenue, static_cast(fees.winnerFeePercent)), 100); + const uint64 winnersOverflow = winnersBlock - perWinnerCap; + const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; + const uint64 carryAdd = winnersOverflow - reserveAdd; + + EXPECT_EQ(ctl.state()->getJackpot(), carryAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), carryAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore + reserveAdd); +} + +TEST(ContractQThirtyFour, Settlement_FloorTopUp_LimitedBySafetyCaps_PayoutBelowFloor) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + ctl.forceFRDisabledForBaseline(); + + // Ensure RL shares exist so distribution payouts leave the contract (otherwise most of distPayout can remain in QTF balance). + const id shareholder1 = id::randomValue(); + const id shareholder2 = id::randomValue(); + constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 2; + constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; + std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; + issueRlSharesTo(rlShares); + + // Fund QRP just above soft floor so top-up is limited by both 10% cap and soft floor. + const uint64 P = ctl.state()->getTicketPriceInternal(); + const uint64 softFloor = smul(P, QTF_RESERVE_SOFT_FLOOR_MULT); // 20*P + const uint64 qrpFunding = softFloor + 5 * P; // 25*P + increaseEnergy(ctl.qrpSelf(), qrpFunding); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x0DDC0FFEE0DDF00DULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const id k3Winner = id::randomValue(); + ctl.fundAndBuyTicket(k3Winner, P, ctl.makeK3Numbers(nums.winning, 0)); + EXPECT_EQ(ctl.state()->getNumberOfPlayers(), 1u); + + const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); + const uint64 k3Before = getBalance(k3Winner); + + ctl.drawWithDigest(testDigest); + + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 revenue = P; + const uint64 winnersBlock = div(smul(revenue, static_cast(fees.winnerFeePercent)), 100); + const uint64 k3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000; + const uint64 k3Floor = smul(P, QTF_K3_FLOOR_MULT); + const uint64 needed = k3Floor - k3Pool; + const uint64 availableAboveFloor = qrpBefore - softFloor; // 5*P + const uint64 maxPerRound = (qrpBefore * QTF_TOPUP_RESERVE_PCT_BP) / 10000; // 10% of total + const uint64 perWinnerCapTotal = smul(P, QTF_TOPUP_PER_WINNER_CAP_MULT); // 25*P + const uint64 maxAllowed = std::min(std::min(maxPerRound, availableAboveFloor), perWinnerCapTotal); // 2.5*P + const uint64 expectedTopUp = std::min(needed, maxAllowed); + const uint64 expectedPayout = k3Pool + expectedTopUp; + + EXPECT_LT(expectedPayout, k3Floor); + EXPECT_EQ(static_cast(getBalance(k3Winner) - k3Before), expectedPayout); + + // With no k2 winners and k3 pool fully paid (top-ups only increase payouts), + // winnersOverflow equals winnersBlock - k3Pool. + const uint64 winnersOverflow = winnersBlock - k3Pool; + const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; + const uint64 carryAdd = winnersOverflow - reserveAdd; + + EXPECT_EQ(ctl.state()->getJackpot(), carryAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), carryAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore - expectedTopUp + reserveAdd); + EXPECT_GE(static_cast(getBalance(ctl.qrpSelf())), softFloor); +} + +TEST(ContractQThirtyFour, Settlement_FloorTopUp_Integration_K2K3FloorsMetWhenReserveSufficient) +{ + ContractTestingQTF ctl; + ctl.startAnyDayEpoch(); + ctl.forceFRDisabledForBaseline(); + + // Ensure RL shares exist so distribution path is exercised (and rounding/payback is deterministic). + const id shareholder1 = id::randomValue(); + const id shareholder2 = id::randomValue(); + constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 4; + constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; + std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; + issueRlSharesTo(rlShares); + + // Fund QRP enough so both tiers can be topped up to floors under all caps. + const uint64 qrpFunding = 100000000ULL; // 100M, 10% cap = 10M, soft floor = 20M. + increaseEnergy(ctl.qrpSelf(), qrpFunding); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x5566778899AABBCCULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + const uint64 P = ctl.state()->getTicketPriceInternal(); + + // Create deterministic winners: 2x k2 winners and 1x k3 winner => pools are small and must be topped up. + const id k2w1 = id::randomValue(); + const id k2w2 = id::randomValue(); + const id k3w1 = id::randomValue(); + ctl.fundAndBuyTicket(k2w1, P, ctl.makeK2Numbers(nums.winning, 0)); + ctl.fundAndBuyTicket(k2w2, P, ctl.makeK2Numbers(nums.winning, 1)); + ctl.fundAndBuyTicket(k3w1, P, ctl.makeK3Numbers(nums.winning, 2)); + + const uint64 qrpBefore = static_cast(getBalance(ctl.qrpSelf())); + const uint64 qtfBefore = static_cast(getBalance(ctl.qtfSelf())); + const uint64 k2w1Before = getBalance(k2w1); + const uint64 k3w1Before = getBalance(k3w1); + const uint64 sh1Before = getBalance(shareholder1); + const uint64 sh2Before = getBalance(shareholder2); + const uint64 rlBefore = getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)); + + EXPECT_EQ(qtfBefore, 3 * P); + + ctl.drawWithDigest(testDigest); + + // Expected pools and top-ups. + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 revenue = 3 * P; + const uint64 winnersBlock = (revenue * fees.winnerFeePercent) / 100; + const uint64 k2Pool = (winnersBlock * QTF_BASE_K2_SHARE_BP) / 10000; + const uint64 k3Pool = (winnersBlock * QTF_BASE_K3_SHARE_BP) / 10000; + + const uint64 k2Floor = P / 2; + const uint64 k3Floor = 5 * P; + const uint64 k2TopUp = (k2Floor * 2 > k2Pool) ? (k2Floor * 2 - k2Pool) : 0; + const uint64 k3TopUp = (k3Floor > k3Pool) ? (k3Floor - k3Pool) : 0; + + // Winners must receive the floors (no per-winner cap binding in this scenario). + EXPECT_EQ(static_cast(getBalance(k2w1) - k2w1Before), k2Floor); + EXPECT_EQ(static_cast(getBalance(k3w1) - k3w1Before), k3Floor); + + // Baseline overflow is the unallocated 32% of winnersBlock (tier pools are fully paid out with floor top-ups, so no extra overflow). + const uint64 winnersOverflow = winnersBlock - k2Pool - k3Pool; + const uint64 reserveAdd = (winnersOverflow * QTF_BASELINE_OVERFLOW_ALPHA_BP) / 10000; + const uint64 carryAdd = winnersOverflow - reserveAdd; + + // Contract balance should match carry (jackpot) after settlement. + EXPECT_EQ(ctl.state()->getJackpot(), carryAdd); + EXPECT_EQ(static_cast(getBalance(ctl.qtfSelf())), carryAdd); + + // QRP: receives reserveAdd, pays out top-ups. + EXPECT_EQ(static_cast(getBalance(ctl.qrpSelf())), qrpBefore - k2TopUp - k3TopUp + reserveAdd); + + // Distribution: verify two holders and RL payback remainder. + const uint64 expectedDistFee = (revenue * fees.distributionFeePercent) / 100; + const uint64 dividendPerShare = expectedDistFee / NUMBER_OF_COMPUTORS; + const uint64 expectedSh1Gain = static_cast(shares1) * dividendPerShare; + const uint64 expectedSh2Gain = static_cast(shares2) * dividendPerShare; + const uint64 expectedPayback = expectedDistFee - (dividendPerShare * NUMBER_OF_COMPUTORS); + EXPECT_EQ(getBalance(shareholder1), sh1Before + expectedSh1Gain); + EXPECT_EQ(getBalance(shareholder2), sh2Before + expectedSh2Gain); + EXPECT_EQ(getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)), rlBefore + expectedPayback); +} + +// ============================================================================ +// HIGH-DEFICIT FR EXTRA REDIRECTS TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, FR_HighDeficit_ExtraRedirectsCalculated) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + // Fix RNG so we can deterministically avoid winners (and especially k=4). + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x4040404040404040ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Setup: High deficit scenario + // Jackpot = 0, Target = 1B, FR active + ctl.state()->setJackpot(0ULL); // Empty jackpot + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target + ctl.state()->setFrActive(true); + ctl.state()->setFrRoundsSinceK4(5); + + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + const QTF::GetFees_output fees = ctl.getFees(); + + // Add many players to generate high revenue + constexpr int numPlayers = 500; + ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); + + const uint64 revenue = ticketPrice * numPlayers; // 500M QU + const uint64 deficit = QTF_DEFAULT_TARGET_JACKPOT - 0; // 1B deficit + + // With high deficit (1B) and significant revenue (500M), extra redirects should be calculated + // Formula (from spec and QThirtyFour.h:1928-1965): + // - deficit Δ = 1B + // - E_k4(500) ≈ 55 rounds (expected rounds to k=4 with 500 tickets) + // - horizon H = min(55, 50) = 50 (capped) + // - required gain per round = Δ/H = 1B/50 = 20M + // - base gain (without extra) ≈ 1% dev + 1% dist + 5% rake + 95% overflow + // ≈ 5M + 5M + 17M + ~98M = ~125M (rough estimate) + // - Since base gain (125M) > required (20M), extra might be 0 or small + // But let's verify the mechanism is working + + const uint64 devBalBefore = getBalance(ctl.state()->team()); + const uint64 jackpotBefore = ctl.state()->getJackpot(); + EXPECT_EQ(jackpotBefore, 0ULL); + + ctl.drawWithDigest(testDigest); + + // After settlement with FR active and high deficit: + const uint64 devBalAfter = getBalance(ctl.state()->team()); + const uint64 jackpotAfter = ctl.state()->getJackpot(); + + // Verify FR is still active + EXPECT_EQ(ctl.state()->getFrActive(), true); + + // Dev should receive less than full 10% of revenue due to FR redirects + const uint64 fullDevPayout = (revenue * fees.teamFeePercent) / 100; // 50M (10% of 500M) + const uint64 actualDevPayout = devBalAfter - devBalBefore; + + // Base redirect alone is 1% of revenue = 5M + const uint64 baseDevRedirect = (revenue * QTF_FR_DEV_REDIRECT_BP) / 10000; // 5M + EXPECT_LT(actualDevPayout, fullDevPayout) << "Dev should receive less than full 10% in FR mode"; + EXPECT_LE(actualDevPayout, fullDevPayout - baseDevRedirect) << "Dev redirect should be at least base 1%"; + + // Jackpot should have grown significantly from: + // - Winners rake (5% of 340M winners block = 17M) + // - Dev/Dist redirects (base 1% each + possible extra) + // - Overflow bias (95% of overflow) + EXPECT_GT(jackpotAfter, 100000000ULL) << "Jackpot should grow by at least 100M from FR mechanisms"; + + // Verify extra redirect cap: dev redirect should not exceed base (1%) + extra max (0.35%) = 1.35% total + const uint64 maxDevRedirectTotal = (revenue * (QTF_FR_DEV_REDIRECT_BP + QTF_FR_EXTRA_MAX_BP / 2)) / 10000; // 1.35% + const uint64 actualDevRedirect = fullDevPayout - actualDevPayout; + EXPECT_LE(actualDevRedirect, maxDevRedirectTotal) << "Dev redirect should not exceed 1.35% of revenue"; + + // Note: The exact extra redirect amount depends on complex calculation in CalculateExtraRedirectBP + // (QThirtyFour.h:1928-1965), which uses fixed-point arithmetic, power calculations, and horizon capping. + // This test verifies the mechanism is active and within bounds. +} + +TEST(ContractQThirtyFour, Settlement_FRMode_ExtraRedirect_ClampsToMax_AndAffectsDevAndDist) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + // Ensure RL shares exist so distribution can be asserted. + const id shareholder1 = id::randomValue(); + const id shareholder2 = id::randomValue(); + constexpr uint32 shares1 = NUMBER_OF_COMPUTORS / 2; + constexpr uint32 shares2 = NUMBER_OF_COMPUTORS - shares1; + std::vector> rlShares{{shareholder1, shares1}, {shareholder2, shares2}}; + issueRlSharesTo(rlShares); + + // Deterministic no-winner tickets. + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0x7777777777777777ULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Force FR on and create an extreme deficit to guarantee extra redirect clamps to max. + ctl.state()->setJackpot(0ULL); + ctl.state()->setTargetJackpotInternal(1000000000000000ULL); // 1e15 + ctl.state()->setFrActive(true); + ctl.state()->setFrRoundsSinceK4(1); + + ctl.beginEpochWithValidTime(); + + const uint64 P = ctl.state()->getTicketPriceInternal(); + constexpr uint64 numPlayers = 10; + ctl.buyRandomTickets(numPlayers, P, nums.losing); + + const QTF::GetFees_output fees = ctl.getFees(); + const uint64 revenue = P * numPlayers; + + const uint64 devBefore = getBalance(ctl.state()->team()); + const uint64 sh1Before = getBalance(shareholder1); + const uint64 sh2Before = getBalance(shareholder2); + const uint64 rlBefore = getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)); + + // Pre-compute expected extra BP using the same private helpers as the contract. + QpiContextUserFunctionCall qpi(QTF_CONTRACT_INDEX); + primeQpiFunctionContext(qpi); + const auto pools = ctl.state()->callCalculatePrizePools(qpi, revenue, true); + const auto baseGainOut = ctl.state()->callCalculateBaseGain(qpi, revenue, pools.winnersBlock); + const uint64 delta = ctl.state()->getTargetJackpotInternal() - ctl.state()->getJackpot(); + const auto extraOut = ctl.state()->callCalculateExtraRedirectBP(qpi, numPlayers, delta, revenue, baseGainOut.baseGain); + ASSERT_EQ(extraOut.extraBP, QTF_FR_EXTRA_MAX_BP); + + const uint64 devExtraBP = extraOut.extraBP / 2; + const uint64 distExtraBP = extraOut.extraBP - devExtraBP; + const uint64 totalDevRedirectBP = QTF_FR_DEV_REDIRECT_BP + devExtraBP; + const uint64 totalDistRedirectBP = QTF_FR_DIST_REDIRECT_BP + distExtraBP; + + const uint64 fullDevFee = (revenue * fees.teamFeePercent) / 100; + const uint64 fullDistFee = (revenue * fees.distributionFeePercent) / 100; + + const uint64 expectedDevRedirect = (revenue * totalDevRedirectBP) / 10000; + const uint64 expectedDistRedirect = (revenue * totalDistRedirectBP) / 10000; + const uint64 expectedDevPayout = fullDevFee - expectedDevRedirect; + const uint64 expectedDistPayout = fullDistFee - expectedDistRedirect; + + ctl.drawWithDigest(testDigest); + + // Dev payout must match exact base+extra redirect math (no caps expected in this scenario). + EXPECT_EQ(static_cast(getBalance(ctl.state()->team()) - devBefore), expectedDevPayout); + + // Distribution must match expectedDistPayout (dividendPerShare flooring + payback). + const uint64 dividendPerShare = expectedDistPayout / NUMBER_OF_COMPUTORS; + const uint64 expectedSh1Gain = static_cast(shares1) * dividendPerShare; + const uint64 expectedSh2Gain = static_cast(shares2) * dividendPerShare; + const uint64 expectedPayback = expectedDistPayout - (dividendPerShare * NUMBER_OF_COMPUTORS); + EXPECT_EQ(getBalance(shareholder1), sh1Before + expectedSh1Gain); + EXPECT_EQ(getBalance(shareholder2), sh2Before + expectedSh2Gain); + EXPECT_EQ(getBalance(id(RL_CONTRACT_INDEX, 0, 0, 0)), rlBefore + expectedPayback); +} + +// ============================================================================ +// POST-K4 WINDOW EXPIRY TESTS +// ============================================================================ + +TEST(ContractQThirtyFour, FR_PostK4WindowExpiry_DoesNotActivateWhenInactive) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xABCDABCDABCDABCDULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Setup: Jackpot below target, but window expired and FR inactive. + ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT / 2); + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); + ctl.state()->setFrActive(false); + ctl.state()->setFrRoundsSinceK4(QTF_FR_POST_K4_WINDOW_ROUNDS); + + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + constexpr int numPlayers = 10; + ctl.buyRandomTickets(numPlayers, ticketPrice, nums.losing); + + ctl.drawWithDigest(testDigest); + + EXPECT_EQ(ctl.state()->getFrActive(), false); + EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS + 1); +} + +TEST(ContractQThirtyFour, FR_PostK4WindowExpiry_DoesNotReactivateWhenWindowExpired) +{ + ContractTestingQTF ctl; + ctl.forceSchedule(QTF_ANY_DAY_SCHEDULE); + + m256i testDigest = {}; + testDigest.m256i_u64[0] = 0xFACEFEEDFACEFEEDULL; + const auto nums = ctl.computeWinningAndLosing(testDigest); + + // Setup: FR active, jackpot below target, but approaching window expiry + ctl.state()->setJackpot(QTF_DEFAULT_TARGET_JACKPOT / 2); // 500M (below target) + ctl.state()->setTargetJackpotInternal(QTF_DEFAULT_TARGET_JACKPOT); // 1B target + ctl.state()->setFrActive(true); + ctl.state()->setFrRoundsSinceK4(QTF_FR_POST_K4_WINDOW_ROUNDS - 1); // One round before window expiry (50 = QTF_FR_POST_K4_WINDOW_ROUNDS) + + ctl.beginEpochWithValidTime(); + + const uint64 ticketPrice = ctl.state()->getTicketPriceInternal(); + + // Add players + constexpr int numPlayers = 10; + for (int i = 0; i < numPlayers; ++i) + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + + // Verify FR is active before settlement + EXPECT_EQ(ctl.state()->getFrActive(), true); + EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS - 1); + EXPECT_LT(ctl.state()->getJackpot(), ctl.state()->getTargetJackpotInternal()); + + ctl.drawWithDigest(testDigest); + + // After settlement (deterministic: no k=4 win is possible): + // - roundsSinceK4 should increment to 50 + // - Next round starts outside the FR post-k4 window. + + const uint64 roundsSinceK4After = ctl.state()->getFrRoundsSinceK4(); + EXPECT_EQ(roundsSinceK4After, QTF_FR_POST_K4_WINDOW_ROUNDS) << "Counter should increment to 50 after draw"; + + // Run one more round: FR must be OFF because roundsSinceK4 >= 50. + ctl.beginEpochWithValidTime(); + + for (int i = 0; i < numPlayers; ++i) + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + + ctl.drawWithDigest(testDigest); + + // After second round: + // - Jackpot still below target + // - roundsSinceK4 = 51 (>= 50) + // - FR is forced OFF outside the window. + EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS + 1); + EXPECT_EQ(ctl.state()->getFrActive(), false); + + // Run a third round to ensure FR stays OFF while still outside the window. + ctl.beginEpochWithValidTime(); + for (int i = 0; i < numPlayers; ++i) + { + const id user = id::randomValue(); + ctl.fundAndBuyTicket(user, ticketPrice, nums.losing); + } + ctl.drawWithDigest(testDigest); + + EXPECT_EQ(ctl.state()->getFrRoundsSinceK4(), QTF_FR_POST_K4_WINDOW_ROUNDS + 2); + EXPECT_EQ(ctl.state()->getFrActive(), false); +} diff --git a/test/contract_qutil.cpp b/test/contract_qutil.cpp new file mode 100644 index 000000000..18e66b7ea --- /dev/null +++ b/test/contract_qutil.cpp @@ -0,0 +1,1751 @@ +#define NO_UEFI + +#include "contract_testing.h" + +#include + +class ContractTestingQUtil : public ContractTesting { +public: + ContractTestingQUtil() { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QUTIL); + callSystemProcedure(QUTIL_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + + // init fees + callSystemProcedure(QUTIL_CONTRACT_INDEX, INITIALIZE, true); + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(QUTIL_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + callSystemProcedure(QX_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QUTIL_CONTRACT_INDEX, END_EPOCH, expectSuccess); + callSystemProcedure(QX_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + + QX::IssueAsset_output issueAsset(const id& issuer, uint64_t assetName, uint64_t numberOfShares) { + QX::IssueAsset_input input; + input.assetName = assetName; + input.numberOfShares = numberOfShares; + input.unitOfMeasurement = 0; + input.numberOfDecimalPlaces = 0; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, 1000000000); + return output; + } + + QX::TransferShareOwnershipAndPossession_output transferAsset(const id& from, const id& to, const Asset& asset, uint64_t amount) { + QX::TransferShareOwnershipAndPossession_input input; + input.issuer = asset.issuer; + input.newOwnerAndPossessor = to; + input.assetName = asset.assetName; + input.numberOfShares = amount; + QX::TransferShareOwnershipAndPossession_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, from, 1000000); + return output; + } + + QUTIL::CreatePoll_output createPoll(const id& creator, const QUTIL::CreatePoll_input& input, uint64_t fee) { + QUTIL::CreatePoll_output output; + memset(&output, 0, sizeof(output)); + invokeUserProcedure(QUTIL_CONTRACT_INDEX, 4, input, output, creator, fee); + return output; + } + + QUTIL::Vote_output vote(const id& voter, const QUTIL::Vote_input& input, uint64_t fee) { + QUTIL::Vote_output output; + memset(&output, 0, sizeof(output)); + invokeUserProcedure(QUTIL_CONTRACT_INDEX, 5, input, output, voter, fee); + return output; + } + + QUTIL::GetCurrentResult_output getCurrentResult(uint64_t poll_id) { + QUTIL::GetCurrentResult_input input; + input.poll_id = poll_id; + QUTIL::GetCurrentResult_output output; + memset(&output, 0, sizeof(output)); + callFunction(QUTIL_CONTRACT_INDEX, 3, input, output); + return output; + } + + QUTIL::GetPollsByCreator_output getPollsByCreator(const id& creator) { + QUTIL::GetPollsByCreator_input input; + input.creator = creator; + QUTIL::GetPollsByCreator_output output; + memset(&output, 0, sizeof(output)); + callFunction(QUTIL_CONTRACT_INDEX, 4, input, output); + return output; + } + + QUTIL::GetCurrentPollId_output getCurrentPollId() + { + QUTIL::GetCurrentPollId_input input; + QUTIL::GetCurrentPollId_output output; + memset(&output, 0, sizeof(output)); + callFunction(QUTIL_CONTRACT_INDEX, 5, input, output); + return output; + } + + QUTIL::GetPollInfo_output getPollInfo(uint64_t poll_id) + { + QUTIL::GetPollInfo_input input; + input.poll_id = poll_id; + QUTIL::GetPollInfo_output output; + memset(&output, 0, sizeof(output)); + callFunction(QUTIL_CONTRACT_INDEX, 6, input, output); + return output; + } + + QUTIL::DistributeQuToShareholders_output distributeQuToShareholders(const id& invocator, const Asset& asset, sint64 amount) { + QUTIL::DistributeQuToShareholders_input input{ asset }; + QUTIL::DistributeQuToShareholders_output output; + invokeUserProcedure(QUTIL_CONTRACT_INDEX, 7, input, output, invocator, amount); + return output; + } +}; + +// Helper function to generate random ID +id generateRandomId() { + return id::randomValue(); +} + +// Helper function to convert string to Array +Array stringToArray(const std::string& str) { + Array arr; + size_t len = std::min(str.size(), static_cast(256)); + for (size_t i = 0; i < len; ++i) { + arr.set(i, static_cast(str[i])); + } + for (size_t i = len; i < 256; ++i) { + arr.set(i, 0); + } + return arr; +} + +// Helper function to generate a dummy Asset +Asset generateAsset() { + Asset asset; + asset.issuer = generateRandomId(); + asset.assetName = 12345; // Simple name for testing + return asset; +} + +TEST(QUtilTest, CreateMultiplePolls_CheckIds) +{ + ContractTestingQUtil qutil; + id creator = generateRandomId(); + uint64_t num_polls = 16; + std::vector created_poll_ids; + + for (uint64_t i = 0; i < num_polls; ++i) + { + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(i)); + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + created_poll_ids.push_back(output.poll_id); + } + + auto current_poll_info = qutil.getCurrentPollId(); + EXPECT_EQ(current_poll_info.current_poll_id, num_polls); + EXPECT_EQ(current_poll_info.active_count, num_polls); + + std::set expected_ids(created_poll_ids.begin(), created_poll_ids.end()); + std::set active_ids; + for (uint64_t i = 0; i < current_poll_info.active_count; ++i) + { + active_ids.insert(current_poll_info.active_poll_ids.get(i)); + } + EXPECT_EQ(active_ids, expected_ids); +} + +TEST(QUtilTest, CreateMultiplePolls_CheckNames) +{ + ContractTestingQUtil qutil; + id creator = generateRandomId(); + uint64_t num_polls = 5; + std::vector poll_names; + std::vector poll_ids; + + for (uint64_t i = 0; i < num_polls; ++i) + { + id poll_name = generateRandomId(); + poll_names.push_back(poll_name); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(i)); + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + poll_ids.push_back(output.poll_id); + } + + for (uint64_t i = 0; i < num_polls; ++i) + { + auto poll_info = qutil.getPollInfo(poll_ids[i]); + EXPECT_EQ(poll_info.found, 1); + EXPECT_EQ(poll_info.poll_info.poll_name, poll_names[i]); + } +} + +TEST(QUtilTest, CreatePollsMoreThanMax_CheckActiveIds) +{ + ContractTestingQUtil qutil; + id creator = generateRandomId(); + uint64_t num_polls_per_epoch = QUTIL_MAX_NEW_POLL; // 16 polls per epoch + uint64_t num_epochs = 2; + std::vector created_poll_ids; + + for (uint64_t epoch = 0; epoch < num_epochs; ++epoch) + { + for (uint64_t i = 0; i < num_polls_per_epoch; ++i) + { + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(epoch * num_polls_per_epoch + i)); + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + created_poll_ids.push_back(output.poll_id); + } + if (epoch < num_epochs - 1) + { + qutil.endEpoch(); + qutil.beginEpoch(); + } + } + + auto current_poll_info = qutil.getCurrentPollId(); + EXPECT_EQ(current_poll_info.current_poll_id, num_polls_per_epoch * num_epochs); // Total polls created: 32 + EXPECT_EQ(current_poll_info.active_count, num_polls_per_epoch); // Only 16 active in current epoch + + std::set expected_active_ids; + for (uint64_t i = (num_epochs - 1) * num_polls_per_epoch; i < num_epochs * num_polls_per_epoch; ++i) + { + expected_active_ids.insert(i); // IDs 16 to 31 should be active + } + + std::set active_ids; + for (uint64_t i = 0; i < current_poll_info.active_count; ++i) + { + active_ids.insert(current_poll_info.active_poll_ids.get(i)); + } + EXPECT_EQ(active_ids, expected_active_ids); +} + +TEST(QUtilTest, CreatePollsMoreThanMax_CheckPollInfo) +{ + ContractTestingQUtil qutil; + id creator = generateRandomId(); + uint64_t num_polls_per_epoch = QUTIL_MAX_NEW_POLL; // 16 polls per epoch + uint64_t num_epochs = 2; + std::map poll_id_to_name; + + for (uint64_t epoch = 0; epoch < num_epochs; ++epoch) + { + for (uint64_t i = 0; i < num_polls_per_epoch; ++i) + { + id poll_name = generateRandomId(); + uint64_t poll_id = epoch * num_polls_per_epoch + i; + poll_id_to_name[poll_id] = poll_name; + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/test" + std::to_string(poll_id)); + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + EXPECT_EQ(output.poll_id, poll_id); + } + if (epoch < num_epochs - 1) + { + qutil.endEpoch(); + qutil.beginEpoch(); + } + } + + // Check polls from the first epoch (IDs 0-15, should be deactivated) + for (uint64_t i = 0; i < num_polls_per_epoch; ++i) + { + auto poll_info = qutil.getPollInfo(i); + EXPECT_EQ(poll_info.found, 1); // Poll exists but is inactive + EXPECT_EQ(poll_info.poll_info.is_active, 0); + } + + // Check polls from the second epoch (IDs 16-31, should be active) + for (uint64_t i = num_polls_per_epoch; i < num_polls_per_epoch * 2; ++i) + { + auto poll_info = qutil.getPollInfo(i); + EXPECT_EQ(poll_info.found, 1); + EXPECT_EQ(poll_info.poll_info.is_active, 1); + EXPECT_EQ(poll_info.poll_info.poll_name, poll_id_to_name[i]); + } +} + +TEST(QUtilTest, CreatePolls_Vote_PassEpoch_CreateNewPolls_Vote_CheckResults) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + uint64_t min_amount = 1000; + + // Create poll 0 + id poll_name0 = generateRandomId(); + QUTIL::CreatePoll_input create_input0; + create_input0.poll_name = poll_name0; + create_input0.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input0.min_amount = min_amount; + create_input0.github_link = stringToArray("https://github.com/qubic/proposal/poll0"); + create_input0.num_assets = 0; + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output0 = qutil.createPoll(creator, create_input0, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id0 = create_output0.poll_id; + + // Create poll 1 + id poll_name1 = generateRandomId(); + QUTIL::CreatePoll_input create_input1; + create_input1.poll_name = poll_name1; + create_input1.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input1.min_amount = min_amount; + create_input1.github_link = stringToArray("https://github.com/qubic/proposal/poll1"); + create_input1.num_assets = 0; + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output1 = qutil.createPoll(creator, create_input1, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id1 = create_output1.poll_id; + + // Vote on poll 0: 2 votes for option 0, 1 for option 1 + id voter0 = generateRandomId(); + increaseEnergy(voter0, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input0; + vote_input0.poll_id = poll_id0; + vote_input0.address = voter0; + vote_input0.amount = min_amount; + vote_input0.chosen_option = 0; + qutil.vote(voter0, vote_input0, QUTIL_VOTE_FEE); + + id voter1 = generateRandomId(); + increaseEnergy(voter1, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input1; + vote_input1.poll_id = poll_id0; + vote_input1.address = voter1; + vote_input1.amount = min_amount; + vote_input1.chosen_option = 0; + qutil.vote(voter1, vote_input1, QUTIL_VOTE_FEE); + + id voter2 = generateRandomId(); + increaseEnergy(voter2, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input2; + vote_input2.poll_id = poll_id0; + vote_input2.address = voter2; + vote_input2.amount = min_amount; + vote_input2.chosen_option = 1; + qutil.vote(voter2, vote_input2, QUTIL_VOTE_FEE); + + // Vote on poll 1: 1 vote for option 0, 2 for option 1 + id voter3 = generateRandomId(); + increaseEnergy(voter3, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input3; + vote_input3.poll_id = poll_id1; + vote_input3.address = voter3; + vote_input3.amount = min_amount; + vote_input3.chosen_option = 0; + qutil.vote(voter3, vote_input3, QUTIL_VOTE_FEE); + + id voter4 = generateRandomId(); + increaseEnergy(voter4, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input4; + vote_input4.poll_id = poll_id1; + vote_input4.address = voter4; + vote_input4.amount = min_amount; + vote_input4.chosen_option = 1; + qutil.vote(voter4, vote_input4, QUTIL_VOTE_FEE); + + id voter5 = generateRandomId(); + increaseEnergy(voter5, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input5; + vote_input5.poll_id = poll_id1; + vote_input5.address = voter5; + vote_input5.amount = min_amount; + vote_input5.chosen_option = 1; + qutil.vote(voter5, vote_input5, QUTIL_VOTE_FEE); + + // Pass the epoch + qutil.endEpoch(); + + // Create poll 2 + id poll_name2 = generateRandomId(); + QUTIL::CreatePoll_input create_input2; + create_input2.poll_name = poll_name2; + create_input2.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input2.min_amount = min_amount; + create_input2.github_link = stringToArray("https://github.com/qubic/proposal/poll2"); + create_input2.num_assets = 0; + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output2 = qutil.createPoll(creator, create_input2, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id2 = create_output2.poll_id; + + // Create poll 3 + id poll_name3 = generateRandomId(); + QUTIL::CreatePoll_input create_input3; + create_input3.poll_name = poll_name3; + create_input3.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input3.min_amount = min_amount; + create_input3.github_link = stringToArray("https://github.com/qubic/proposal/poll3"); + create_input3.num_assets = 0; + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output3 = qutil.createPoll(creator, create_input3, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id3 = create_output3.poll_id; + + // Vote on poll 2: 1 vote for option 2 + id voter6 = generateRandomId(); + increaseEnergy(voter6, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input6; + vote_input6.poll_id = poll_id2; + vote_input6.address = voter6; + vote_input6.amount = min_amount; + vote_input6.chosen_option = 2; + qutil.vote(voter6, vote_input6, QUTIL_VOTE_FEE); + + // Vote on poll 3: 1 vote for option 3 + id voter7 = generateRandomId(); + increaseEnergy(voter7, min_amount + QUTIL_VOTE_FEE); + QUTIL::Vote_input vote_input7; + vote_input7.poll_id = poll_id3; + vote_input7.address = voter7; + vote_input7.amount = min_amount; + vote_input7.chosen_option = 3; + qutil.vote(voter7, vote_input7, QUTIL_VOTE_FEE); + + // Check results for old polls + auto result0 = qutil.getCurrentResult(poll_id0); + EXPECT_EQ(result0.is_active, 0); + EXPECT_EQ(result0.result.get(0), 2 * min_amount); + EXPECT_EQ(result0.result.get(1), min_amount); + EXPECT_EQ(result0.voter_count.get(0), 2); + EXPECT_EQ(result0.voter_count.get(1), 1); + + auto result1 = qutil.getCurrentResult(poll_id1); + EXPECT_EQ(result1.is_active, 0); + EXPECT_EQ(result1.result.get(0), min_amount); // One vote for option 0 + EXPECT_EQ(result1.result.get(1), 2 * min_amount); // Two votes for option 1 + EXPECT_EQ(result1.voter_count.get(0), 1); + EXPECT_EQ(result1.voter_count.get(1), 2); + + // Check results for new polls + auto result2 = qutil.getCurrentResult(poll_id2); + EXPECT_EQ(result2.is_active, 1); + EXPECT_EQ(result2.result.get(2), min_amount); + EXPECT_EQ(result2.voter_count.get(2), 1); + for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) + { + if (i != 2) + { + EXPECT_EQ(result2.result.get(i), 0); + EXPECT_EQ(result2.voter_count.get(i), 0); + } + } + + auto result3 = qutil.getCurrentResult(poll_id3); + EXPECT_EQ(result3.is_active, 1); + EXPECT_EQ(result3.result.get(3), min_amount); + EXPECT_EQ(result3.voter_count.get(3), 1); + for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) + { + if (i != 3) + { + EXPECT_EQ(result3.result.get(i), 0); + EXPECT_EQ(result3.voter_count.get(i), 0); + } + } +} + +TEST(QUtilTest, VoterListUpdateAndCompaction) { + ContractTestingQUtil qutil; + + id creator = generateRandomId(); + uint64_t min_amount = 1000; + id poll_name = generateRandomId(); + Array github_link = stringToArray("https://github.com/qubic/proposal/test"); + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = poll_type; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id0 = create_output.poll_id; + + id voterA = generateRandomId(); + id voterB = generateRandomId(); + id voterC = generateRandomId(); + id voterD = generateRandomId(); + id voterE = generateRandomId(); + id voterF = generateRandomId(); + id voterG = generateRandomId(); + + // Give each voter enough energy for voting (min_amount + fee) for two polls + increaseEnergy(voterA, min_amount + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterB, min_amount + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterC, min_amount + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterD, min_amount + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterE, min_amount + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterF, min_amount + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterG, min_amount + 2 * QUTIL_VOTE_FEE); + + int voterA_index = spectrumIndex(voterA); + int voterB_index = spectrumIndex(voterB); + int voterC_index = spectrumIndex(voterC); + int voterD_index = spectrumIndex(voterD); + int voterE_index = spectrumIndex(voterE); + int voterF_index = spectrumIndex(voterF); + int voterG_index = spectrumIndex(voterG); + + // Scenario 1: All voters valid for Poll 0 + QUTIL::Vote_input vote_inputA; + vote_inputA.poll_id = poll_id0; + vote_inputA.address = voterA; + vote_inputA.amount = min_amount; + vote_inputA.chosen_option = 0; + EXPECT_TRUE(qutil.vote(voterA, vote_inputA, QUTIL_VOTE_FEE).success); + + EXPECT_EQ(getBalance(voterA), min_amount + QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_inputB; + vote_inputB.poll_id = poll_id0; + vote_inputB.address = voterB; + vote_inputB.amount = min_amount; + vote_inputB.chosen_option = 0; + qutil.vote(voterB, vote_inputB, QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_inputC; + vote_inputC.poll_id = poll_id0; + vote_inputC.address = voterC; + vote_inputC.amount = min_amount; + vote_inputC.chosen_option = 0; + qutil.vote(voterC, vote_inputC, QUTIL_VOTE_FEE); + + auto result = qutil.getCurrentResult(poll_id0); + EXPECT_EQ(result.result.get(0), 3000); // A, B, C: 1000 each + EXPECT_EQ(result.voter_count.get(0), 3); + + auto balance_b = getBalance(voterB); + // Scenario 2: Invalidate voterB by decreasing energy below min_amount + decreaseEnergy(voterB_index, min_amount + QUTIL_VOTE_FEE - 500); // Leave 500, below min_amount + balance_b = getBalance(voterB); + + QUTIL::Vote_input vote_inputD; + vote_inputD.poll_id = poll_id0; + vote_inputD.address = voterD; + vote_inputD.amount = min_amount; + vote_inputD.chosen_option = 0; + qutil.vote(voterD, vote_inputD, QUTIL_VOTE_FEE); + + result = qutil.getCurrentResult(poll_id0); + EXPECT_EQ(result.result.get(0), 3000); // A, C, D: 1000 each, B invalid + EXPECT_EQ(result.voter_count.get(0), 3); + + // Scenario 3: Invalidate voterA and voterC + decreaseEnergy(voterA_index, min_amount + QUTIL_VOTE_FEE - 500); // Leave 500 + decreaseEnergy(voterC_index, min_amount + QUTIL_VOTE_FEE - 500); // Leave 500 + + QUTIL::Vote_input vote_inputE; + vote_inputE.poll_id = poll_id0; + vote_inputE.address = voterE; + vote_inputE.amount = min_amount; + vote_inputE.chosen_option = 0; + qutil.vote(voterE, vote_inputE, QUTIL_VOTE_FEE); + + result = qutil.getCurrentResult(poll_id0); + EXPECT_EQ(result.result.get(0), 2000); // D, E: 1000 each, others invalid + EXPECT_EQ(result.voter_count.get(0), 2); + + // Scenario 4: Single voter with new poll + id poll_name2 = generateRandomId(); + QUTIL::CreatePoll_input create_input2; + create_input2.poll_name = poll_name2; + create_input2.poll_type = poll_type; + create_input2.min_amount = min_amount; + create_input2.github_link = github_link; + create_input2.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output2 = qutil.createPoll(creator, create_input2, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id2 = create_output2.poll_id; + + id voterH = generateRandomId(); + increaseEnergy(voterH, min_amount + QUTIL_VOTE_FEE); + int voterH_index = spectrumIndex(voterH); + QUTIL::Vote_input vote_inputH; + vote_inputH.poll_id = poll_id2; + vote_inputH.address = voterH; + vote_inputH.amount = min_amount; + vote_inputH.chosen_option = 0; + qutil.vote(voterH, vote_inputH, QUTIL_VOTE_FEE); + + result = qutil.getCurrentResult(poll_id2); + EXPECT_EQ(result.result.get(0), 1000); // H: 1000 + EXPECT_EQ(result.voter_count.get(0), 1); + + // Scenario 5: Multiple polls with voter invalidation and new votes + // Create a new poll (Poll 1) + id poll_name1 = generateRandomId(); + QUTIL::CreatePoll_input create_input1; + create_input1.poll_name = poll_name1; + create_input1.poll_type = poll_type; + create_input1.min_amount = min_amount; + create_input1.github_link = github_link; + create_input1.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output1 = qutil.createPoll(creator, create_input1, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id1 = create_output1.poll_id; + + // Voters D, E voted for Poll 0, already did above. Only A, B, C need to revote again. F now votes for Poll 0 + increaseEnergy(voterA, 500 + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterB, 500 + 2 * QUTIL_VOTE_FEE); + increaseEnergy(voterC, 500 + 2 * QUTIL_VOTE_FEE); + + EXPECT_EQ(getBalance(voterA), 1200); + EXPECT_EQ(getBalance(voterB), 1200); + EXPECT_EQ(getBalance(voterC), 1200); + EXPECT_EQ(getBalance(voterD), 1100); + EXPECT_EQ(getBalance(voterE), 1100); + EXPECT_EQ(getBalance(voterF), 1200); + EXPECT_EQ(getBalance(voterG), 1200); + + vote_inputB.poll_id = poll_id0; + qutil.vote(voterB, vote_inputB, QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_inputF; + vote_inputF.poll_id = poll_id0; + vote_inputF.address = voterF; + vote_inputF.amount = min_amount; + vote_inputF.chosen_option = 0; + qutil.vote(voterF, vote_inputF, QUTIL_VOTE_FEE); + + // Add A and C voting for Poll 0 again + vote_inputA.poll_id = poll_id0; + qutil.vote(voterA, vote_inputA, QUTIL_VOTE_FEE); + vote_inputC.poll_id = poll_id0; + qutil.vote(voterC, vote_inputC, QUTIL_VOTE_FEE); + + // Voters A, B, C, D, E, F vote for Poll 1 + vote_inputA.poll_id = poll_id1; + qutil.vote(voterA, vote_inputA, QUTIL_VOTE_FEE); + vote_inputB.poll_id = poll_id1; + qutil.vote(voterB, vote_inputB, QUTIL_VOTE_FEE); + vote_inputC.poll_id = poll_id1; + qutil.vote(voterC, vote_inputC, QUTIL_VOTE_FEE); + vote_inputD.poll_id = poll_id1; + qutil.vote(voterD, vote_inputD, QUTIL_VOTE_FEE); + vote_inputE.poll_id = poll_id1; + qutil.vote(voterE, vote_inputE, QUTIL_VOTE_FEE); + vote_inputF.poll_id = poll_id1; + qutil.vote(voterF, vote_inputF, QUTIL_VOTE_FEE); + + EXPECT_EQ(getBalance(voterA), 1000); + EXPECT_EQ(getBalance(voterB), 1000); + EXPECT_EQ(getBalance(voterC), 1000); + EXPECT_EQ(getBalance(voterD), 1000); + EXPECT_EQ(getBalance(voterE), 1000); + EXPECT_EQ(getBalance(voterF), 1000); + EXPECT_EQ(getBalance(voterG), 1200); + + // Decrease energy for voters B and D below min_amount for both polls + decreaseEnergy(voterB_index, 500); + decreaseEnergy(voterD_index, 500); + + EXPECT_EQ(getBalance(voterA), 1000); + EXPECT_EQ(getBalance(voterB), 500); + EXPECT_EQ(getBalance(voterC), 1000); + EXPECT_EQ(getBalance(voterD), 500); + EXPECT_EQ(getBalance(voterE), 1000); + EXPECT_EQ(getBalance(voterF), 1000); + EXPECT_EQ(getBalance(voterG), 1200); + + // Voter G votes for both Poll 0 and Poll 1 + QUTIL::Vote_input vote_inputG; + vote_inputG.address = voterG; + vote_inputG.amount = min_amount; + vote_inputG.chosen_option = 0; + + vote_inputG.poll_id = poll_id0; + qutil.vote(voterG, vote_inputG, QUTIL_VOTE_FEE); + vote_inputG.poll_id = poll_id1; + qutil.vote(voterG, vote_inputG, QUTIL_VOTE_FEE); + + // Get and verify results for Poll 0 + result = qutil.getCurrentResult(poll_id0); + EXPECT_EQ(result.result.get(0), 5000); // A, C, E, F, G: 1000 each, B and D invalid + EXPECT_EQ(result.voter_count.get(0), 5); + + // Get and verify results for Poll 1 + result = qutil.getCurrentResult(poll_id1); + EXPECT_EQ(result.result.get(0), 5000); // A, C, E, F, G: 1000 each, B and D invalid + EXPECT_EQ(result.voter_count.get(0), 5); +} + +// Test successful Qubic poll creation +TEST(QUtilTest, CreatePoll_Success_Qubic) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + Array allowed_assets; + uint64_t num_assets = 0; + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.allowed_assets = allowed_assets; + input.num_assets = num_assets; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = output.poll_id; + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 1); + EXPECT_EQ(polls.poll_ids.get(0), poll_id); + + auto result = qutil.getCurrentResult(poll_id); + for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) { + EXPECT_EQ(result.result.get(i), 0); // No votes yet + } +} + +// Test successful Asset poll creation +TEST(QUtilTest, CreatePoll_Success_Asset) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + Array allowed_assets; + allowed_assets.set(0, generateAsset()); + uint64_t num_assets = 1; + uint64_t poll_type = QUTIL_POLL_TYPE_ASSET; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.allowed_assets = allowed_assets; + input.num_assets = num_assets; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = output.poll_id; + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 1); + EXPECT_EQ(polls.poll_ids.get(0), poll_id); +} + +// Test CreatePoll failure due to insufficient funds +TEST(QUtilTest, CreatePoll_InsufficientFunds) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE - 1); + qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE - 1); + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 0); +} + +// Test CreatePoll failure due to invalid poll type +TEST(QUtilTest, CreatePoll_InvalidPollType) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = 3; // Invalid type + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 0); +} + +// Test CreatePoll failure due to invalid num_assets for Qubic poll +TEST(QUtilTest, CreatePoll_InvalidNumAssetsQubic) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = QUTIL_POLL_TYPE_QUBIC; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 1; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 0); +} + +// Test CreatePoll failure due to invalid num_assets for Asset poll +TEST(QUtilTest, CreatePoll_InvalidNumAssetsAsset) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = QUTIL_POLL_TYPE_ASSET; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 0); +} + +// Test successful voting +TEST(QUtilTest, Vote_Success) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = poll_type; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + id voter = generateRandomId(); + increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voter; + vote_input.amount = min_amount; + vote_input.chosen_option = 0; + + auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_output.success); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(0), min_amount); + EXPECT_EQ(result.voter_count.get(0), 1); +} + +// Test Vote failure due to insufficient fee +TEST(QUtilTest, Vote_InsufficientFee) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + id voter = generateRandomId(); + increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE - 1); + + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voter; + vote_input.amount = min_amount; + vote_input.chosen_option = 0; + + auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE - 1); + EXPECT_FALSE(vote_output.success); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.voter_count.get(0), 0); +} + +// Test Vote failure due to invalid poll ID +TEST(QUtilTest, Vote_InvalidPollId) { + ContractTestingQUtil qutil; + id voter = generateRandomId(); + increaseEnergy(voter, 1000 + QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_input; + vote_input.poll_id = 999; // Non-existent poll + vote_input.address = voter; + vote_input.amount = 1000; + vote_input.chosen_option = 0; + + auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_output.success); +} + +// Test Vote failure due to insufficient balance +TEST(QUtilTest, Vote_InsufficientBalance) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + id voter = generateRandomId(); + increaseEnergy(voter, min_amount - 1 + QUTIL_VOTE_FEE); // Less than min_amount + + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voter; + vote_input.amount = min_amount; + vote_input.chosen_option = 0; + + auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_output.success); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.voter_count.get(0), 0); +} + +// Test Vote failure due to invalid option +TEST(QUtilTest, Vote_InvalidOption) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + id voter = generateRandomId(); + increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voter; + vote_input.amount = min_amount; + vote_input.chosen_option = QUTIL_MAX_OPTIONS; // 64 is invalid + + auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_output.success); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.voter_count.get(0), 0); +} + +// Test GetCurrentResult success +TEST(QUtilTest, GetCurrentResult_Success) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + id voter = generateRandomId(); + increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voter; + vote_input.amount = min_amount; + vote_input.chosen_option = 1; + + qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(1), min_amount); + EXPECT_EQ(result.voter_count.get(1), 1); +} + +// Test GetCurrentResult failure due to invalid poll ID +TEST(QUtilTest, GetCurrentResult_InvalidPollId) { + ContractTestingQUtil qutil; + auto result = qutil.getCurrentResult(999); // Non-existent poll + for (uint64_t i = 0; i < QUTIL_MAX_OPTIONS; ++i) { + EXPECT_EQ(result.result.get(i), 0); // Should return default values + } +} + +// Test GetPollsByCreator with polls +TEST(QUtilTest, GetPollsByCreator_WithPolls) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name1 = generateRandomId(); + id poll_name2 = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input input; + input.poll_type = QUTIL_POLL_TYPE_QUBIC; + input.min_amount = min_amount; + input.github_link = github_link; + input.num_assets = 0; + + input.poll_name = poll_name1; + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output1 = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + + input.poll_name = poll_name2; + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto output2 = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 2); + EXPECT_TRUE(polls.poll_ids.get(0) == output1.poll_id || polls.poll_ids.get(1) == output1.poll_id); + EXPECT_TRUE(polls.poll_ids.get(0) == output2.poll_id || polls.poll_ids.get(1) == output2.poll_id); +} + +// Test GetPollsByCreator with no polls +TEST(QUtilTest, GetPollsByCreator_NoPolls) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 0); +} + +// Scenario ID 1: Create a poll and have 10 random IDs vote +TEST(QUtilTest, ScenarioID1_CreatePoll_10Voters) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + Array github_link = stringToArray("https://github.com/qubic/proposal/abc"); // Test link, does not exist + + QUTIL::CreatePoll_input create_input; + create_input.poll_name = poll_name; + create_input.poll_type = QUTIL_POLL_TYPE_QUBIC; + create_input.min_amount = min_amount; + create_input.github_link = github_link; + create_input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + std::vector voters; + for (int i = 0; i < 10; ++i) { + id voter = generateRandomId(); + voters.push_back(voter); + increaseEnergy(voter, min_amount + QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voter; + vote_input.amount = min_amount; + vote_input.chosen_option = i % QUTIL_MAX_OPTIONS; // Options 0-9 + + auto vote_output = qutil.vote(voter, vote_input, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_output.success); + } + + auto result = qutil.getCurrentResult(poll_id); + for (int i = 0; i < 10; ++i) { + EXPECT_EQ(result.result.get(i), min_amount); + EXPECT_EQ(result.voter_count.get(i), 1); + } + + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 1); + EXPECT_EQ(polls.poll_ids.get(0), poll_id); +} + +TEST(QUtilTest, CreatePoll_InvalidGithubLink) { + ContractTestingQUtil qutil; + id creator = generateRandomId(); + id poll_name = generateRandomId(); + uint64_t min_amount = 1000; + // Invalid GitHub link (does not start with "https://github.com/") + Array invalid_github_link = stringToArray("https://gitlab.com/invalidlink/proposal/abc"); + uint64_t poll_type = QUTIL_POLL_TYPE_QUBIC; + + QUTIL::CreatePoll_input input; + input.poll_name = poll_name; + input.poll_type = poll_type; + input.min_amount = min_amount; + input.github_link = invalid_github_link; + input.num_assets = 0; + + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + // Attempt to create the poll with invalid GitHub link + auto output = qutil.createPoll(creator, input, QUTIL_POLL_CREATION_FEE); + // Expect poll_id to be 0 indicating failure + EXPECT_EQ(output.poll_id, 0); + + // Verify that no poll was created for the creator + auto polls = qutil.getPollsByCreator(creator); + EXPECT_EQ(polls.count, 0); +} + +// Create an asset poll and have voters with allowed assets vote +TEST(QUtilTest, CreateAssetPoll_VotersWithAssets) +{ + ContractTestingQUtil qutil; + + id creator = generateRandomId(); + id issuer = generateRandomId(); + id voterX = generateRandomId(); + id voterY = generateRandomId(); + id voterZ = generateRandomId(); + id voterW = generateRandomId(); + + increaseEnergy(issuer, 2000000000 + 1000000 * 10); // For issuance and transfers + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + increaseEnergy(voterX, 10000000); // for votes and transfers + increaseEnergy(voterY, 10000000); + increaseEnergy(voterZ, 10000000); + increaseEnergy(voterW, 10000000); + + // Issue assets with valid names + unsigned long long assetNameA = assetNameFromString("ASSETA"); + unsigned long long assetNameB = assetNameFromString("ASSETB"); + Asset assetA = { issuer, assetNameA }; + Asset assetB = { issuer, assetNameB }; + qutil.issueAsset(issuer, assetNameA, 1000000); + qutil.issueAsset(issuer, assetNameB, 1000000); + + // Transfer assets + auto transferred_amount1 = qutil.transferAsset(issuer, voterX, assetA, 2000); + auto transferred_amount2 = qutil.transferAsset(issuer, voterX, assetB, 1500); + auto transferred_amount3 = qutil.transferAsset(issuer, voterY, assetA, 1000); + auto transferred_amount4 = qutil.transferAsset(issuer, voterZ, assetB, 1200); + + // Create asset poll + Array allowed_assets; + allowed_assets.set(0, assetA); + allowed_assets.set(1, assetB); + QUTIL::CreatePoll_input create_input; + create_input.poll_name = generateRandomId(); + create_input.poll_type = QUTIL_POLL_TYPE_ASSET; + create_input.min_amount = 1000; + create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); + create_input.allowed_assets = allowed_assets; + create_input.num_assets = 2; + + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + auto current_poll_id = qutil.getCurrentPollId(); + + // Voters vote + QUTIL::Vote_input vote_inputX; + vote_inputX.poll_id = poll_id; + vote_inputX.address = voterX; + vote_inputX.amount = 2000; // Max of A and B + vote_inputX.chosen_option = 0; + auto vote_outputX = qutil.vote(voterX, vote_inputX, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_outputX.success); + + QUTIL::Vote_input vote_inputY; + vote_inputY.poll_id = poll_id; + vote_inputY.address = voterY; + vote_inputY.amount = 1000; // Holding of A + vote_inputY.chosen_option = 1; + auto vote_outputY = qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_outputY.success); + + QUTIL::Vote_input vote_inputZ; + vote_inputZ.poll_id = poll_id; + vote_inputZ.address = voterZ; + vote_inputZ.amount = 1200; // Holding of B + vote_inputZ.chosen_option = 0; + auto vote_outputZ = qutil.vote(voterZ, vote_inputZ, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_outputZ.success); + + QUTIL::Vote_input vote_inputW; + vote_inputW.poll_id = poll_id; + vote_inputW.address = voterW; + vote_inputW.amount = 1000; // No assets + vote_inputW.chosen_option = 0; + auto vote_outputW = qutil.vote(voterW, vote_inputW, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_outputW.success); + + // Check results + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(0), 2000 + 1200); // VoterX + VoterZ + EXPECT_EQ(result.result.get(1), 1000); // VoterY + EXPECT_EQ(result.voter_count.get(0), 2); + EXPECT_EQ(result.voter_count.get(1), 1); +} + +// Voters with no allowed assets cannot vote +TEST(QUtilTest, VotersNoAllowedAssets) +{ + ContractTestingQUtil qutil; + + id creator = generateRandomId(); + id issuer = generateRandomId(); + id voterW = generateRandomId(); + + increaseEnergy(issuer, 2000000000); + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + increaseEnergy(voterW, QUTIL_VOTE_FEE); + + unsigned long long assetNameA = assetNameFromString("ASSETA"); + Asset assetA = { issuer, assetNameA }; + qutil.issueAsset(issuer, assetNameA, 1000000); + + Array allowed_assets; + allowed_assets.set(0, assetA); + QUTIL::CreatePoll_input create_input; + create_input.poll_name = generateRandomId(); + create_input.poll_type = QUTIL_POLL_TYPE_ASSET; + create_input.min_amount = 1000; + create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); + create_input.allowed_assets = allowed_assets; + create_input.num_assets = 1; + + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + QUTIL::Vote_input vote_inputW; + vote_inputW.poll_id = poll_id; + vote_inputW.address = voterW; + vote_inputW.amount = 1000; + vote_inputW.chosen_option = 0; + auto vote_outputW = qutil.vote(voterW, vote_inputW, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_outputW.success); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(0), 0); + EXPECT_EQ(result.voter_count.get(0), 0); +} + +// Voters with one allowed asset can vote +TEST(QUtilTest, VotersWithOneAllowedAsset) +{ + ContractTestingQUtil qutil; + + id creator = generateRandomId(); + id issuer = generateRandomId(); + id voterY = generateRandomId(); + + increaseEnergy(issuer, 2000000000 + 1000000); + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + increaseEnergy(voterY, QUTIL_VOTE_FEE); + + unsigned long long assetNameA = assetNameFromString("ASSETA"); + unsigned long long assetNameB = assetNameFromString("ASSETB"); + Asset assetA = { issuer, assetNameA }; + Asset assetB = { issuer, assetNameB }; + qutil.issueAsset(issuer, assetNameA, 1000000); + qutil.issueAsset(issuer, assetNameB, 1000000); + qutil.transferAsset(issuer, voterY, assetA, 1000); + + Array allowed_assets; + allowed_assets.set(0, assetA); + allowed_assets.set(1, assetB); + QUTIL::CreatePoll_input create_input; + create_input.poll_name = generateRandomId(); + create_input.poll_type = QUTIL_POLL_TYPE_ASSET; + create_input.min_amount = 1000; + create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); + create_input.allowed_assets = allowed_assets; + create_input.num_assets = 2; + + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + QUTIL::Vote_input vote_inputY; + vote_inputY.poll_id = poll_id; + vote_inputY.address = voterY; + vote_inputY.amount = 1000; + vote_inputY.chosen_option = 1; + auto vote_outputY = qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_outputY.success); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(1), 1000); + EXPECT_EQ(result.voter_count.get(1), 1); +} + +// Decrease voter shares and update voting power +TEST(QUtilTest, DecreaseShares_UpdateVotingPower) +{ + ContractTestingQUtil qutil; + + id creator = generateRandomId(); + id issuer = generateRandomId(); + id voterX = generateRandomId(); + id voterY = generateRandomId(); + + increaseEnergy(issuer, 2000000000 + 1000000 * 10); + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + increaseEnergy(voterX, 10000000); + increaseEnergy(voterY, 10000000); + + unsigned long long assetNameA = assetNameFromString("ASSETA"); + Asset assetA = { issuer, assetNameA }; + qutil.issueAsset(issuer, assetNameA, 1000000); + qutil.transferAsset(issuer, voterX, assetA, 2000); + qutil.transferAsset(issuer, voterY, assetA, 1000); + + Array allowed_assets; + allowed_assets.set(0, assetA); + QUTIL::CreatePoll_input create_input; + create_input.poll_name = generateRandomId(); + create_input.poll_type = QUTIL_POLL_TYPE_ASSET; + create_input.min_amount = 1000; + create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); + create_input.allowed_assets = allowed_assets; + create_input.num_assets = 1; + + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + // Initial votes + QUTIL::Vote_input vote_inputX; + vote_inputX.poll_id = poll_id; + vote_inputX.address = voterX; + vote_inputX.amount = 2000; + vote_inputX.chosen_option = 0; + qutil.vote(voterX, vote_inputX, QUTIL_VOTE_FEE); + + QUTIL::Vote_input vote_inputY; + vote_inputY.poll_id = poll_id; + vote_inputY.address = voterY; + vote_inputY.amount = 1000; + vote_inputY.chosen_option = 1; + qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); + + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(0), 2000); + EXPECT_EQ(result.result.get(1), 1000); + EXPECT_EQ(result.voter_count.get(0), 1); + EXPECT_EQ(result.voter_count.get(1), 1); + + // Decrease voterX's shares below min_amount + qutil.transferAsset(voterX, issuer, assetA, 1900); // Leaves 100 + + // VoterY votes again to trigger update + qutil.vote(voterY, vote_inputY, QUTIL_VOTE_FEE); + + // Check updated results + result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(0), 0); // VoterX removed + EXPECT_EQ(result.result.get(1), 1000); // VoterY remains + EXPECT_EQ(result.voter_count.get(0), 0); + EXPECT_EQ(result.voter_count.get(1), 1); + + // VoterX tries to vote again with insufficient shares + auto vote_outputX = qutil.vote(voterX, vote_inputX, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_outputX.success); +} + +TEST(QUtilTest, MultipleVoters_ShareTransfers_EligibilityTest) +{ + ContractTestingQUtil qutil; + + id creator = generateRandomId(); + id issuer = generateRandomId(); + std::vector voters(10); + for (auto& v : voters) + { + v = generateRandomId(); + } + + increaseEnergy(issuer, 4000000000); // for issuance and transfers + increaseEnergy(creator, QUTIL_POLL_CREATION_FEE); + for (const auto& v : voters) + { + increaseEnergy(v, 10000000); // for votes and transfers + } + + // Issue 3 asset + unsigned long long assetNameA = assetNameFromString("ASSETA"); + unsigned long long assetNameB = assetNameFromString("ASSETB"); + unsigned long long assetNameC = assetNameFromString("ASSETC"); + Asset assetA = { issuer, assetNameA }; + Asset assetB = { issuer, assetNameB }; + Asset assetC = { issuer, assetNameC }; + qutil.issueAsset(issuer, assetNameA, 1000000); + qutil.issueAsset(issuer, assetNameB, 1000000); + qutil.issueAsset(issuer, assetNameC, 1000000); + + // Distribute assets + // Voters 0-2: 2000 A, 500 B, 500 C + for (int i = 0; i < 3; i++) + { + qutil.transferAsset(issuer, voters[i], assetA, 2000); + qutil.transferAsset(issuer, voters[i], assetB, 500); + qutil.transferAsset(issuer, voters[i], assetC, 500); + } + // Voters 3-5: 500 A, 2000 B, 500 C + for (int i = 3; i < 6; i++) + { + qutil.transferAsset(issuer, voters[i], assetA, 500); + qutil.transferAsset(issuer, voters[i], assetB, 2000); + qutil.transferAsset(issuer, voters[i], assetC, 500); + } + // Voters 6-9: 500 A, 500 B, 2000 C + for (int i = 6; i < 10; i++) + { + qutil.transferAsset(issuer, voters[i], assetA, 500); + qutil.transferAsset(issuer, voters[i], assetB, 500); + qutil.transferAsset(issuer, voters[i], assetC, 2000); + } + + // Create asset poll + Array allowed_assets; + allowed_assets.set(0, assetA); + allowed_assets.set(1, assetB); + allowed_assets.set(2, assetC); + QUTIL::CreatePoll_input create_input; + create_input.poll_name = generateRandomId(); + create_input.poll_type = QUTIL_POLL_TYPE_ASSET; + create_input.min_amount = 1000; + create_input.github_link = stringToArray("https://github.com/qubic/proposal/test"); + create_input.allowed_assets = allowed_assets; + create_input.num_assets = 3; + auto create_output = qutil.createPoll(creator, create_input, QUTIL_POLL_CREATION_FEE); + uint64_t poll_id = create_output.poll_id; + + for (int i = 0; i < 10; i++) + { + uint64_t option = (i < 3) ? 0 : (i < 6 ? 1 : 2); + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voters[i]; + vote_input.amount = 2000; // Max holding + vote_input.chosen_option = option; + auto vote_output = qutil.vote(voters[i], vote_input, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_output.success); + } + + // Check initial results + auto result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.is_active, 1); + EXPECT_EQ(result.result.get(0), 3 * 2000); // Voters 0-2 + EXPECT_EQ(result.result.get(1), 3 * 2000); // Voters 3-5 + EXPECT_EQ(result.result.get(2), 4 * 2000); // Voters 6-9 + EXPECT_EQ(result.voter_count.get(0), 3); + EXPECT_EQ(result.voter_count.get(1), 3); + EXPECT_EQ(result.voter_count.get(2), 4); + + // Make 3 voters ineligible + // Voter 0: All assets to 999 + qutil.transferAsset(voters[0], issuer, assetA, 1001); // A: 2000 - 1001 = 999 + qutil.transferAsset(issuer, voters[0], assetB, 499); // B: 500 + 499 = 999 + qutil.transferAsset(issuer, voters[0], assetC, 499); // C: 500 + 499 = 999 + // Voter 3: Reduce B to 999 + qutil.transferAsset(voters[3], issuer, assetB, 1001); // B: 2000 - 1001 = 999 + // Voter 6: Reduce C to 999 + qutil.transferAsset(voters[6], issuer, assetC, 1001); // C: 2000 - 1001 = 999 + + // Trigger voter list to update + QUTIL::Vote_input vote_input1; + vote_input1.poll_id = poll_id; + vote_input1.address = voters[1]; + vote_input1.amount = 2000; + vote_input1.chosen_option = 0; + auto vote_output1 = qutil.vote(voters[1], vote_input1, QUTIL_VOTE_FEE); + EXPECT_TRUE(vote_output1.success); + + // Check updated results + result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.result.get(0), 2 * 2000); // Voters 1,2 + EXPECT_EQ(result.result.get(1), 2 * 2000); // Voters 4,5 + EXPECT_EQ(result.result.get(2), 3 * 2000); // Voters 7-9 + EXPECT_EQ(result.voter_count.get(0), 2); + EXPECT_EQ(result.voter_count.get(1), 2); + EXPECT_EQ(result.voter_count.get(2), 3); + + // Verify ineligible voters cannot vote + for (int i : {0, 3, 6}) + { + QUTIL::Vote_input vote_input; + vote_input.poll_id = poll_id; + vote_input.address = voters[i]; + vote_input.amount = 2000; + vote_input.chosen_option = (i < 3) ? 0 : (i < 6 ? 1 : 2); + auto vote_output = qutil.vote(voters[i], vote_input, QUTIL_VOTE_FEE); + EXPECT_FALSE(vote_output.success); + } + + qutil.endEpoch(); + qutil.beginEpoch(); + + // Check updated results + result = qutil.getCurrentResult(poll_id); + EXPECT_EQ(result.is_active, 0); + EXPECT_EQ(result.result.get(0), 2 * 2000); // Voters 1,2 + EXPECT_EQ(result.result.get(1), 2 * 2000); // Voters 4,5 + EXPECT_EQ(result.result.get(2), 3 * 2000); // Voters 7-9 + EXPECT_EQ(result.voter_count.get(0), 2); + EXPECT_EQ(result.voter_count.get(1), 2); + EXPECT_EQ(result.voter_count.get(2), 3); +} + +TEST(QUtilTest, DistributeQuToShareholders) +{ + ContractTestingQUtil qutil; + + id distributor = generateRandomId(); + id issuer = generateRandomId(); + std::vector shareholder(10); + for (auto& v : shareholder) + { + v = generateRandomId(); + } + + increaseEnergy(issuer, 4000000000); // for issuance and transfers + increaseEnergy(distributor, 10000000000); + + // Issue 3 asset + unsigned long long assetNameA = assetNameFromString("ASSETA"); + unsigned long long assetNameB = assetNameFromString("ASSETB"); + unsigned long long assetNameC = assetNameFromString("ASSETC"); + Asset assetA = { issuer, assetNameA }; + Asset assetB = { issuer, assetNameB }; + Asset assetC = { issuer, assetNameC }; + qutil.issueAsset(issuer, assetNameA, 10); + qutil.issueAsset(issuer, assetNameB, 10000); + qutil.issueAsset(issuer, assetNameC, 10000000); + + // Distribute assets + // shareholder 0-2: 1 A, 500 B, 500 C + for (int i = 0; i < 3; i++) + { + qutil.transferAsset(issuer, shareholder[i], assetA, 1); + qutil.transferAsset(issuer, shareholder[i], assetB, 500); + qutil.transferAsset(issuer, shareholder[i], assetC, 600); + } + // shareholder 3-5: 0 A, 2000 B, 500 C + for (int i = 3; i < 6; i++) + { + qutil.transferAsset(issuer, shareholder[i], assetB, 2000); + qutil.transferAsset(issuer, shareholder[i], assetC, 500); + } + // shareholder 6-9: 1 A, 500 B, 0 C + for (int i = 6; i < 10; i++) + { + qutil.transferAsset(issuer, shareholder[i], assetA, 1); + qutil.transferAsset(issuer, shareholder[i], assetB, 500); + } + + QUTIL::DistributeQuToShareholders_output output; + sint64 distributorBalanceBefore, shareholderBalanceBefore; + + // Error case 1: asset without shareholders + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, { distributor, assetNameA }, 10000000); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore); + EXPECT_EQ(output.shareholders, 0); + EXPECT_EQ(output.totalShares, 0); + EXPECT_EQ(output.amountPerShare, 0); + EXPECT_EQ(output.fees, 0); + + // Error case 2: amount too low to pay fee + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetA, 1); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore); + EXPECT_EQ(output.shareholders, 8); + EXPECT_EQ(output.totalShares, 10); + EXPECT_LE(output.amountPerShare, 0); + EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Error case 3: amount too low to pay 1 QU per share + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetA, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER + 9); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore); + EXPECT_EQ(output.shareholders, 8); + EXPECT_EQ(output.totalShares, 10); + EXPECT_EQ(output.amountPerShare, 0); + EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Success case with assetA + exactly calculated amount + sint64 amountPerShare = 50; + sint64 totalAmount = 10 * amountPerShare + 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetA, totalAmount); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + amountPerShare); + EXPECT_EQ(output.shareholders, 8); + EXPECT_EQ(output.totalShares, 10); + EXPECT_EQ(output.amountPerShare, amountPerShare); + EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Success case with assetA + amount with some QUs that cannot be evenly distributed and are refundet + amountPerShare = 100; + totalAmount = 10 * amountPerShare + 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetA, totalAmount + 7); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + amountPerShare); + EXPECT_EQ(output.shareholders, 8); + EXPECT_EQ(output.totalShares, 10); + EXPECT_EQ(output.amountPerShare, amountPerShare); + EXPECT_EQ(output.fees, 8 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Success case with assetB + exactly calculated amount + amountPerShare = 1000; + totalAmount = 10000 * amountPerShare + 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetB, totalAmount); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 500 * amountPerShare); + EXPECT_EQ(output.shareholders, 11); + EXPECT_EQ(output.totalShares, 10000); + EXPECT_EQ(output.amountPerShare, amountPerShare); + EXPECT_EQ(output.fees, 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Success case with assetB + amount with some QUs that cannot be evenly distributed and are refundet + amountPerShare = 42; + totalAmount = 10000 * amountPerShare + 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetB, totalAmount + 9999); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 500 * amountPerShare); + EXPECT_EQ(output.shareholders, 11); + EXPECT_EQ(output.totalShares, 10000); + EXPECT_EQ(output.amountPerShare, amountPerShare); + EXPECT_EQ(output.fees, 11 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Success case with assetC + exactly calculated amount (fee is minimal) + amountPerShare = 123; + totalAmount = 10000000 * amountPerShare + 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetC, totalAmount); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 600 * amountPerShare); + EXPECT_EQ(output.shareholders, 7); + EXPECT_EQ(output.totalShares, 10000000); + EXPECT_EQ(output.amountPerShare, amountPerShare); + EXPECT_EQ(output.fees, 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); + + // Success case with assetC + non-minimal fee (fee payed too much is donation for running QUTIL -> burned with fee) + amountPerShare = 654; + totalAmount = 10000000 * amountPerShare + 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER; + distributorBalanceBefore = getBalance(distributor); + shareholderBalanceBefore = getBalance(shareholder[0]); + output = qutil.distributeQuToShareholders(distributor, assetC, totalAmount + 123456); + EXPECT_EQ(getBalance(distributor), distributorBalanceBefore - totalAmount); + EXPECT_EQ(getBalance(shareholder[0]), shareholderBalanceBefore + 600 * amountPerShare); + EXPECT_EQ(output.shareholders, 7); + EXPECT_EQ(output.totalShares, 10000000); + EXPECT_EQ(output.amountPerShare, amountPerShare); + EXPECT_EQ(output.fees, 7 * QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDER_FEE_PER_SHAREHOLDER); +} diff --git a/test/contract_qvault.cpp b/test/contract_qvault.cpp new file mode 100644 index 000000000..d73f8e150 --- /dev/null +++ b/test/contract_qvault.cpp @@ -0,0 +1,874 @@ +#define NO_UEFI + +#include +#include + +#include "contract_testing.h" + +static std::mt19937_64 rand64; +static constexpr uint64 QVAULT_QCAP_MAX_HOLDERS = 131072; +static constexpr uint64 QVAULT_ISSUE_ASSET_FEE = 1000000000ull; +static constexpr uint64 QVAULT_TOKEN_TRANSFER_FEE = 1000000ull; +static constexpr uint32 QVAULT_SMALL_AMOUNT_QCAP_TRANSFER = 1000; +static constexpr uint32 QVAULT_BIG_AMOUNT_QCAP_TRANSFER = 1000000; +static constexpr uint64 QVAULT_MAX_REVENUE = 1000000000000ull; +static constexpr uint64 QVAULT_MIN_REVENUE = 100000000000ull; +static const id QVAULT_CONTRACT_ID(QVAULT_CONTRACT_INDEX, 0, 0, 0); +const id QVAULT_QCAP_ISSUER = ID(_Q, _C, _A, _P, _W, _M, _Y, _R, _S, _H, _L, _B, _J, _H, _S, _T, _T, _Z, _Q, _V, _C, _I, _B, _A, _R, _V, _O, _A, _S, _K, _D, _E, _N, _A, _S, _A, _K, _N, _O, _B, _R, _G, _P, _F, _W, _W, _K, _R, _C, _U, _V, _U, _A, _X, _Y, _E); +const id QVAULT_authAddress1 = ID(_T, _K, _U, _W, _W, _S, _N, _B, _A, _E, _G, _W, _J, _H, _Q, _J, _D, _F, _L, _G, _Q, _H, _J, _J, _C, _J, _B, _A, _X, _B, _S, _Q, _M, _Q, _A, _Z, _J, _J, _D, _Y, _X, _E, _P, _B, _V, _B, _B, _L, _I, _Q, _A, _N, _J, _T, _I, _D); +const id QVAULT_authAddress2 = ID(_F, _X, _J, _F, _B, _T, _J, _M, _Y, _F, _J, _H, _P, _B, _X, _C, _D, _Q, _T, _L, _Y, _U, _K, _G, _M, _H, _B, _B, _Z, _A, _A, _F, _T, _I, _C, _W, _U, _K, _R, _B, _M, _E, _K, _Y, _N, _U, _P, _M, _R, _M, _B, _D, _N, _D, _R, _G); +const id QVAULT_authAddress3 = ID(_K, _E, _F, _D, _Z, _T, _Y, _L, _F, _E, _R, _A, _H, _D, _V, _L, _N, _Q, _O, _R, _D, _H, _F, _Q, _I, _B, _S, _B, _Z, _C, _W, _S, _Z, _X, _Z, _F, _F, _A, _N, _O, _T, _F, _A, _H, _W, _M, _O, _V, _G, _T, _R, _Q, _J, _P, _X, _D); +const id QVAULT_reinvestingAddress = ID(_R, _U, _U, _Y, _R, _V, _N, _K, _J, _X, _M, _L, _R, _B, _B, _I, _R, _I, _P, _D, _I, _B, _M, _H, _D, _H, _U, _A, _Z, _B, _Q, _K, _N, _B, _J, _T, _R, _D, _S, _P, _G, _C, _L, _Z, _C, _Q, _W, _A, _K, _C, _F, _Q, _J, _K, _K, _E); +const id QVAULT_adminAddress = ID(_H, _E, _C, _G, _U, _G, _H, _C, _J, _K, _Q, _O, _S, _D, _T, _M, _E, _H, _Q, _Y, _W, _D, _D, _T, _L, _F, _D, _A, _S, _Z, _K, _M, _G, _J, _L, _S, _R, _C, _S, _T, _H, _H, _A, _P, _P, _E, _D, _L, _G, _B, _L, _X, _J, _M, _N, _D); +const id QVAULT_initialBannedAddress1 = ID(_K, _E, _F, _D, _Z, _T, _Y, _L, _F, _E, _R, _A, _H, _D, _V, _L, _N, _Q, _O, _R, _D, _H, _F, _Q, _I, _B, _S, _B, _Z, _C, _W, _S, _Z, _X, _Z, _F, _F, _A, _N, _O, _T, _F, _A, _H, _W, _M, _O, _V, _G, _T, _R, _Q, _J, _P, _X, _D); +const id QVAULT_initialBannedAddress2 = ID(_E, _S, _C, _R, _O, _W, _B, _O, _T, _F, _T, _F, _I, _C, _I, _F, _P, _U, _X, _O, _J, _K, _G, _Q, _P, _Y, _X, _C, _A, _B, _L, _Z, _V, _M, _M, _U, _C, _M, _J, _F, _S, _G, _S, _A, _I, _A, _T, _Y, _I, _N, _V, _T, _Y, _G, _O, _A); + +static unsigned long long random(unsigned long long minValue, unsigned long long maxValue) +{ + if(minValue > maxValue) + { + return 0; + } + return minValue + rand64() % (maxValue - minValue); +} + +static id getUser(unsigned long long i) +{ + return id(i, i / 2 + 4, i + 10, i * 3 + 8); +} + +static std::vector getRandomUsers(unsigned int totalUsers, unsigned int maxNum) +{ + unsigned long long userCount = random(0, maxNum); + std::vector users; + users.reserve(userCount); + for (unsigned int i = 0; i < userCount; ++i) + { + unsigned long long userIdx = random(0, totalUsers - 1); + users.push_back(getUser(userIdx)); + } + return users; +} + +class QVAULTChecker : public QVAULT +{ +public: + void endEpochChecker(uint64 revenue, const std::vector& QCAPHolders) + { + uint64 paymentForShareholders = QPI::div(revenue * shareholderDividend, 1000ULL); + uint64 paymentForQCAPHolders = QPI::div(revenue * QCAPHolderPermille, 1000ULL); + uint64 paymentForReinvest = QPI::div(revenue * reinvestingPermille, 1000ULL); + uint64 amountOfBurn = QPI::div(revenue * burnPermille, 1000ULL); + uint64 paymentForDevelopment = revenue - paymentForShareholders - paymentForQCAPHolders - paymentForReinvest - amountOfBurn; + + if(paymentForReinvest > QVAULT_MAX_REINVEST_AMOUNT) + { + paymentForQCAPHolders += paymentForReinvest - QVAULT_MAX_REINVEST_AMOUNT; + paymentForReinvest = QVAULT_MAX_REINVEST_AMOUNT; + } + + uint64 QCAPCirculatedSupply = QVAULT_QCAP_MAX_SUPPLY; + + for(uint64 i = 0 ; i < QCAPHolders.size(); i++) + { + for(uint64 j = 0 ; j < numberOfBannedAddress; j++) + { + if(QCAPHolders[i] == bannedAddress.get(j)) + { + QCAPCirculatedSupply -= numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, QCAPHolders[i], QCAPHolders[i], QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); + break; + } + } + } + + QCAPCirculatedSupply -= numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, QVAULT_initialBannedAddress1, QVAULT_initialBannedAddress1, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); + QCAPCirculatedSupply -= numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, QVAULT_initialBannedAddress2, QVAULT_initialBannedAddress2, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX); + /* + This for loop will check the revenue distributed to QCAPHolders. + */ + for (const auto& user : QCAPHolders) + { + uint64 j = 0; + for(j = 0 ; j < numberOfBannedAddress; j++) + { + if(user == bannedAddress.get(j)) + { + break; + } + } + if(j != numberOfBannedAddress) + { + continue; + } + EXPECT_EQ(QPI::div(paymentForQCAPHolders, QCAPCirculatedSupply) * numberOfPossessedShares(QVAULT_QCAP_ASSETNAME, QCAP_ISSUER, user, user, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), getBalance(user) - 1); + } + if(paymentForReinvest > QVAULT_MAX_REINVEST_AMOUNT) + { + EXPECT_EQ(QVAULT_MAX_REINVEST_AMOUNT, getBalance(reinvestingAddress)); + } + else + { + EXPECT_EQ(paymentForReinvest, getBalance(reinvestingAddress)); + } + EXPECT_EQ(paymentForDevelopment, getBalance(adminAddress)); + } + + void balanceChecker(const id& user) + { + EXPECT_EQ(getBalance(user), 1); + } + + void submitAuthAddressChecker() + { + EXPECT_EQ(NULL_ID, newAuthAddress1); + EXPECT_EQ(NULL_ID, newAuthAddress2); + EXPECT_EQ(NULL_ID, newAuthAddress3); + } + + void submitAuthAddressWithExactAuthId(const id& newAuthAddress) + { + EXPECT_EQ(newAuthAddress1, newAuthAddress); + EXPECT_EQ(newAuthAddress2, newAuthAddress); + EXPECT_EQ(newAuthAddress3, newAuthAddress); + } + + void changeAuthAddressChecker(uint32 numberOfAuth, const id& newAuthAddress) + { + if(numberOfAuth == 1) + { + EXPECT_EQ(authAddress1, newAuthAddress); + } + else if(numberOfAuth == 2) + { + EXPECT_EQ(authAddress2, newAuthAddress); + } + else + { + EXPECT_EQ(authAddress3, newAuthAddress); + } + } + + void submitDistributionPermilleChecker(uint32 newQCAPHolderPt, uint32 newReinvestingPt, uint32 newDevPt) + { + EXPECT_EQ(newQCAPHolderPt, newQCAPHolderPermille1); + EXPECT_EQ(newQCAPHolderPt, newQCAPHolderPermille2); + EXPECT_EQ(newQCAPHolderPt, newQCAPHolderPermille3); + + EXPECT_EQ(newReinvestingPt, newReinvestingPermille1); + EXPECT_EQ(newReinvestingPt, newReinvestingPermille2); + EXPECT_EQ(newReinvestingPt, newReinvestingPermille3); + + EXPECT_EQ(newDevPt, newDevPermille1); + EXPECT_EQ(newDevPt, newDevPermille2); + EXPECT_EQ(newDevPt, newDevPermille3); + } + + void changeDistributionPermilleChecker(uint32 newQCAPHolderPt, uint32 newReinvestingPt, uint32 newDevPt) + { + EXPECT_EQ(newQCAPHolderPt, QCAPHolderPermille); + EXPECT_EQ(newReinvestingPt, reinvestingPermille); + EXPECT_EQ(newDevPt, devPermille); + } + + void submitReinvestingAddressChecker(const id& newReinvestingAddress) + { + EXPECT_EQ(newReinvestingAddress1, newReinvestingAddress); + EXPECT_EQ(newReinvestingAddress2, newReinvestingAddress); + EXPECT_EQ(newReinvestingAddress3, newReinvestingAddress); + } + + void changeReinvestingAddressChecker(const id& newReinvestingAddress) + { + EXPECT_EQ(reinvestingAddress, newReinvestingAddress); + } + + void submitAdminAddressChecker(const id& newAdminAddress) + { + EXPECT_EQ(newAdminAddress1, newAdminAddress); + EXPECT_EQ(newAdminAddress2, newAdminAddress); + EXPECT_EQ(newAdminAddress3, newAdminAddress); + } + + void changeAdminAddressChecker(const id& newAdminAddress) + { + EXPECT_EQ(adminAddress, newAdminAddress); + } + + void submitBannedAddressChecker(const id& newBannedAddress) + { + EXPECT_EQ(bannedAddress1, newBannedAddress); + EXPECT_EQ(bannedAddress2, newBannedAddress); + EXPECT_EQ(bannedAddress3, newBannedAddress); + } + + void saveBannedAddressChecker(const id& newBannedAddress) + { + EXPECT_EQ(bannedAddress.get(numberOfBannedAddress - 1), newBannedAddress); + } + + void submitUnbannedAddressChecker(const id& newUnbannedAddress) + { + EXPECT_EQ(unbannedAddress1, newUnbannedAddress); + EXPECT_EQ(unbannedAddress2, newUnbannedAddress); + EXPECT_EQ(unbannedAddress3, newUnbannedAddress); + } + + void saveUnbannedAddressChecker(const id& unbannedAddress) + { + for(uint32 i = 0 ; i < numberOfBannedAddress; i++) + { + EXPECT_NE(unbannedAddress, bannedAddress.get(i)); + } + } + + void getDataChecker(const getData_output& output) + { + EXPECT_EQ(output.authAddress1, authAddress1); + EXPECT_EQ(output.authAddress2, authAddress2); + EXPECT_EQ(output.authAddress3, authAddress3); + EXPECT_EQ(output.reinvestingAddress, reinvestingAddress); + EXPECT_EQ(output.shareholderDividend, shareholderDividend); + EXPECT_EQ(output.devPermille, devPermille); + EXPECT_EQ(output.QCAPHolderPermille, QCAPHolderPermille); + EXPECT_EQ(output.reinvestingPermille, reinvestingPermille); + EXPECT_EQ(output.adminAddress, adminAddress); + EXPECT_EQ(output.newAuthAddress1, newAuthAddress1); + EXPECT_EQ(output.newAuthAddress2, newAuthAddress2); + EXPECT_EQ(output.newAuthAddress3, newAuthAddress3); + EXPECT_EQ(output.newAdminAddress1, newAdminAddress1); + EXPECT_EQ(output.newAdminAddress2, newAdminAddress2); + EXPECT_EQ(output.newAdminAddress3, newAdminAddress3); + EXPECT_EQ(output.newReinvestingAddress1, newReinvestingAddress1); + EXPECT_EQ(output.newReinvestingAddress2, newReinvestingAddress2); + EXPECT_EQ(output.newReinvestingAddress3, newReinvestingAddress3); + EXPECT_EQ(output.numberOfBannedAddress, numberOfBannedAddress); + EXPECT_EQ(output.bannedAddress1, bannedAddress1); + EXPECT_EQ(output.bannedAddress2, bannedAddress2); + EXPECT_EQ(output.bannedAddress3, bannedAddress3); + EXPECT_EQ(output.unbannedAddress1, unbannedAddress1); + EXPECT_EQ(output.unbannedAddress2, unbannedAddress2); + EXPECT_EQ(output.unbannedAddress3, unbannedAddress3); + } +}; + +class ContractTestingQvault : protected ContractTesting +{ +public: + ContractTestingQvault() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QVAULT); + callSystemProcedure(QVAULT_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + QVAULTChecker* getState() + { + return (QVAULTChecker*)contractStates[QVAULT_CONTRACT_INDEX]; + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(QVAULT_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + QVAULT::getData_output getData() const + { + QVAULT::getData_input input; + QVAULT::getData_output output; + + callFunction(QVAULT_CONTRACT_INDEX, 1, input, output); + return output; + } + + void submitAuthAddress(const id& authAddress, const id& newAuthAddress) + { + QVAULT::submitAuthAddress_input input; + QVAULT::submitAuthAddress_output output; + + input.newAddress = newAuthAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 1, input, output, authAddress, 0); + } + + void changeAuthAddress(const id& authAddress, uint32 numberOfChangedAddress) + { + QVAULT::changeAuthAddress_input input{numberOfChangedAddress}; + QVAULT::changeAuthAddress_output output; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 2, input, output, authAddress, 0); + } + + void submitDistributionPermille(const id& authAddress, uint32 newQCAPHolderPermille, uint32 newReinvestingPermille, uint32 newDevPermille) + { + QVAULT::submitDistributionPermille_input input; + QVAULT::submitDistributionPermille_output output; + + input.newDevPermille = newDevPermille; + input.newQCAPHolderPermille = newQCAPHolderPermille; + input.newReinvestingPermille = newReinvestingPermille; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 3, input, output, authAddress, 0); + } + + void changeDistributionPermille(const id& authAddress, uint32 newQCAPHolderPermille, uint32 newReinvestingPermille, uint32 newDevPermille) + { + QVAULT::changeDistributionPermille_input input; + QVAULT::changeDistributionPermille_output output; + + input.newDevPermille = newDevPermille; + input.newQCAPHolderPermille = newQCAPHolderPermille; + input.newReinvestingPermille = newReinvestingPermille; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 4, input, output, authAddress, 0); + } + + void submitReinvestingAddress(const id& authAddress, const id& newReinvestingAddress) + { + QVAULT::submitReinvestingAddress_input input; + QVAULT::submitReinvestingAddress_output output; + + input.newAddress = newReinvestingAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 5, input, output, authAddress, 0); + } + + void changeReinvestingAddress(const id& authAddress, const id& newReinvestingAddress) + { + QVAULT::changeReinvestingAddress_input input; + QVAULT::changeReinvestingAddress_output output; + + input.newAddress = newReinvestingAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 6, input, output, authAddress, 0); + } + + void submitAdminAddress(const id& authAddress, const id& newAdminAddress) + { + QVAULT::submitAdminAddress_input input; + QVAULT::submitAdminAddress_output output; + + input.newAddress = newAdminAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 7, input, output, authAddress, 0); + } + + void changeAdminAddress(const id& authAddress, const id& newAdminAddress) + { + QVAULT::changeAdminAddress_input input; + QVAULT::changeAdminAddress_output output; + + input.newAddress = newAdminAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 8, input, output, authAddress, 0); + } + + void submitBannedAddress(const id& authAddress, const id& bannedAddress) + { + QVAULT::submitBannedAddress_input input; + QVAULT::submitBannedAddress_output output; + + input.bannedAddress = bannedAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 9, input, output, authAddress, 0); + } + + void saveBannedAddress(const id& authAddress, const id& bannedAddress) + { + QVAULT::saveBannedAddress_input input; + QVAULT::saveBannedAddress_output output; + + input.bannedAddress = bannedAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 10, input, output, authAddress, 0); + } + + void submitUnbannedAddress(const id& authAddress, const id& unbannedAddress) + { + QVAULT::submitUnbannedAddress_input input; + QVAULT::submitUnbannedAddress_output output; + + input.unbannedAddress = unbannedAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 11, input, output, authAddress, 0); + } + + void saveUnbannedAddress(const id& authAddress, const id& unbannedAddress) + { + QVAULT::unblockBannedAddress_input input; + QVAULT::unblockBannedAddress_output output; + + input.unbannedAddress = unbannedAddress; + + invokeUserProcedure(QVAULT_CONTRACT_INDEX, 12, input, output, authAddress, 0); + } + + sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) + { + QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QVAULT_ISSUE_ASSET_FEE); + return output.issuedNumberOfShares; + } + + sint64 TransferShareOwnershipAndPossession(const id& issuer, uint64 assetName, sint64 numberOfShares, id newOwnerAndPossesor) + { + QX::TransferShareOwnershipAndPossession_input input; + QX::TransferShareOwnershipAndPossession_output output; + + input.assetName = assetName; + input.issuer = issuer; + input.newOwnerAndPossessor = newOwnerAndPossesor; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, issuer, QVAULT_TOKEN_TRANSFER_FEE); + + return output.transferredNumberOfShares; + } +}; + +TEST(ContractQvault, END_EPOCH) +{ + ContractTestingQvault qvault; + + id issuer = QVAULT_QCAP_ISSUER; + uint64 assetName = assetNameFromString("QCAP"); + sint64 numberOfShares = QVAULT_QCAP_MAX_SUPPLY; + + + increaseEnergy(issuer, QVAULT_ISSUE_ASSET_FEE); + EXPECT_EQ(qvault.issueAsset(issuer, assetName, numberOfShares, 0, 0), numberOfShares); + + uint64 currentAmount = QVAULT_QCAP_MAX_SUPPLY; + uint64 numberOfHolder = 0; + bool flag = 0; + auto QCAPHolders = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + std::map transferChecker; + + /* + sending the QCAP token to bannedAddresses + */ + increaseEnergy(issuer, QVAULT_TOKEN_TRANSFER_FEE); + increaseEnergy(QVAULT_initialBannedAddress1, 1); + currentAmount -= qvault.TransferShareOwnershipAndPossession(issuer, assetName, random(0, QVAULT_BIG_AMOUNT_QCAP_TRANSFER), QVAULT_initialBannedAddress1); + + increaseEnergy(issuer, QVAULT_TOKEN_TRANSFER_FEE); + increaseEnergy(QVAULT_initialBannedAddress2, 1); + currentAmount -= qvault.TransferShareOwnershipAndPossession(issuer, assetName, random(0, QVAULT_BIG_AMOUNT_QCAP_TRANSFER), QVAULT_initialBannedAddress2); + /* + this while statement will distribute the QCAP token to holders. + */ + while(1) + { + uint64 amountOfQCAPTransfer; + if(flag) + { + amountOfQCAPTransfer = random(0, QVAULT_BIG_AMOUNT_QCAP_TRANSFER); + } + else + { + amountOfQCAPTransfer = random(0, QVAULT_SMALL_AMOUNT_QCAP_TRANSFER); + } + if(currentAmount < amountOfQCAPTransfer || numberOfHolder == QCAPHolders.size()) + { + break; + } + + if(transferChecker[QCAPHolders[numberOfHolder]]) + { + QCAPHolders.erase(QCAPHolders.begin() + numberOfHolder); + continue; + } + transferChecker[QCAPHolders[numberOfHolder]] = 1; + currentAmount -= amountOfQCAPTransfer; + increaseEnergy(issuer, QVAULT_TOKEN_TRANSFER_FEE); + increaseEnergy(QCAPHolders[numberOfHolder], 1); + qvault.getState()->balanceChecker(QCAPHolders[numberOfHolder]); + EXPECT_EQ(qvault.TransferShareOwnershipAndPossession(issuer, assetName, amountOfQCAPTransfer, QCAPHolders[numberOfHolder]), amountOfQCAPTransfer); + numberOfHolder++; + } + + uint64 revenue = random(QVAULT_MIN_REVENUE, QVAULT_MAX_REVENUE); + increaseEnergy(QVAULT_CONTRACT_ID, revenue); + qvault.endEpoch(); + qvault.getState()->endEpochChecker(revenue, QCAPHolders); +} + +TEST(ContractQvault, submitAuthAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + for (const auto& user : randomAddresses) + { + // make sure that user exists in spectrum + increaseEnergy(user, 1); + + // checking to change the auth address using the non-authAddress + qvault.submitAuthAddress(user, user); + qvault.getState()->submitAuthAddressChecker(); + } + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the auth address using the exact authAddresss + qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.getState()->submitAuthAddressWithExactAuthId(randomAddresses[0]); +} + +TEST(ContractQvault, changeAuthAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the authAddress3 with exact process + qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.changeAuthAddress(QVAULT_authAddress1, 3); + qvault.getState()->changeAuthAddressChecker(3, randomAddresses[0]); + + qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[1]); + qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.changeAuthAddress(QVAULT_authAddress2, 3); + qvault.getState()->changeAuthAddressChecker(3, randomAddresses[1]); + + qvault.submitAuthAddress(QVAULT_authAddress1, QVAULT_authAddress3); + qvault.submitAuthAddress(QVAULT_authAddress2, QVAULT_authAddress3); + qvault.changeAuthAddress(QVAULT_authAddress2, 3); + qvault.getState()->changeAuthAddressChecker(3, QVAULT_authAddress3); + + // checking to change the authAddress2 with exact process + qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.changeAuthAddress(QVAULT_authAddress1, 2); + qvault.getState()->changeAuthAddressChecker(2, randomAddresses[0]); + + qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[1]); + qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[1]); + qvault.changeAuthAddress(QVAULT_authAddress3, 2); + qvault.getState()->changeAuthAddressChecker(2, randomAddresses[1]); + + qvault.submitAuthAddress(QVAULT_authAddress1, QVAULT_authAddress2); + qvault.submitAuthAddress(QVAULT_authAddress3, QVAULT_authAddress2); + qvault.changeAuthAddress(QVAULT_authAddress3, 2); + qvault.getState()->changeAuthAddressChecker(2, QVAULT_authAddress2); + + // checking to change the authAddress1 with exact process + qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.changeAuthAddress(QVAULT_authAddress2, 1); + qvault.getState()->changeAuthAddressChecker(1, randomAddresses[0]); + + qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[1]); + qvault.changeAuthAddress(QVAULT_authAddress3, 1); + qvault.getState()->changeAuthAddressChecker(1, randomAddresses[1]); + + qvault.submitAuthAddress(QVAULT_authAddress2, QVAULT_authAddress1); + qvault.submitAuthAddress(QVAULT_authAddress3, QVAULT_authAddress1); + qvault.changeAuthAddress(QVAULT_authAddress3, 1); + qvault.getState()->changeAuthAddressChecker(1, QVAULT_authAddress1); +} + +TEST(ContractQvault, submitDistributionPermille) +{ + ContractTestingQvault qvault; + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the Permille + qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); + qvault.getState()->submitDistributionPermilleChecker(500, 400, 70); +} + +TEST(ContractQvault, changeDistributionPermille) +{ + ContractTestingQvault qvault; + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the Permille + qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); + qvault.changeDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.getState()->changeDistributionPermilleChecker(500, 400, 70); + + qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); + qvault.changeDistributionPermille(QVAULT_authAddress2, 500, 400, 70); + qvault.getState()->changeDistributionPermilleChecker(500, 400, 70); + + qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); + qvault.changeDistributionPermille(QVAULT_authAddress3, 500, 400, 70); + qvault.getState()->changeDistributionPermilleChecker(500, 400, 70); +} + +TEST(ContractQvault, submitReinvestingAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the reinvestingAddress + qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.getState()->submitReinvestingAddressChecker(randomAddresses[0]); +} + +TEST(ContractQvault, changeReinvestingAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the reinvestingAddress + qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.changeReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.getState()->changeReinvestingAddressChecker(randomAddresses[0]); + + qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[1]); + qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[1]); + qvault.changeReinvestingAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.getState()->changeReinvestingAddressChecker(randomAddresses[1]); + + qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[2]); + qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[2]); + qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[2]); + qvault.changeReinvestingAddress(QVAULT_authAddress3, randomAddresses[2]); + qvault.getState()->changeReinvestingAddressChecker(randomAddresses[2]); +} + +TEST(ContractQvault, submitAdminAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the adminAddress + qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.getState()->submitAdminAddressChecker(randomAddresses[0]); +} + +TEST(ContractQvault, changeAdminAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to change the adminAddress + qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.changeAdminAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.getState()->changeAdminAddressChecker(randomAddresses[0]); + + qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[1]); + qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[1]); + qvault.changeAdminAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.getState()->changeAdminAddressChecker(randomAddresses[1]); + + qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[2]); + qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[2]); + qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[2]); + qvault.changeAdminAddress(QVAULT_authAddress3, randomAddresses[2]); + qvault.getState()->changeAdminAddressChecker(randomAddresses[2]); +} + +TEST(ContractQvault, submitBannedAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to submit the bannedAddress + qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.getState()->submitBannedAddressChecker(randomAddresses[0]); +} + +TEST(ContractQvault, saveBannedAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to save the bannedAddress + qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.saveBannedAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.getState()->saveBannedAddressChecker(randomAddresses[0]); + + qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[1]); + qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[1]); + qvault.saveBannedAddress(QVAULT_authAddress2, randomAddresses[1]); + qvault.getState()->saveBannedAddressChecker(randomAddresses[1]); + + qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[2]); + qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[2]); + qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[2]); + qvault.saveBannedAddress(QVAULT_authAddress3, randomAddresses[2]); + qvault.getState()->saveBannedAddressChecker(randomAddresses[2]); +} + +TEST(ContractQvault, submitUnbannedAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + // checking to submit unbannedAddress + qvault.submitUnbannedAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitUnbannedAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitUnbannedAddress(QVAULT_authAddress3, randomAddresses[0]); + qvault.getState()->submitUnbannedAddressChecker(randomAddresses[0]); +} + +TEST(ContractQvault, unblockBannedAddress) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + for(uint32 i = 0 ; i < 10; i++) + { + qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[i]); + qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[i]); + qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[i]); + qvault.saveBannedAddress(QVAULT_authAddress1, randomAddresses[i]); + } + + // checking to unblock the bannedAddress + for(uint32 i = 0 ; i < 10; i++) + { + qvault.submitUnbannedAddress(QVAULT_authAddress1, randomAddresses[i]); + qvault.submitUnbannedAddress(QVAULT_authAddress2, randomAddresses[i]); + qvault.submitUnbannedAddress(QVAULT_authAddress3, randomAddresses[i]); + qvault.saveUnbannedAddress(QVAULT_authAddress1, randomAddresses[i]); + qvault.getState()->saveUnbannedAddressChecker(randomAddresses[i]); + } +} + +TEST(ContractQvault, getData) +{ + ContractTestingQvault qvault; + + auto randomAddresses = getRandomUsers(QVAULT_QCAP_MAX_HOLDERS, QVAULT_QCAP_MAX_HOLDERS); + + // make sure that user exists in spectrum + increaseEnergy(QVAULT_authAddress1, 1); + increaseEnergy(QVAULT_authAddress2, 1); + increaseEnergy(QVAULT_authAddress3, 1); + + qvault.submitAuthAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitAuthAddress(QVAULT_authAddress3, randomAddresses[0]); + + qvault.submitDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress2, 500, 400, 70); + qvault.submitDistributionPermille(QVAULT_authAddress3, 500, 400, 70); + + qvault.submitReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitReinvestingAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitReinvestingAddress(QVAULT_authAddress3, randomAddresses[0]); + + qvault.submitAdminAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitAdminAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitAdminAddress(QVAULT_authAddress3, randomAddresses[0]); + + qvault.submitBannedAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitBannedAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitBannedAddress(QVAULT_authAddress3, randomAddresses[0]); + + qvault.submitUnbannedAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.submitUnbannedAddress(QVAULT_authAddress2, randomAddresses[0]); + qvault.submitUnbannedAddress(QVAULT_authAddress3, randomAddresses[0]); + + auto output = qvault.getData(); + qvault.getState()->getDataChecker(output); + + qvault.changeAuthAddress(QVAULT_authAddress1, 3); + qvault.changeDistributionPermille(QVAULT_authAddress1, 500, 400, 70); + qvault.changeReinvestingAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.changeAdminAddress(QVAULT_authAddress1, randomAddresses[0]); + qvault.saveBannedAddress(QVAULT_authAddress1, randomAddresses[0]); + + output = qvault.getData(); + qvault.getState()->getDataChecker(output); +} \ No newline at end of file diff --git a/test/contract_qx.cpp b/test/contract_qx.cpp new file mode 100644 index 000000000..d1fe0e827 --- /dev/null +++ b/test/contract_qx.cpp @@ -0,0 +1,253 @@ +#define NO_UEFI + +#include "contract_testing.h" + +#define PRINT_DETAILS 0 + +static constexpr uint64 QX_ISSUE_ASSET_FEE = 1000000000ull; + +std::string assetNameFromInt64(uint64 assetName); + +class QxChecker : public QX +{ +public: + struct Order + { + id issuer; + uint64 assetName; + id entity; + sint64 price; + sint64 numberOfShares; + + bool operator<(const Order& other) const + { + return memcmp(this, &other, sizeof(other)) < 0; + } + }; + + void checkCollectionConsistency() + { + EXPECT_EQ(_entityOrders.population(), _assetOrders.population()); + + std::set entityOrders; + std::map entityCounter; + for (uint64 i = 0; i < _entityOrders.capacity(); ++i) + { + QX::_EntityOrder order = _entityOrders.element(i); + if (!order.numberOfShares) + continue; + Order o; + o.issuer = order.issuer; + o.assetName = order.assetName; + o.entity = _entityOrders.pov(i); + o.price = _entityOrders.priority(i); + o.numberOfShares = order.numberOfShares; + entityOrders.insert(o); + + ++entityCounter[o.entity]; + } + + for (const auto& p : entityCounter) + { + EXPECT_EQ(_entityOrders.population(p.first), p.second); + } + + std::set assetOrders; + for (uint64 i = 0; i < _assetOrders.capacity(); ++i) + { + QX::_AssetOrder order = _assetOrders.element(i); + if (!order.numberOfShares) + continue; + Order o; + id pov = _assetOrders.pov(i); + o.issuer = id(pov.u64._0, pov.u64._1, pov.u64._2, 0); + o.assetName = pov.u64._3; + o.entity = order.entity; + o.price = _assetOrders.priority(i); + o.numberOfShares = order.numberOfShares; + assetOrders.insert(o); + } + + EXPECT_EQ(entityOrders.size(), assetOrders.size()); + auto it1 = entityOrders.begin(), it2 = assetOrders.begin(); + while (it1 != entityOrders.end()) + { + // issuer cannot be fully obtained from assetOrder (4th element missing) + EXPECT_EQ(it1->issuer.u64._0, it2->issuer.u64._0); + EXPECT_EQ(it1->issuer.u64._1, it2->issuer.u64._1); + EXPECT_EQ(it1->issuer.u64._2, it2->issuer.u64._2); + EXPECT_EQ(it1->assetName, it2->assetName); + EXPECT_EQ(it1->entity, it2->entity); + EXPECT_EQ(it1->price, it2->price); + EXPECT_EQ(it1->numberOfShares, it2->numberOfShares); + ++it1; ++it2; + } + } + + void cleanupCollections() + { + constexpr bool forceCleanup = false; + const auto population = _entityOrders.population(); + checkCollectionConsistency(); + if (forceCleanup) + { + _entityOrders.cleanup(); + _assetOrders.cleanup(); + } + else + { + _entityOrders.cleanupIfNeeded(30); + _assetOrders.cleanupIfNeeded(30); + } + checkCollectionConsistency(); + EXPECT_EQ(population, _entityOrders.population()); + } +}; + +class ContractTestingQx : protected ContractTesting +{ +public: + ContractTestingQx() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + } + + QxChecker* getState() + { + return (QxChecker*)contractStates[QX_CONTRACT_INDEX]; + } + + bool loadState(const CHAR16* filename) + { + return load(filename, sizeof(QX), contractStates[QX_CONTRACT_INDEX]) == sizeof(QX); + } + + // TODO: add other functions + + QX::AssetBidOrders_output assetBidOrders(const id& issuer, uint64 assetName, uint64 offset) + { + QX::AssetBidOrders_input input{ issuer, assetName, offset }; + QX::AssetBidOrders_output output; + callFunction(QX_CONTRACT_INDEX, 3, input, output); + return output; + } + + QX::EntityBidOrders_output entityBidOrders(const id& entity, uint64 offset) + { + QX::EntityBidOrders_input input{ entity, offset }; + QX::EntityBidOrders_output output; + callFunction(QX_CONTRACT_INDEX, 5, input, output); + return output; + } + + sint64 issueAsset(const id& issuer, uint64 assetName, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) + { + QX::IssueAsset_input input{ assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, issuer, QX_ISSUE_ASSET_FEE); + return output.issuedNumberOfShares; + } + + // TODO: add other procedures + + void endTick(bool expectSuccess = true) + { + callSystemProcedure(QX_CONTRACT_INDEX, END_TICK, expectSuccess); + } +}; + + +TEST(ContractQx, IssueAsset) +{ + ContractTestingQx qx; + + id issuer(1, 2, 3, 4); + uint64 assetName = assetNameFromString("QUTIL"); + sint64 numberOfShares = 1000000; + + increaseEnergy(issuer, QX_ISSUE_ASSET_FEE); + EXPECT_EQ(qx.issueAsset(issuer, assetName, numberOfShares, 0, 0), numberOfShares); + + EXPECT_EQ(numberOfPossessedShares(assetName, issuer, issuer, issuer, QX_CONTRACT_INDEX, QX_CONTRACT_INDEX), numberOfShares); +} + +TEST(ContractQx, BugEntityBidOrders) +{ + ContractTestingQx qx; + m256i issuer = m256i::zero(); + uint64 assetName = assetNameFromString("QUTIL"); + auto entityIdentity = (const unsigned char*)"EEWCBEZNLEITWFWVEOFBLKHVXTAARMIGJNXICDIRIFDBUDGFXEYABULCFXAN"; + m256i entityPubkey; + getPublicKeyFromIdentity(entityIdentity, entityPubkey.m256i_u8); + + if (!qx.loadState(L"contract0001.128")) + { + std::cout << "Skipping test due to missing file!" << std::endl; + return; + } + + qx.getState()->checkCollectionConsistency(); + + auto entityBidOrders = qx.entityBidOrders(entityPubkey, 0); + int entityBidOrdersCount = 0; + for (auto i = 0ull; i < entityBidOrders.orders.capacity(); ++i) + { + const auto& order = entityBidOrders.orders.get(i); + if (!order.price) + break; + + if (order.issuer == issuer && order.assetName == assetName) + ++entityBidOrdersCount; + +#if PRINT_DETAILS + std::cout + << "entity " << entityPubkey + << ", issuer " << order.issuer + << ", assetName " << assetNameFromInt64(order.assetName) + << ", price " << order.price + << ", shares " << order.numberOfShares << std::endl; +#endif + } + + auto assertBidOrders = qx.assetBidOrders(issuer, assetName, 0); + int assertBidOrdersCount = 0; + for (auto i = 0ull; i < assertBidOrders.orders.capacity(); ++i) + { + const auto& order = assertBidOrders.orders.get(i); + if (!order.price) + break; + + if (order.entity == entityPubkey) + ++assertBidOrdersCount; + +#if PRINT_DETAILS + std::cout + << "entity " << order.entity + << ", issuer " << issuer + << ", assetName " << assetNameFromInt64(assetName) + << ", price " << order.price + << ", shares " << order.numberOfShares << std::endl; +#endif + } + + EXPECT_EQ(assertBidOrdersCount, entityBidOrdersCount); +} + +TEST(ContractQx, CleanupCollections) +{ + ContractTestingQx qx; + if (qx.loadState(L"contract0001.163")) + { + std::cout << "QX state file:" << std::endl; + QxChecker* state = qx.getState(); + qx.endTick(); + state->cleanupCollections(); + } + else + { + std::cout << "QX state file not found. Skipping file test..." << std::endl; + } +} diff --git a/test/contract_rl.cpp b/test/contract_rl.cpp new file mode 100644 index 000000000..55e0afbac --- /dev/null +++ b/test/contract_rl.cpp @@ -0,0 +1,1283 @@ +// File: test/contract_rl.cpp +#define NO_UEFI + +#include "contract_testing.h" + +constexpr uint16 PROCEDURE_INDEX_BUY_TICKET = 1; +constexpr uint16 PROCEDURE_INDEX_SET_PRICE = 2; +constexpr uint16 PROCEDURE_INDEX_SET_SCHEDULE = 3; +constexpr uint16 FUNCTION_INDEX_GET_FEES = 1; +constexpr uint16 FUNCTION_INDEX_GET_PLAYERS = 2; +constexpr uint16 FUNCTION_INDEX_GET_WINNERS = 3; +constexpr uint16 FUNCTION_INDEX_GET_TICKET_PRICE = 4; +constexpr uint16 FUNCTION_INDEX_GET_MAX_NUM_PLAYERS = 5; +constexpr uint16 FUNCTION_INDEX_GET_STATE = 6; +constexpr uint16 FUNCTION_INDEX_GET_BALANCE = 7; +constexpr uint16 FUNCTION_INDEX_GET_NEXT_EPOCH_DATA = 8; +constexpr uint16 FUNCTION_INDEX_GET_DRAW_HOUR = 9; +constexpr uint16 FUNCTION_INDEX_GET_SCHEDULE = 10; +constexpr uint8 STATE_SELLING = static_cast(RL::EState::SELLING); +constexpr uint8 STATE_LOCKED = 0u; + +constexpr uint8 RL_ANY_DAY_DRAW_SCHEDULE = 0xFF; // 0xFF sets bits 0..6 (WED..TUE); bit 7 is unused/ignored by logic + +static uint32 makeDateStamp(uint16 year, uint8 month, uint8 day) +{ + const uint8 shortYear = static_cast(year - 2000); + return static_cast(shortYear << 9 | month << 5 | day); +} + +inline bool operator==(uint8 left, RL::EReturnCode right) +{ + return left == RL::toReturnCode(right); +} +inline bool operator==(RL::EReturnCode left, uint8 right) +{ + return right == left; +} +inline bool operator!=(uint8 left, RL::EReturnCode right) +{ + return !(left == right); +} +inline bool operator!=(RL::EReturnCode left, uint8 right) +{ + return !(right == left); +} + +// Equality operator for comparing WinnerInfo objects +// Compares all fields (address, revenue, epoch, tick, dayOfWeek) +bool operator==(const RL::WinnerInfo& left, const RL::WinnerInfo& right) +{ + return left.winnerAddress == right.winnerAddress && left.revenue == right.revenue && left.epoch == right.epoch && left.tick == right.tick && + left.dayOfWeek == right.dayOfWeek; +} + +// Test helper that exposes internal state assertions and utilities +class RLChecker : public RL +{ +public: + void checkFees(const GetFees_output& fees) + { + EXPECT_EQ(fees.returnCode, EReturnCode::SUCCESS); + + EXPECT_EQ(fees.distributionFeePercent, distributionFeePercent); + EXPECT_EQ(fees.teamFeePercent, teamFeePercent); + EXPECT_EQ(fees.winnerFeePercent, winnerFeePercent); + EXPECT_EQ(fees.burnPercent, burnPercent); + } + + void checkPlayers(const GetPlayers_output& output) const + { + EXPECT_EQ(output.returnCode, EReturnCode::SUCCESS); + EXPECT_EQ(output.players.capacity(), players.capacity()); + EXPECT_EQ(output.playerCounter, playerCounter); + + for (uint64 i = 0; i < playerCounter; ++i) + { + EXPECT_EQ(output.players.get(i), players.get(i)); + } + } + + void checkWinners(const GetWinners_output& output) const + { + EXPECT_EQ(output.returnCode, EReturnCode::SUCCESS); + EXPECT_EQ(output.winners.capacity(), winners.capacity()); + + const uint64 expectedCount = mod(winnersCounter, winners.capacity()); + EXPECT_EQ(output.winnersCounter, expectedCount); + + for (uint64 i = 0; i < expectedCount; ++i) + { + EXPECT_EQ(output.winners.get(i), winners.get(i)); + } + } + + void randomlyAddPlayers(uint64 maxNewPlayers) + { + playerCounter = mod(maxNewPlayers, players.capacity()); + for (uint64 i = 0; i < playerCounter; ++i) + { + players.set(i, id::randomValue()); + } + } + + void randomlyAddWinners(uint64 maxNewWinners) + { + const uint64 newWinnerCount = mod(maxNewWinners, winners.capacity()); + + winnersCounter = 0; + WinnerInfo wi; + + for (uint64 i = 0; i < newWinnerCount; ++i) + { + wi.epoch = 1; + wi.tick = 1; + wi.revenue = 1000000; + wi.winnerAddress = id::randomValue(); + winners.set(winnersCounter++, wi); + } + } + + void setScheduleMask(uint8 newMask) { schedule = newMask; } + + uint64 getPlayerCounter() const { return playerCounter; } + + uint64 getTicketPrice() const { return ticketPrice; } + + uint32 getLastDrawDateStamp() const { return lastDrawDateStamp; } + const id& team() const { return teamAddress; } +}; + +class ContractTestingRL : protected ContractTesting +{ +public: + ContractTestingRL() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(QX); + system.epoch = contractDescriptions[QX_CONTRACT_INDEX].constructionEpoch; + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(RL); + system.epoch = contractDescriptions[RL_CONTRACT_INDEX].constructionEpoch; + callSystemProcedure(RL_CONTRACT_INDEX, INITIALIZE); + } + + // Access internal contract state for assertions + RLChecker* state() { return reinterpret_cast(contractStates[RL_CONTRACT_INDEX]); } + + RL::GetFees_output getFees() + { + RL::GetFees_input input; + RL::GetFees_output output; + + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_FEES, input, output); + return output; + } + + RL::GetPlayers_output getPlayers() + { + RL::GetPlayers_input input; + RL::GetPlayers_output output; + + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_PLAYERS, input, output); + return output; + } + + RL::GetWinners_output getWinners() + { + RL::GetWinners_input input; + RL::GetWinners_output output; + + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_WINNERS, input, output); + return output; + } + + // Wrapper for public function RL::GetTicketPrice + RL::GetTicketPrice_output getTicketPrice() + { + RL::GetTicketPrice_input input; + RL::GetTicketPrice_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_TICKET_PRICE, input, output); + return output; + } + + // Wrapper for public function RL::GetMaxNumberOfPlayers + RL::GetMaxNumberOfPlayers_output getMaxNumberOfPlayers() + { + RL::GetMaxNumberOfPlayers_input input; + RL::GetMaxNumberOfPlayers_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_MAX_NUM_PLAYERS, input, output); + return output; + } + + // Wrapper for public function RL::GetState + RL::GetState_output getStateInfo() + { + RL::GetState_input input; + RL::GetState_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_STATE, input, output); + return output; + } + + // Wrapper for public function RL::GetBalance + // Returns current contract on-chain balance (incoming - outgoing) + RL::GetBalance_output getBalanceInfo() + { + RL::GetBalance_input input; + RL::GetBalance_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_BALANCE, input, output); + return output; + } + + // Wrapper for public function RL::GetNextEpochData + RL::GetNextEpochData_output getNextEpochData() + { + RL::GetNextEpochData_input input; + RL::GetNextEpochData_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_NEXT_EPOCH_DATA, input, output); + return output; + } + + // Wrapper for public function RL::GetDrawHour + RL::GetDrawHour_output getDrawHour() + { + RL::GetDrawHour_input input; + RL::GetDrawHour_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_DRAW_HOUR, input, output); + return output; + } + + // Wrapper for public function RL::GetSchedule + RL::GetSchedule_output getSchedule() + { + RL::GetSchedule_input input; + RL::GetSchedule_output output; + callFunction(RL_CONTRACT_INDEX, FUNCTION_INDEX_GET_SCHEDULE, input, output); + return output; + } + + RL::BuyTicket_output buyTicket(const id& user, sint64 reward) + { + RL::BuyTicket_input input; + RL::BuyTicket_output output; + if (!invokeUserProcedure(RL_CONTRACT_INDEX, PROCEDURE_INDEX_BUY_TICKET, input, output, user, reward)) + { + output.returnCode = RL::toReturnCode(RL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + // Added: wrapper for SetPrice procedure + RL::SetPrice_output setPrice(const id& invocator, uint64 newPrice) + { + RL::SetPrice_input input; + input.newPrice = newPrice; + RL::SetPrice_output output; + if (!invokeUserProcedure(RL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_PRICE, input, output, invocator, 0)) + { + output.returnCode = RL::toReturnCode(RL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + // Added: wrapper for SetSchedule procedure + RL::SetSchedule_output setSchedule(const id& invocator, uint8 newSchedule) + { + RL::SetSchedule_input input; + input.newSchedule = newSchedule; + RL::SetSchedule_output output; + if (!invokeUserProcedure(RL_CONTRACT_INDEX, PROCEDURE_INDEX_SET_SCHEDULE, input, output, invocator, 0)) + { + output.returnCode = RL::toReturnCode(RL::EReturnCode::UNKNOWN_ERROR); + } + return output; + } + + void BeginEpoch() { callSystemProcedure(RL_CONTRACT_INDEX, BEGIN_EPOCH); } + + void EndEpoch() { callSystemProcedure(RL_CONTRACT_INDEX, END_EPOCH); } + + void BeginTick() { callSystemProcedure(RL_CONTRACT_INDEX, BEGIN_TICK); } + + // Returns the SELF contract account address + id rlSelf() const { return id(RL_CONTRACT_INDEX, 0, 0, 0); } + + // Computes remaining contract balance after winner/team/distribution/burn payouts + // Distribution is floored to a multiple of NUMBER_OF_COMPUTORS + uint64 expectedRemainingAfterPayout(uint64 before, const RL::GetFees_output& fees) + { + const uint64 burn = (before * fees.burnPercent) / 100; + const uint64 distribPer = ((before * fees.distributionFeePercent) / 100) / NUMBER_OF_COMPUTORS; + const uint64 distrib = distribPer * NUMBER_OF_COMPUTORS; // floor to a multiple + const uint64 team = (before * fees.teamFeePercent) / 100; + const uint64 winner = (before * fees.winnerFeePercent) / 100; + return before - burn - distrib - team - winner; + } + + // Fund user and buy a ticket, asserting success + void increaseAndBuy(ContractTestingRL& ctl, const id& user, uint64 ticketPrice) + { + increaseEnergy(user, ticketPrice * 2); + const RL::BuyTicket_output out = ctl.buyTicket(user, ticketPrice); + EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); + } + + // Assert contract account balance equals the value returned by RL::GetBalance + void expectContractBalanceEqualsGetBalance(ContractTestingRL& ctl, const id& contractAddress) + { + const RL::GetBalance_output out = ctl.getBalanceInfo(); + EXPECT_EQ(out.balance, getBalance(contractAddress)); + } + + // New: set full date and hour (UTC), then sync QPI time + void setDateTime(uint16 year, uint8 month, uint8 day, uint8 hour) + { + updateTime(); + utcTime.Year = year; + utcTime.Month = month; + utcTime.Day = day; + utcTime.Hour = hour; + utcTime.Minute = 0; + utcTime.Second = 0; + utcTime.Nanosecond = 0; + updateQpiTime(); + } + + // New: advance to the next tick boundary where tick % RL_TICK_UPDATE_PERIOD == 0 and run BEGIN_TICK once + void forceBeginTick() + { + system.tick = system.tick + (RL_TICK_UPDATE_PERIOD - mod(system.tick, static_cast(RL_TICK_UPDATE_PERIOD))); + + BeginTick(); + } + + // New: helper to advance one calendar day and perform a scheduled draw at 12:00 UTC + void advanceOneDayAndDraw() + { + // Use a safe base month to avoid invalid dates: January 2025 + static uint16 y = 2025; + static uint8 m = 1; + static uint8 d = 10; // start from 10th + // advance one day within January bounds + d = static_cast(d + 1); + if (d > 31) + { + d = 1; // wrap within month for simplicity in tests + } + setDateTime(y, m, d, 12); + forceBeginTick(); + } + + // Force schedule mask directly in state (bypasses external call, suitable for tests) + void forceSchedule(uint8 scheduleMask) + { + state()->setScheduleMask(scheduleMask); + // NOTE: we do not call SetSchedule here to avoid epoch transitions in tests. + } + + void beginEpochWithDate(uint16 year, uint8 month, uint8 day, uint8 hour = static_cast(RL_DEFAULT_DRAW_HOUR + 1)) + { + setDateTime(year, month, day, hour); + BeginEpoch(); + } + + void beginEpochWithValidTime() { beginEpochWithDate(2025, 1, 20); } +}; + +TEST(ContractRandomLottery, SetPriceAndScheduleApplyNextEpoch) +{ + ContractTestingRL ctl; + ctl.beginEpochWithValidTime(); + + // Default epoch configuration: draws 3 times per week at the default price + const uint64 oldPrice = ctl.state()->getTicketPrice(); + EXPECT_EQ(ctl.getSchedule().schedule, RL_DEFAULT_SCHEDULE); + + // Queue a new price (5,000,000) and limit draws to only Wednesday + constexpr uint64 newPrice = 5000000; + constexpr uint8 wednesdayOnly = static_cast(1 << WEDNESDAY); + increaseEnergy(ctl.state()->team(), 3); + EXPECT_EQ(ctl.setPrice(ctl.state()->team(), newPrice).returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.setSchedule(ctl.state()->team(), wednesdayOnly).returnCode, RL::EReturnCode::SUCCESS); + + const RL::NextEpochData nextDataBefore = ctl.getNextEpochData().nextEpochData; + EXPECT_EQ(nextDataBefore.newPrice, newPrice); + EXPECT_EQ(nextDataBefore.schedule, wednesdayOnly); + + // Until END_EPOCH the old settings remain active + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); + EXPECT_EQ(ctl.getSchedule().schedule, RL_DEFAULT_SCHEDULE); + + // Transition closes the epoch and applies both pending changes + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); + EXPECT_EQ(ctl.getSchedule().schedule, wednesdayOnly); + + const RL::NextEpochData nextDataAfter = ctl.getNextEpochData().nextEpochData; + EXPECT_EQ(nextDataAfter.newPrice, 0u); + EXPECT_EQ(nextDataAfter.schedule, 0u); + + // In the next epoch tickets must sell at the updated price + ctl.beginEpochWithDate(2025, 1, 15); // Wednesday + const id buyer = id::randomValue(); + increaseEnergy(buyer, newPrice * 2); + const uint64 balBefore = getBalance(buyer); + const uint64 playersBefore = ctl.state()->getPlayerCounter(); + const RL::BuyTicket_output buyOut = ctl.buyTicket(buyer, newPrice); + EXPECT_EQ(buyOut.returnCode, RL::EReturnCode::SUCCESS); + const uint64 playersAfterFirstBuy = playersBefore + 1; + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterFirstBuy); + EXPECT_EQ(getBalance(buyer), balBefore - newPrice); + + // Second user also buys a ticket at the new price + const id secondBuyer = id::randomValue(); + increaseEnergy(secondBuyer, newPrice * 2); + const uint64 secondBalBefore = getBalance(secondBuyer); + const RL::BuyTicket_output secondBuyOut = ctl.buyTicket(secondBuyer, newPrice); + EXPECT_EQ(secondBuyOut.returnCode, RL::EReturnCode::SUCCESS); + const uint64 playersAfterBuy = playersAfterFirstBuy + 1; + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterBuy); + EXPECT_EQ(getBalance(secondBuyer), secondBalBefore - newPrice); + + // Draws should only trigger on Wednesdays now: starting on Wednesday means the draw + // is deferred until the next Wednesday in the schedule. + const uint64 winnersBefore = ctl.getWinners().winnersCounter; + ctl.setDateTime(2025, 1, 15, RL_DEFAULT_DRAW_HOUR + 1); // current Wednesday + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterBuy); + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore); + + // No draw on non-scheduled days between Wednesdays + ctl.setDateTime(2025, 1, 21, RL_DEFAULT_DRAW_HOUR + 1); // Tuesday next week + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersAfterBuy); + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore); + + // Next Wednesday processes the draw + ctl.setDateTime(2025, 1, 22, RL_DEFAULT_DRAW_HOUR + 1); // next Wednesday + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore + 1); + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_LOCKED); + + // After the draw and before the next epoch begins, ticket purchases are blocked + const id lockedBuyer = id::randomValue(); + increaseEnergy(lockedBuyer, newPrice); + const RL::BuyTicket_output lockedOut = ctl.buyTicket(lockedBuyer, newPrice); + EXPECT_EQ(lockedOut.returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); +} + +TEST(ContractRandomLottery, DefaultInitTimeGuardSkipsPlaceholderDate) +{ + ContractTestingRL ctl; + + const uint64 ticketPrice = ctl.state()->getTicketPrice(); + + // Allow draws every day so weekday logic does not block BEGIN_TICK + ctl.forceSchedule(RL_ANY_DAY_DRAW_SCHEDULE); + + // Simulate the placeholder 2022-04-13 QPI date during initialization + ctl.setDateTime(2022, 4, 13, RL_DEFAULT_DRAW_HOUR + 1); + ctl.BeginEpoch(); + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_LOCKED); + + // Selling is blocked until a valid date arrives + const id blockedBuyer = id::randomValue(); + increaseEnergy(blockedBuyer, ticketPrice); + const RL::BuyTicket_output denied = ctl.buyTicket(blockedBuyer, ticketPrice); + EXPECT_EQ(denied.returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + + const uint64 winnersBefore = ctl.getWinners().winnersCounter; + + // BEGIN_TICK should detect the placeholder date and skip processing, but remember the sentinel day + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getLastDrawDateStamp(), RL_DEFAULT_INIT_TIME); + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_LOCKED); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore); + + // First valid day re-opens selling but still skips the draw + ctl.setDateTime(2025, 1, 10, RL_DEFAULT_DRAW_HOUR + 1); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); + EXPECT_NE(ctl.state()->getLastDrawDateStamp(), RL_DEFAULT_INIT_TIME); + + const id playerA = id::randomValue(); + const id playerB = id::randomValue(); + ctl.increaseAndBuy(ctl, playerA, ticketPrice); + ctl.increaseAndBuy(ctl, playerB, ticketPrice); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 2u); + + // The immediate next valid day should run the actual draw + ctl.setDateTime(2025, 1, 11, RL_DEFAULT_DRAW_HOUR + 1); + ctl.forceBeginTick(); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBefore + 1); + EXPECT_NE(ctl.state()->getLastDrawDateStamp(), RL_DEFAULT_INIT_TIME); +} + +TEST(ContractRandomLottery, SellingUnlocksWhenTimeSetBeforeScheduledDay) +{ + ContractTestingRL ctl; + + const uint64 ticketPrice = ctl.state()->getTicketPrice(); + + ctl.setDateTime(2022, 4, 13, RL_DEFAULT_DRAW_HOUR + 1); + ctl.BeginEpoch(); + + const id deniedBuyer = id::randomValue(); + increaseEnergy(deniedBuyer, ticketPrice); + EXPECT_EQ(ctl.buyTicket(deniedBuyer, ticketPrice).returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); + + ctl.setDateTime(2025, 1, 14, RL_DEFAULT_DRAW_HOUR + 2); // Tuesday, not scheduled by default + ctl.forceBeginTick(); + + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); + EXPECT_EQ(ctl.state()->getLastDrawDateStamp(), 0u); + + const id allowedBuyer = id::randomValue(); + increaseEnergy(allowedBuyer, ticketPrice); + const RL::BuyTicket_output allowed = ctl.buyTicket(allowedBuyer, ticketPrice); + EXPECT_EQ(allowed.returnCode, RL::EReturnCode::SUCCESS); +} + +TEST(ContractRandomLottery, SellingUnlocksWhenTimeSetOnDrawDay) +{ + ContractTestingRL ctl; + + const uint64 ticketPrice = ctl.state()->getTicketPrice(); + + ctl.setDateTime(2022, 4, 13, RL_DEFAULT_DRAW_HOUR + 1); + ctl.BeginEpoch(); + + const id deniedBuyer = id::randomValue(); + increaseEnergy(deniedBuyer, ticketPrice); + EXPECT_EQ(ctl.buyTicket(deniedBuyer, ticketPrice).returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); + + ctl.setDateTime(2025, 1, 15, RL_DEFAULT_DRAW_HOUR + 2); // Wednesday draw day + ctl.forceBeginTick(); + + const uint32 expectedStamp = makeDateStamp(2025, 1, 15); + EXPECT_EQ(ctl.state()->getLastDrawDateStamp(), expectedStamp); + EXPECT_EQ(ctl.getStateInfo().currentState, STATE_SELLING); + + const id allowedBuyer = id::randomValue(); + increaseEnergy(allowedBuyer, ticketPrice); + const RL::BuyTicket_output allowed = ctl.buyTicket(allowedBuyer, ticketPrice); + EXPECT_EQ(allowed.returnCode, RL::EReturnCode::SUCCESS); +} + +TEST(ContractRandomLottery, PostIncomingTransfer) +{ + ContractTestingRL ctl; + static constexpr uint64 transferAmount = 123456789; + + const id sender = id::randomValue(); + increaseEnergy(sender, transferAmount); + EXPECT_EQ(getBalance(sender), transferAmount); + + const id contractAddress = ctl.rlSelf(); + EXPECT_EQ(getBalance(contractAddress), 0); + + notifyContractOfIncomingTransfer(sender, contractAddress, transferAmount, QPI::TransferType::standardTransaction); + + EXPECT_EQ(getBalance(sender), transferAmount); + EXPECT_EQ(getBalance(contractAddress), 0); +} + +TEST(ContractRandomLottery, GetFees) +{ + ContractTestingRL ctl; + RL::GetFees_output output = ctl.getFees(); + ctl.state()->checkFees(output); +} + +TEST(ContractRandomLottery, GetPlayers) +{ + ContractTestingRL ctl; + + // Initially empty + RL::GetPlayers_output output = ctl.getPlayers(); + ctl.state()->checkPlayers(output); + + // Add random players directly to state (test helper) + constexpr uint64 maxPlayersToAdd = 10; + ctl.state()->randomlyAddPlayers(maxPlayersToAdd); + output = ctl.getPlayers(); + ctl.state()->checkPlayers(output); +} + +TEST(ContractRandomLottery, GetWinners) +{ + ContractTestingRL ctl; + + // Populate winners history artificially + constexpr uint64 maxNewWinners = 10; + ctl.state()->randomlyAddWinners(maxNewWinners); + RL::GetWinners_output winnersOutput = ctl.getWinners(); + ctl.state()->checkWinners(winnersOutput); +} + +TEST(ContractRandomLottery, BuyTicket) +{ + ContractTestingRL ctl; + + const uint64 ticketPrice = ctl.state()->getTicketPrice(); + + // 1. Attempt when state is LOCKED (should fail and refund invocation reward) + { + const id userLocked = id::randomValue(); + increaseEnergy(userLocked, ticketPrice * 2); + RL::BuyTicket_output out = ctl.buyTicket(userLocked, ticketPrice); + EXPECT_EQ(out.returnCode, RL::EReturnCode::TICKET_SELLING_CLOSED); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0); + } + + // Switch to SELLING to allow purchases + ctl.beginEpochWithValidTime(); + + // 2. Loop over several users and test invalid price, success, duplicate + constexpr uint64 userCount = 5; + uint64 expectedPlayers = 0; + + for (uint64 i = 0; i < userCount; ++i) + { + const id user = id::randomValue(); + increaseEnergy(user, ticketPrice * 5); + + // (a) Invalid price (wrong reward sent) — player not added + { + // < ticketPrice + RL::BuyTicket_output outInvalid = ctl.buyTicket(user, ticketPrice - 1); + EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::TICKET_INVALID_PRICE); + EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); + + // == 0 + outInvalid = ctl.buyTicket(user, 0); + EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::TICKET_INVALID_PRICE); + EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); + + // < 0 + outInvalid = ctl.buyTicket(user, -1LL * ticketPrice); + EXPECT_NE(outInvalid.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); + } + + // (b) Valid purchase — player added + { + const RL::BuyTicket_output outOk = ctl.buyTicket(user, ticketPrice); + EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); + ++expectedPlayers; + EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); + } + + // (c) Duplicate purchase — allowed, increases count + { + const RL::BuyTicket_output outDup = ctl.buyTicket(user, ticketPrice); + EXPECT_EQ(outDup.returnCode, RL::EReturnCode::SUCCESS); + ++expectedPlayers; + EXPECT_EQ(ctl.state()->getPlayerCounter(), expectedPlayers); + } + } + + // 3. Sanity check: number of tickets equals twice the number of users (due to duplicate buys) + EXPECT_EQ(ctl.state()->getPlayerCounter(), userCount * 2); +} + +// Updated: payout is triggered by BEGIN_TICK with schedule/time gating, not by END_EPOCH +TEST(ContractRandomLottery, DrawAndPayout_BeginTick) +{ + ContractTestingRL ctl; + + const id contractAddress = ctl.rlSelf(); + const uint64 ticketPrice = ctl.state()->getTicketPrice(); + + // Current fee configuration (set in INITIALIZE) + const RL::GetFees_output fees = ctl.getFees(); + const uint8 teamPercent = fees.teamFeePercent; // Team commission percent + const uint8 winnerPercent = fees.winnerFeePercent; // Winner payout percent + + // Ensure schedule allows draw any day + ctl.forceSchedule(RL_ANY_DAY_DRAW_SCHEDULE); + + // --- Scenario 1: No players (nothing to payout, no winner recorded) --- + { + ctl.beginEpochWithValidTime(); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + + RL::GetWinners_output before = ctl.getWinners(); + const uint64 winnersBefore = before.winnersCounter; + + // Need to move to a new day and call BEGIN_TICK to allow draw + ctl.advanceOneDayAndDraw(); + + RL::GetWinners_output after = ctl.getWinners(); + EXPECT_EQ(after.winnersCounter, winnersBefore); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + } + + // --- Scenario 2: Exactly one player (ticket refunded, no winner recorded) --- + { + ctl.beginEpochWithValidTime(); + + const id solo = id::randomValue(); + increaseEnergy(solo, ticketPrice); + const uint64 balanceBefore = getBalance(solo); + + const RL::BuyTicket_output out = ctl.buyTicket(solo, ticketPrice); + EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 1u); + EXPECT_EQ(getBalance(solo), balanceBefore - ticketPrice); + + const uint64 winnersBeforeCount = ctl.getWinners().winnersCounter; + + ctl.advanceOneDayAndDraw(); + + // Refund happened + EXPECT_EQ(getBalance(solo), balanceBefore); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + + const RL::GetWinners_output winners = ctl.getWinners(); + // No new winners appended + EXPECT_EQ(winners.winnersCounter, winnersBeforeCount); + } + + // --- Scenario 2b: Multiple tickets from the same player are treated as single participant --- + { + ctl.beginEpochWithValidTime(); + + const id solo = id::randomValue(); + increaseEnergy(solo, ticketPrice * 10); + const uint64 balanceBefore = getBalance(solo); + + for (int i = 0; i < 5; ++i) + { + EXPECT_EQ(ctl.buyTicket(solo, ticketPrice).returnCode, RL::EReturnCode::SUCCESS); + } + EXPECT_EQ(ctl.state()->getPlayerCounter(), 5u); + + const uint64 winnersBeforeCount = ctl.getWinners().winnersCounter; + + ctl.advanceOneDayAndDraw(); + + // All tickets refunded, no winner recorded + EXPECT_EQ(getBalance(solo), balanceBefore); + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersBeforeCount); + EXPECT_EQ(getBalance(contractAddress), 0u); + } + + // --- Scenario 3: Multiple players (winner chosen, fees processed, correct remaining on contract) --- + { + ctl.beginEpochWithValidTime(); + + constexpr uint32 N = 5 * 2; + struct PlayerInfo + { + id addr; + uint64 balanceBefore; + uint64 balanceAfterBuy; + }; + std::vector infos; + infos.reserve(N); + + // Add N/2 distinct players, each making two valid purchases + for (uint32 i = 0; i < N; i += 2) + { + const id randomUser = id::randomValue(); + ctl.increaseAndBuy(ctl, randomUser, ticketPrice); + ctl.increaseAndBuy(ctl, randomUser, ticketPrice); + const uint64 bBefore = getBalance(randomUser); + infos.push_back({randomUser, bBefore + (ticketPrice * 2), bBefore}); // account for ticket deduction + } + + EXPECT_EQ(ctl.state()->getPlayerCounter(), N); + + const uint64 contractBalanceBefore = getBalance(contractAddress); + EXPECT_EQ(contractBalanceBefore, ticketPrice * N); + + const uint64 teamBalanceBefore = getBalance(ctl.state()->team()); + + const RL::GetWinners_output winnersBefore = ctl.getWinners(); + const uint64 winnersCountBefore = winnersBefore.winnersCounter; + + ctl.advanceOneDayAndDraw(); + + // Players reset after draw + EXPECT_EQ(ctl.state()->getPlayerCounter(), 0u); + + const RL::GetWinners_output winnersAfter = ctl.getWinners(); + EXPECT_EQ(winnersAfter.winnersCounter, winnersCountBefore + 1); + + // Newly appended winner info + const RL::WinnerInfo wi = winnersAfter.winners.get(mod(winnersCountBefore, winnersAfter.winners.capacity())); + EXPECT_NE(wi.winnerAddress, id::zero()); + EXPECT_EQ(wi.revenue, (ticketPrice * N * winnerPercent) / 100); + + // Winner address must be one of the players + bool found = false; + for (const PlayerInfo& inf : infos) + { + if (inf.addr == wi.winnerAddress) + { + found = true; + break; + } + } + EXPECT_TRUE(found); + + // Check winner balance increment and others unchanged + for (const PlayerInfo& inf : infos) + { + const uint64 bal = getBalance(inf.addr); + const uint64 balanceAfterBuy = inf.addr == wi.winnerAddress ? inf.balanceAfterBuy + wi.revenue : inf.balanceAfterBuy; + EXPECT_EQ(bal, balanceAfterBuy); + } + + // Team fee transferred + const uint64 teamFeeExpected = (ticketPrice * N * teamPercent) / 100; + EXPECT_EQ(getBalance(ctl.state()->team()), teamBalanceBefore + teamFeeExpected); + + // Burn (remaining on contract) + const uint64 burnExpected = ctl.expectedRemainingAfterPayout(contractBalanceBefore, fees); + EXPECT_EQ(getBalance(contractAddress), burnExpected); + } + + // --- Scenario 4: Several consecutive draws (winners accumulate, balances consistent) --- + { + const uint32 rounds = 3; + const uint32 playersPerRound = 6 * 2; // even number to mimic duplicates if desired + + // Remember starting winners count and team balance + const uint64 winnersStart = ctl.getWinners().winnersCounter; + const uint64 teamStartBal = getBalance(ctl.state()->team()); + + uint64 teamAccrued = 0; + + for (uint32 r = 0; r < rounds; ++r) + { + ctl.beginEpochWithValidTime(); + + struct P + { + id addr; + uint64 balAfterBuy; + }; + std::vector

roundPlayers; + roundPlayers.reserve(playersPerRound); + + // Each player buys two tickets in this round + for (uint32 i = 0; i < playersPerRound; i += 2) + { + const id u = id::randomValue(); + ctl.increaseAndBuy(ctl, u, ticketPrice); + ctl.increaseAndBuy(ctl, u, ticketPrice); + const uint64 balAfter = getBalance(u); + roundPlayers.push_back({u, balAfter}); + } + + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersPerRound); + + const uint64 winnersBefore = ctl.getWinners().winnersCounter; + const uint64 contractBefore = getBalance(contractAddress); + const uint64 teamBalBeforeRound = getBalance(ctl.state()->team()); + + ctl.advanceOneDayAndDraw(); + + // Winners should increase by exactly one + const RL::GetWinners_output wOut = ctl.getWinners(); + EXPECT_EQ(wOut.winnersCounter, winnersBefore + 1); + + // Validate winner entry + const RL::WinnerInfo newWi = wOut.winners.get(mod(winnersBefore, wOut.winners.capacity())); + EXPECT_NE(newWi.winnerAddress, id::zero()); + EXPECT_EQ(newWi.revenue, (contractBefore * winnerPercent) / 100); + + // Winner must be one of the current round players + bool inRound = false; + for (const auto& p : roundPlayers) + { + if (p.addr == newWi.winnerAddress) + { + inRound = true; + break; + } + } + EXPECT_TRUE(inRound); + + // Check players' balances after payout + for (const auto& p : roundPlayers) + { + const uint64 b = getBalance(p.addr); + const uint64 expected = (p.addr == newWi.winnerAddress) ? (p.balAfterBuy + newWi.revenue) : p.balAfterBuy; + EXPECT_EQ(b, expected); + } + + // Team fee for the whole round's contract balance + const uint64 teamFee = (contractBefore * teamPercent) / 100; + teamAccrued += teamFee; + EXPECT_EQ(getBalance(ctl.state()->team()), teamBalBeforeRound + teamFee); + + // Contract remaining should match expected + const uint64 expectedRemaining = ctl.expectedRemainingAfterPayout(contractBefore, fees); + EXPECT_EQ(getBalance(contractAddress), expectedRemaining); + } + + // After all rounds winners increased by rounds and team received cumulative fees + EXPECT_EQ(ctl.getWinners().winnersCounter, winnersStart + rounds); + EXPECT_EQ(getBalance(ctl.state()->team()), teamStartBal + teamAccrued); + } +} +TEST(ContractRandomLottery, GetBalance) +{ + ContractTestingRL ctl; + + const id contractAddress = ctl.rlSelf(); + const uint64 ticketPrice = ctl.state()->getTicketPrice(); + + // Initially, contract balance is 0 + { + const RL::GetBalance_output out0 = ctl.getBalanceInfo(); + EXPECT_EQ(out0.balance, 0u); + EXPECT_EQ(out0.balance, getBalance(contractAddress)); + } + + // Open selling and perform several purchases + ctl.beginEpochWithValidTime(); + + constexpr uint32 K = 3; + for (uint32 i = 0; i < K; ++i) + { + const id user = id::randomValue(); + ctl.increaseAndBuy(ctl, user, ticketPrice); + ctl.expectContractBalanceEqualsGetBalance(ctl, contractAddress); + } + + // Before draw, balance equals the total cost of tickets + { + const RL::GetBalance_output outBefore = ctl.getBalanceInfo(); + EXPECT_EQ(outBefore.balance, ticketPrice * K); + } + + // Trigger draw and verify expected remaining amount against contract balance and function output + const uint64 contractBalanceBefore = getBalance(contractAddress); + const RL::GetFees_output fees = ctl.getFees(); + + // Ensure schedule allows draw and perform it + ctl.forceSchedule(RL_ANY_DAY_DRAW_SCHEDULE); + ctl.advanceOneDayAndDraw(); + + const RL::GetBalance_output outAfter = ctl.getBalanceInfo(); + const uint64 envAfter = getBalance(contractAddress); + EXPECT_EQ(outAfter.balance, envAfter); + + const uint64 expectedRemaining = ctl.expectedRemainingAfterPayout(contractBalanceBefore, fees); + EXPECT_EQ(outAfter.balance, expectedRemaining); +} + +TEST(ContractRandomLottery, GetTicketPrice) +{ + ContractTestingRL ctl; + + const RL::GetTicketPrice_output out = ctl.getTicketPrice(); + EXPECT_EQ(out.ticketPrice, ctl.state()->getTicketPrice()); +} + +TEST(ContractRandomLottery, GetMaxNumberOfPlayers) +{ + ContractTestingRL ctl; + + const RL::GetMaxNumberOfPlayers_output out = ctl.getMaxNumberOfPlayers(); + // Compare against the known constant via GetPlayers capacity + const RL::GetPlayers_output playersOut = ctl.getPlayers(); + EXPECT_EQ(static_cast(out.numberOfPlayers), static_cast(playersOut.players.capacity())); +} + +TEST(ContractRandomLottery, GetState) +{ + ContractTestingRL ctl; + + // Initially LOCKED + { + const RL::GetState_output out0 = ctl.getStateInfo(); + EXPECT_EQ(out0.currentState, STATE_LOCKED); + } + + // After BeginEpoch — SELLING + ctl.beginEpochWithValidTime(); + { + const RL::GetState_output out1 = ctl.getStateInfo(); + EXPECT_EQ(out1.currentState, STATE_SELLING); + } + + // After END_EPOCH — back to LOCKED (selling disabled until next epoch) + ctl.EndEpoch(); + { + const RL::GetState_output out2 = ctl.getStateInfo(); + EXPECT_EQ(out2.currentState, STATE_LOCKED); + } +} + +// --- New tests for SetPrice and NextEpochData --- + +TEST(ContractRandomLottery, SetPrice_AccessControl) +{ + ContractTestingRL ctl; + + const uint64 oldPrice = ctl.state()->getTicketPrice(); + const uint64 newPrice = oldPrice * 2; + + // Random user must not have permission + const id randomUser = id::randomValue(); + increaseEnergy(randomUser, 1); + + const RL::SetPrice_output outDenied = ctl.setPrice(randomUser, newPrice); + EXPECT_EQ(outDenied.returnCode, RL::EReturnCode::ACCESS_DENIED); + + // Price doesn't change immediately nor after END_EPOCH implicitly + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); +} + +TEST(ContractRandomLottery, SetPrice_ZeroNotAllowed) +{ + ContractTestingRL ctl; + + increaseEnergy(ctl.state()->team(), 1); + + const uint64 oldPrice = ctl.state()->getTicketPrice(); + + const RL::SetPrice_output outInvalid = ctl.setPrice(ctl.state()->team(), 0); + EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::TICKET_INVALID_PRICE); + + // Price remains unchanged even after END_EPOCH + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); +} + +TEST(ContractRandomLottery, SetPrice_AppliesAfterEndEpoch) +{ + ContractTestingRL ctl; + + increaseEnergy(ctl.state()->team(), 1); + + const uint64 oldPrice = ctl.state()->getTicketPrice(); + const uint64 newPrice = oldPrice * 2; + + const RL::SetPrice_output outOk = ctl.setPrice(ctl.state()->team(), newPrice); + EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); + + // Check NextEpochData reflects pending change + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, newPrice); + + // Until END_EPOCH the price remains unchanged + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); + + // Applied after END_EPOCH + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); + + // NextEpochData cleared + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, 0u); + + // Another END_EPOCH without a new SetPrice doesn't change the price + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); +} + +TEST(ContractRandomLottery, SetPrice_OverrideBeforeEndEpoch) +{ + ContractTestingRL ctl; + + increaseEnergy(ctl.state()->team(), 1); + + const uint64 oldPrice = ctl.state()->getTicketPrice(); + const uint64 firstPrice = oldPrice + 1000; + const uint64 secondPrice = oldPrice + 7777; + + // Two SetPrice calls before END_EPOCH — the last one should apply + EXPECT_EQ(ctl.setPrice(ctl.state()->team(), firstPrice).returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.setPrice(ctl.state()->team(), secondPrice).returnCode, RL::EReturnCode::SUCCESS); + + // NextEpochData shows the last queued value + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, secondPrice); + + // Until END_EPOCH the old price remains + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, oldPrice); + + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, secondPrice); +} + +TEST(ContractRandomLottery, SetPrice_AffectsNextEpochBuys) +{ + ContractTestingRL ctl; + + increaseEnergy(ctl.state()->team(), 1); + + const uint64 oldPrice = ctl.state()->getTicketPrice(); + const uint64 newPrice = oldPrice * 3; + + // Open selling and buy at the old price + ctl.beginEpochWithValidTime(); + const id u1 = id::randomValue(); + increaseEnergy(u1, oldPrice * 2); + { + const RL::BuyTicket_output out1 = ctl.buyTicket(u1, oldPrice); + EXPECT_EQ(out1.returnCode, RL::EReturnCode::SUCCESS); + } + + // Set a new price, but before END_EPOCH purchases should use the old price logic (split by old price) + { + const RL::SetPrice_output setOut = ctl.setPrice(ctl.state()->team(), newPrice); + EXPECT_EQ(setOut.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.newPrice, newPrice); + } + + const id u2 = id::randomValue(); + increaseEnergy(u2, newPrice * 2); + { + const uint64 balBefore = getBalance(u2); + const uint64 playersBefore = ctl.state()->getPlayerCounter(); + const RL::BuyTicket_output outNow = ctl.buyTicket(u2, newPrice); + EXPECT_EQ(outNow.returnCode, RL::EReturnCode::SUCCESS); + // floor(newPrice/oldPrice) tickets were bought, the remainder was refunded + const uint64 bought = newPrice / oldPrice; + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + bought); + EXPECT_EQ(getBalance(u2), balBefore - bought * oldPrice); + } + + // END_EPOCH: new price will apply + ctl.EndEpoch(); + EXPECT_EQ(ctl.getTicketPrice().ticketPrice, newPrice); + + // In the next epoch, a purchase at the new price should succeed exactly once per price + ctl.beginEpochWithValidTime(); + { + const uint64 balBefore = getBalance(u2); + const uint64 playersBefore = ctl.state()->getPlayerCounter(); + const RL::BuyTicket_output outOk = ctl.buyTicket(u2, newPrice); + EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + 1); + EXPECT_EQ(getBalance(u2), balBefore - newPrice); + } +} + +TEST(ContractRandomLottery, BuyMultipleTickets_ExactMultiple_NoRemainder) +{ + ContractTestingRL ctl; + ctl.beginEpochWithValidTime(); + const uint64 price = ctl.state()->getTicketPrice(); + const id user = id::randomValue(); + constexpr uint64 k = 7; + increaseEnergy(user, price * k); + const uint64 playersBefore = ctl.state()->getPlayerCounter(); + const RL::BuyTicket_output out = ctl.buyTicket(user, price * k); + EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + k); +} + +TEST(ContractRandomLottery, BuyMultipleTickets_WithRemainder_Refunded) +{ + ContractTestingRL ctl; + ctl.beginEpochWithValidTime(); + const uint64 price = ctl.state()->getTicketPrice(); + const id user = id::randomValue(); + constexpr uint64 k = 5; + const uint64 r = price / 3; // partial remainder + increaseEnergy(user, price * k + r); + const uint64 balBefore = getBalance(user); + const uint64 playersBefore = ctl.state()->getPlayerCounter(); + const RL::BuyTicket_output out = ctl.buyTicket(user, price * k + r); + EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.state()->getPlayerCounter(), playersBefore + k); + // Remainder refunded, only k * price spent + EXPECT_EQ(getBalance(user), balBefore - k * price); +} + +TEST(ContractRandomLottery, BuyMultipleTickets_CapacityPartialRefund) +{ + ContractTestingRL ctl; + ctl.beginEpochWithValidTime(); + const uint64 price = ctl.state()->getTicketPrice(); + const uint64 capacity = ctl.getPlayers().players.capacity(); + + // Fill almost up to capacity + const uint64 toFill = (capacity > 5) ? (capacity - 5) : 0; + for (uint64 i = 0; i < toFill; ++i) + { + const id u = id::randomValue(); + increaseEnergy(u, price); + EXPECT_EQ(ctl.buyTicket(u, price).returnCode, RL::EReturnCode::SUCCESS); + } + EXPECT_EQ(ctl.state()->getPlayerCounter(), toFill); + + // Try to buy 10 tickets — only remaining 5 accepted, the rest refunded + const id buyer = id::randomValue(); + increaseEnergy(buyer, price * 10); + const uint64 balBefore = getBalance(buyer); + const RL::BuyTicket_output out = ctl.buyTicket(buyer, price * 10); + EXPECT_EQ(out.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.state()->getPlayerCounter(), capacity); + EXPECT_EQ(getBalance(buyer), balBefore - price * 5); +} + +TEST(ContractRandomLottery, BuyMultipleTickets_AllSoldOut) +{ + ContractTestingRL ctl; + ctl.beginEpochWithValidTime(); + const uint64 price = ctl.state()->getTicketPrice(); + const uint64 capacity = ctl.getPlayers().players.capacity(); + + // Fill to capacity + for (uint64 i = 0; i < capacity; ++i) + { + const id u = id::randomValue(); + increaseEnergy(u, price); + EXPECT_EQ(ctl.buyTicket(u, price).returnCode, RL::EReturnCode::SUCCESS); + } + EXPECT_EQ(ctl.state()->getPlayerCounter(), capacity); + + // Any purchase refunds the full amount and returns ALL_SOLD_OUT code + const id buyer = id::randomValue(); + increaseEnergy(buyer, price * 3); + const uint64 balBefore = getBalance(buyer); + const RL::BuyTicket_output out = ctl.buyTicket(buyer, price * 3); + EXPECT_EQ(out.returnCode, RL::EReturnCode::TICKET_ALL_SOLD_OUT); + EXPECT_EQ(getBalance(buyer), balBefore); +} + +// functions related to schedule and draw hour + +TEST(ContractRandomLottery, GetSchedule_And_SetSchedule) +{ + ContractTestingRL ctl; + + // Default schedule set on initialize must include Wednesday (bit 0) + const RL::GetSchedule_output s0 = ctl.getSchedule(); + EXPECT_NE(s0.schedule, 0u); + + // Access control: random user cannot set schedule + const id rnd = id::randomValue(); + increaseEnergy(rnd, 1); + const RL::SetSchedule_output outDenied = ctl.setSchedule(rnd, RL_ANY_DAY_DRAW_SCHEDULE); + EXPECT_EQ(outDenied.returnCode, RL::EReturnCode::ACCESS_DENIED); + + // Invalid value: zero mask not allowed + increaseEnergy(ctl.state()->team(), 1); + const RL::SetSchedule_output outInvalid = ctl.setSchedule(ctl.state()->team(), 0); + EXPECT_EQ(outInvalid.returnCode, RL::EReturnCode::INVALID_VALUE); + + // Valid update queues into NextEpochData and applies after END_EPOCH + const uint8 newMask = 0x5A; // some non-zero mask (bits set for selected days) + const RL::SetSchedule_output outOk = ctl.setSchedule(ctl.state()->team(), newMask); + EXPECT_EQ(outOk.returnCode, RL::EReturnCode::SUCCESS); + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.schedule, newMask); + + // Not applied yet + EXPECT_NE(ctl.getSchedule().schedule, newMask); + + // Apply + ctl.EndEpoch(); + EXPECT_EQ(ctl.getSchedule().schedule, newMask); + EXPECT_EQ(ctl.getNextEpochData().nextEpochData.schedule, 0u); +} + +TEST(ContractRandomLottery, GetDrawHour_DefaultAfterBeginEpoch) +{ + ContractTestingRL ctl; + + // Initially drawHour is 0 (not configured) + EXPECT_EQ(ctl.getDrawHour().drawHour, 0u); + + // After BeginEpoch default is 11 UTC + ctl.beginEpochWithValidTime(); + EXPECT_EQ(ctl.getDrawHour().drawHour, RL_DEFAULT_DRAW_HOUR); +} + + diff --git a/test/contract_testex.cpp b/test/contract_testex.cpp new file mode 100644 index 000000000..104e2b3f7 --- /dev/null +++ b/test/contract_testex.cpp @@ -0,0 +1,2173 @@ +#define NO_UEFI + +#include +#include + +#include "contract_testing.h" +#include "oracle_testing.h" + +static const id TESTEXA_CONTRACT_ID(TESTEXA_CONTRACT_INDEX, 0, 0, 0); +static const id TESTEXB_CONTRACT_ID(TESTEXB_CONTRACT_INDEX, 0, 0, 0); +static const id TESTEXC_CONTRACT_ID(TESTEXC_CONTRACT_INDEX, 0, 0, 0); +static const id TESTEXD_CONTRACT_ID(TESTEXD_CONTRACT_INDEX, 0, 0, 0); +static const id USER1(123, 456, 789, 876); +static const id USER2(42, 424, 4242, 42424); +static const id USER3(98, 76, 54, 3210); +static const id USER4(9878, 7645, 541, 3210); + +void checkPreManagementRightsTransferInput(const PreManagementRightsTransfer_input& observed, const PreManagementRightsTransfer_input& expected) +{ + EXPECT_EQ(observed.asset.assetName, expected.asset.assetName); + EXPECT_EQ(observed.asset.issuer, expected.asset.issuer); + EXPECT_EQ(observed.numberOfShares, expected.numberOfShares); + EXPECT_EQ(observed.offeredFee, expected.offeredFee); + EXPECT_EQ((int)observed.otherContractIndex, (int)expected.otherContractIndex); + EXPECT_EQ(observed.owner, expected.owner); + EXPECT_EQ(observed.possessor, expected.possessor); +} + +void checkPostManagementRightsTransferInput(const PostManagementRightsTransfer_input& observed, const PostManagementRightsTransfer_input& expected) +{ + EXPECT_EQ(observed.asset.assetName, expected.asset.assetName); + EXPECT_EQ(observed.asset.issuer, expected.asset.issuer); + EXPECT_EQ(observed.numberOfShares, expected.numberOfShares); + EXPECT_EQ(observed.receivedFee, expected.receivedFee); + EXPECT_EQ((int)observed.otherContractIndex, (int)expected.otherContractIndex); + EXPECT_EQ(observed.owner, expected.owner); + EXPECT_EQ(observed.possessor, expected.possessor); +} + +class StateCheckerTestExampleA : public TESTEXA +{ +public: + void checkPostReleaseCounter(uint32 expectedCount) + { + EXPECT_EQ(this->postReleaseSharesCounter, expectedCount); + } + + const PreManagementRightsTransfer_input& getPreReleaseInput() const + { + return this->prevPreReleaseSharesInput; + } + + const PostManagementRightsTransfer_input& getPostReleaseInput() const + { + return this->prevPostReleaseSharesInput; + } + + void checkPostAcquireCounter(uint32 expectedCount) + { + EXPECT_EQ(this->postAcquireShareCounter, expectedCount); + } + + const PreManagementRightsTransfer_input& getPreAcquireInput() const + { + return this->prevPreAcquireSharesInput; + } + + const PostManagementRightsTransfer_input& getPostAcquireInput() const + { + return this->prevPostAcquireSharesInput; + } + + void checkVariablesSetByProposal( + uint64 expectedVariable1, + uint32 expectedVariable2, + sint8 expectedVariable3) const + { + EXPECT_EQ(this->dummyStateVariable1, expectedVariable1); + EXPECT_EQ(this->dummyStateVariable2, expectedVariable2); + EXPECT_EQ(this->dummyStateVariable3, expectedVariable3); + } +}; + +class StateCheckerTestExampleB : public TESTEXB +{ +public: + void checkPostReleaseCounter(uint32 expectedCount) + { + EXPECT_EQ(this->postReleaseSharesCounter, expectedCount); + } + + const PreManagementRightsTransfer_input& getPreReleaseInput() const + { + return this->prevPreReleaseSharesInput; + } + + const PostManagementRightsTransfer_input& getPostReleaseInput() const + { + return this->prevPostReleaseSharesInput; + } + + void checkPostAcquireCounter(uint32 expectedCount) + { + EXPECT_EQ(this->postAcquireShareCounter, expectedCount); + } + + const PreManagementRightsTransfer_input& getPreAcquireInput() const + { + return this->prevPreAcquireSharesInput; + } + + const PostManagementRightsTransfer_input& getPostAcquireInput() const + { + return this->prevPostAcquireSharesInput; + } + + void checkVariablesSetByProposal( + sint64 expectedVariable1, + sint64 expectedVariable2, + sint64 expectedVariable3) const + { + EXPECT_EQ(this->fee1, expectedVariable1); + EXPECT_EQ(this->fee2, expectedVariable2); + EXPECT_EQ(this->fee3, expectedVariable3); + } +}; + +class ContractTestingTestEx : protected ContractTesting +{ +public: + QX::Fees_output qxFees; + + ContractTestingTestEx() + { + initEmptySpectrum(); + initEmptyUniverse(); + INIT_CONTRACT(TESTEXA); + callSystemProcedure(TESTEXA_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(TESTEXB); + callSystemProcedure(TESTEXB_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(TESTEXC); + callSystemProcedure(TESTEXC_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(TESTEXD); + callSystemProcedure(TESTEXD_CONTRACT_INDEX, INITIALIZE); + INIT_CONTRACT(QX); + callSystemProcedure(QX_CONTRACT_INDEX, INITIALIZE); + + EXPECT_TRUE(oracleEngine.init(computorPublicKeys)); + EXPECT_TRUE(OI::initOracleInterfaces()); + + checkContractExecCleanup(); + + // query QX fees + callFunction(QX_CONTRACT_INDEX, 1, QX::Fees_input(), qxFees); + } + + ~ContractTestingTestEx() + { + oracleEngine.deinit(); + checkContractExecCleanup(); + } + + StateCheckerTestExampleA* getStateTestExampleA() + { + return (StateCheckerTestExampleA*)contractStates[TESTEXA_CONTRACT_INDEX]; + } + + StateCheckerTestExampleB* getStateTestExampleB() + { + return (StateCheckerTestExampleB*)contractStates[TESTEXB_CONTRACT_INDEX]; + } + + sint64 issueAssetQx(const Asset& asset, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) + { + QX::IssueAsset_input input{ asset.assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; + QX::IssueAsset_output output; + invokeUserProcedure(QX_CONTRACT_INDEX, 1, input, output, asset.issuer, qxFees.assetIssuanceFee); + return output.issuedNumberOfShares; + } + + sint64 issueAssetTestExA(const Asset& asset, sint64 numberOfShares, uint64 unitOfMeasurement, sint8 numberOfDecimalPlaces) + { + TESTEXA::IssueAsset_input input{ asset.assetName, numberOfShares, unitOfMeasurement, numberOfDecimalPlaces }; + TESTEXA::IssueAsset_output output; + invokeUserProcedure(TESTEXA_CONTRACT_INDEX, 1, input, output, asset.issuer, 0); + return output.issuedNumberOfShares; + } + + sint64 transferShareOwnershipAndPossessionQx(const Asset& asset, const id& currentOwnerAndPossessor, const id& newOwnerAndPossessor, sint64 numberOfShares) + { + QX::TransferShareOwnershipAndPossession_input input; + QX::TransferShareOwnershipAndPossession_output output; + + input.assetName = asset.assetName; + input.issuer = asset.issuer; + input.newOwnerAndPossessor = newOwnerAndPossessor; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 2, input, output, currentOwnerAndPossessor, qxFees.transferFee); + + return output.transferredNumberOfShares; + } + + template + sint64 transferShareOwnershipAndPossession(const Asset& asset, const id& currentOwnerAndPossessor, const id& newOwnerAndPossessor, sint64 numberOfShares) + { + typename StateStruct::TransferShareOwnershipAndPossession_input input; + typename StateStruct::TransferShareOwnershipAndPossession_output output; + + input.asset = asset; + input.newOwnerAndPossessor = newOwnerAndPossessor; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(StateStruct::__contract_index, 2, input, output, currentOwnerAndPossessor, 0); + + return output.transferredNumberOfShares; + } + + sint64 transferShareManagementRightsQx(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, unsigned int newManagingContractIndex, sint64 fee = 0) + { + QX::TransferShareManagementRights_input input; + QX::TransferShareManagementRights_output output; + + input.asset = asset; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(QX_CONTRACT_INDEX, 9, input, output, currentOwnerAndPossessor, fee); + + return output.transferredNumberOfShares; + } + + template + sint64 transferShareManagementRights(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, unsigned int newManagingContractIndex, sint64 fee = 0) + { + typename StateStruct::TransferShareManagementRights_input input; + typename StateStruct::TransferShareManagementRights_output output; + + input.asset = asset; + input.newManagingContractIndex = newManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(StateStruct::__contract_index, 3, input, output, currentOwnerAndPossessor, fee); + + return output.transferredNumberOfShares; + } + + template + void setPreReleaseSharesOutput(bool allowTransfer, sint64 requestedFee) + { + typename StateStruct::SetPreReleaseSharesOutput_input input{ allowTransfer, requestedFee }; + typename StateStruct::SetPreReleaseSharesOutput_output output; + invokeUserProcedure(StateStruct::__contract_index, 4, input, output, USER1, 0); + } + + template + void setPreAcquireSharesOutput(bool allowTransfer, sint64 requestedFee) + { + typename StateStruct::SetPreReleaseSharesOutput_input input{ allowTransfer, requestedFee }; + typename StateStruct::SetPreReleaseSharesOutput_output output; + invokeUserProcedure(StateStruct::__contract_index, 5, input, output, USER1, 0); + } + + + template + sint64 acquireShareManagementRights(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, unsigned int prevManagingContractIndex, sint64 fee = 0, const id& originator = NULL_ID) + { + typename StateStruct::AcquireShareManagementRights_input input; + typename StateStruct::AcquireShareManagementRights_output output; + + input.asset = asset; + input.ownerAndPossessor = currentOwnerAndPossessor; + input.oldManagingContractIndex = prevManagingContractIndex; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(StateStruct::__contract_index, 6, input, output, + (isZero(originator)) ? currentOwnerAndPossessor : originator, fee); + + return output.transferredNumberOfShares; + } + + sint64 getTestExAsShareManagementRightsByInvokingTestExB(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, sint64 fee = 0) + { + TESTEXB::GetTestExampleAShareManagementRights_input input; + TESTEXB::GetTestExampleAShareManagementRights_output output; + + input.asset = asset; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(TESTEXB::__contract_index, 7, input, output, currentOwnerAndPossessor, fee); + + return output.transferredNumberOfShares; + } + + sint64 getTestExAsShareManagementRightsByInvokingTestExC(const Asset& asset, const id& currentOwnerAndPossessor, sint64 numberOfShares, sint64 fee = 0) + { + TESTEXC::GetTestExampleAShareManagementRights_input input; + TESTEXC::GetTestExampleAShareManagementRights_output output; + + input.asset = asset; + input.numberOfShares = numberOfShares; + + invokeUserProcedure(TESTEXC::__contract_index, 7, input, output, currentOwnerAndPossessor, fee); + + return output.transferredNumberOfShares; + } + + TESTEXA::QueryQpiFunctions_output queryQpiFunctions(const TESTEXA::QueryQpiFunctions_input& input) + { + TESTEXA::QueryQpiFunctions_output output; + callFunction(TESTEXA_CONTRACT_INDEX, 1, input, output); + return output; + } + + unsigned int callFunctionOfTestExampleAFromTextExampleB(const TESTEXA::QueryQpiFunctions_input& input, TESTEXA::QueryQpiFunctions_output& output, bool expectSuccess) + { + return callFunction(TESTEXB_CONTRACT_INDEX, 1, input, output, true, expectSuccess); + } + + unsigned int callErrorTriggerFunction() + { + TESTEXA::ErrorTriggerFunction_input input; + TESTEXA::ErrorTriggerFunction_output output; + return callFunction(TESTEXA_CONTRACT_INDEX, 5, input, output, true, false); + } + + template + typename StateStruct::IncomingTransferAmounts_output getIncomingTransferAmounts() + { + typename StateStruct::IncomingTransferAmounts_input input; + typename StateStruct::IncomingTransferAmounts_output output; + EXPECT_EQ(callFunction(StateStruct::__contract_index, 20, input, output), NoContractError); + return output; + } + + template + bool qpiTransfer(const id& destinationPublicKey, sint64 amount, sint64 fee = 0, const id& originator = USER1) + { + typename StateStruct::QpiTransfer_input input{ destinationPublicKey, amount }; + typename StateStruct::QpiTransfer_output output; + return invokeUserProcedure(StateStruct::__contract_index, 20, input, output, originator, fee); + } + + template + bool qpiDistributeDividends(sint64 amountPerShare, sint64 fee = 0, const id& originator = USER1) + { + typename StateStruct::QpiDistributeDividends_input input{ amountPerShare }; + typename StateStruct::QpiDistributeDividends_output output; + return invokeUserProcedure(StateStruct::__contract_index, 21, input, output, originator, fee); + } + + template + typename StateStruct::GetIpoBid_output getIpoBid(unsigned int ipoContractIndex, unsigned int bidIndex) + { + typename StateStruct::GetIpoBid_input input{ ipoContractIndex, bidIndex }; + typename StateStruct::GetIpoBid_output output; + EXPECT_EQ(callFunction(StateStruct::__contract_index, 30, input, output), NoContractError); + return output; + } + + template + long long qpiBidInIpo(unsigned int ipoContractIndex, long long pricePerShare, unsigned short numberOfShares, sint64 fee = 0, const id& originator = USER1) + { + typename StateStruct::QpiBidInIpo_input input{ ipoContractIndex, pricePerShare, numberOfShares }; + typename StateStruct::QpiBidInIpo_output output; + if (invokeUserProcedure(StateStruct::__contract_index, 30, input, output, originator, fee)) + return output; + else + return -2; + } + + template + uint16 setShareholderProposal(const id& originator, const typename StateStruct::SetShareholderProposal_input& input) + { + typename StateStruct::SetShareholderProposal_output output; + EXPECT_TRUE(invokeUserProcedure(StateStruct::__contract_index, 65534, input, output, originator, 0)); + return output; + } + + template + bool setShareholderVotes(const id& originator, uint16 proposalIndex, const typename StateStruct::ProposalDataT& proposalData, sint64 voteValue) + { + // Contract procedure expects ProposalMultiVoteDataV1, but ProposalSingleVoteDataV1 is compatible + ProposalSingleVoteDataV1 input{ proposalIndex, proposalData.type, proposalData.tick, voteValue }; + typename StateStruct::SetShareholderVotes_output output; + invokeUserProcedure(StateStruct::__contract_index, 65535, input, output, originator, 0, false); + return output; + } + + template + bool setShareholderVotes(const id& originator, uint16 proposalIndex, const typename StateStruct::ProposalDataT& proposalData, + const std::vector>& voteValueCountPairs) + { + ASSERT(voteValueCountPairs.size() <= 8); + ProposalMultiVoteDataV1 input{ proposalIndex, proposalData.type, proposalData.tick }; + input.voteValues.set(0, NO_VOTE_VALUE); // default with no voteValueCountPairs (vote count 0): set all to no votes + for (size_t i = 0; i < voteValueCountPairs.size(); ++i) + { + input.voteValues.set(i, voteValueCountPairs[i].first); + input.voteCounts.set(i, voteValueCountPairs[i].second); + } + typename StateStruct::SetShareholderVotes_output output; + invokeUserProcedure(StateStruct::__contract_index, 65535, input, output, originator, 0); + return output; + } + + TESTEXB::TestInterContractCallError_output testInterContractCallError() + { + TESTEXB::TestInterContractCallError_input input; + input.dummy = 0; + TESTEXB::TestInterContractCallError_output output; + invokeUserProcedure(TESTEXB_CONTRACT_INDEX, 50, input, output, USER1, 0); + return output; + } + + template + std::vector getShareholderProposalIndices(bit activeProposals) + { + typename StateStruct::GetShareholderProposalIndices_input input{ activeProposals, -1 }; + typename StateStruct::GetShareholderProposalIndices_output output; + std::vector indices; + do + { + callFunction(StateStruct::__contract_index, 65532, input, output); + for (uint16 i = 0; i < output.numOfIndices; ++i) + indices.push_back(output.indices.get(i)); + } while (output.numOfIndices == output.indices.capacity()); + return indices; + } + + template + StateStruct::GetShareholderProposal_output getShareholderProposal(uint16 proposalIndex) + { + typename StateStruct::GetShareholderProposal_input input{ proposalIndex }; + typename StateStruct::GetShareholderProposal_output output; + callFunction(StateStruct::__contract_index, 65533, input, output); + return output; + } + + template + ProposalMultiVoteDataV1 getShareholderVotes(uint16 proposalIndex, const id& voter) + { + typename StateStruct::GetShareholderVotes_input input{ voter, proposalIndex }; + typename StateStruct::GetShareholderVotes_output output; + callFunction(StateStruct::__contract_index, 65534, input, output); + return output; + } + + template + ProposalSummarizedVotingDataV1 getShareholderVotingResults(uint16 proposalIndex) + { + typename StateStruct::GetShareholderVotingResults_input input{ proposalIndex }; + typename StateStruct::GetShareholderVotingResults_output output; + callFunction(StateStruct::__contract_index, 65535, input, output); + return output; + } + + uint16 setupShareholderProposalTestExA( + const id& proposer, uint16 type, + bool setVar1 = false, uint64 valueVar1 = 0, + bool setVar2 = false, uint32 valueVar2 = 0, + bool setVar3 = false, sint8 valueVar3 = 0, + bool expectSuccess = true) + { + TESTEXA::SetShareholderProposal_input input; + setMemory(input, 0); + input.proposalData.epoch = system.epoch; + input.proposalData.type = type; + switch (ProposalTypes::cls(type)) + { + case ProposalTypes::Class::Variable: + { + if (setVar1) + { + EXPECT_FALSE(setVar2); + EXPECT_FALSE(setVar3); + input.proposalData.data.variableOptions.variable = 0; + input.proposalData.data.variableOptions.value = valueVar1; + } + else if (setVar2) + { + EXPECT_FALSE(setVar1); + EXPECT_FALSE(setVar3); + input.proposalData.data.variableOptions.variable = 1; + input.proposalData.data.variableOptions.value = valueVar2; + } + else if (setVar3) + { + EXPECT_FALSE(setVar1); + EXPECT_FALSE(setVar2); + input.proposalData.data.variableOptions.variable = 2; + input.proposalData.data.variableOptions.value = valueVar3; + } + break; + } + case ProposalTypes::Class::MultiVariables: + input.multiVarData.hasValueDummyStateVariable1 = setVar1; + input.multiVarData.hasValueDummyStateVariable2 = setVar2; + input.multiVarData.hasValueDummyStateVariable3 = setVar3; + input.multiVarData.optionYesValues.dummyStateVariable1 = valueVar1; + input.multiVarData.optionYesValues.dummyStateVariable2 = valueVar2; + input.multiVarData.optionYesValues.dummyStateVariable3 = valueVar3; + break; + } + uint16 proposalIdx = this->setShareholderProposal(proposer, input); + if (expectSuccess) + EXPECT_NE((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); + else + EXPECT_EQ((int)proposalIdx, (int)INVALID_PROPOSAL_INDEX); + return proposalIdx; + } + + template + uint16 setProposalInOtherContractAsShareholder(const id& originator, uint16 otherContractIndex, const FullProposalDataT& fullProposalData) + { + typename StateStruct::SetProposalInOtherContractAsShareholder_input input; + copyToBuffer(input, fullProposalData); + input.otherContractIndex = otherContractIndex; + typename StateStruct::SetProposalInOtherContractAsShareholder_output output; + invokeUserProcedure(StateStruct::__contract_index, 40, input, output, originator, 0); + return output.proposalIndex; + } + + template + bool setVotesInOtherContractAsShareholder(const id& originator, uint16 otherContractIndex, uint16 proposalIndex, const ProposalDataT& proposalData, + const std::vector>& voteValueCountPairs) + { + ASSERT(voteValueCountPairs.size() <= 8); + typename StateStruct::SetVotesInOtherContractAsShareholder_input input{ {proposalIndex, proposalData.type, proposalData.tick} }; + input.otherContractIndex = otherContractIndex; + input.voteData.voteValues.set(0, NO_VOTE_VALUE); // default with no voteValueCountPairs (vote count 0): set all to no votes + for (size_t i = 0; i < voteValueCountPairs.size(); ++i) + { + input.voteData.voteValues.set(i, voteValueCountPairs[i].first); + input.voteData.voteCounts.set(i, voteValueCountPairs[i].second); + } + typename StateStruct::SetVotesInOtherContractAsShareholder_output output; + invokeUserProcedure(StateStruct::__contract_index, 41, input, output, originator, 0); + return output.success; + } + + void beginEpoch(bool expectSuccess = true) + { + callSystemProcedure(TESTEXD_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + callSystemProcedure(TESTEXC_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + callSystemProcedure(TESTEXB_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + callSystemProcedure(TESTEXA_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + callSystemProcedure(QX_CONTRACT_INDEX, BEGIN_EPOCH, expectSuccess); + } + + void endEpoch(bool expectSuccess = true) + { + callSystemProcedure(TESTEXD_CONTRACT_INDEX, END_EPOCH, expectSuccess); + callSystemProcedure(TESTEXC_CONTRACT_INDEX, END_EPOCH, expectSuccess); + callSystemProcedure(TESTEXB_CONTRACT_INDEX, END_EPOCH, expectSuccess); + callSystemProcedure(TESTEXA_CONTRACT_INDEX, END_EPOCH, expectSuccess); + callSystemProcedure(QX_CONTRACT_INDEX, END_EPOCH, expectSuccess); + } + + void endTick(bool expectSuccess = true) + { + callSystemProcedure(TESTEXD_CONTRACT_INDEX, END_TICK, expectSuccess); + callSystemProcedure(TESTEXC_CONTRACT_INDEX, END_TICK, expectSuccess); + callSystemProcedure(TESTEXB_CONTRACT_INDEX, END_TICK, expectSuccess); + callSystemProcedure(TESTEXA_CONTRACT_INDEX, END_TICK, expectSuccess); + callSystemProcedure(QX_CONTRACT_INDEX, END_TICK, expectSuccess); + } + + uint64 queryPriceOracle(const id& invocator, uint32 timeoutMilliseconds, const OI::Price::OracleQuery& query) + { + TESTEXC::QueryPriceOracle_input input; + input.priceOracleQuery = query; + input.timeoutMilliseconds = timeoutMilliseconds; + TESTEXC::QueryPriceOracle_output output; + EXPECT_TRUE(invokeUserProcedure(TESTEXC_CONTRACT_INDEX, 100, input, output, invocator, 0)); + return output.oracleQueryId; + } +}; + +void checkVoteCounts(const ProposalMultiVoteDataV1& votes, const std::vector>& expectedVoteValueCountPairs) +{ + std::vector> expectedPairsNotFound = expectedVoteValueCountPairs; + for (int i = 0; i < votes.voteCounts.capacity(); ++i) + { + sint64 value = votes.voteValues.get(i); + uint32 count = votes.voteCounts.get(i); + std::pair pair(value, count); + auto it = std::find(expectedPairsNotFound.begin(), expectedPairsNotFound.end(), pair); + if (it != expectedPairsNotFound.end()) + { + // value-count pair found + expectedPairsNotFound.erase(it); + } + else if (count) + { + FAIL() << "Error: unexpected vote value/count pair " << value << "/" << count; + } + } + for (const auto& it : expectedPairsNotFound) + { + FAIL() << "Error: missing vote value/count pair " << it.first << "/" << it.second; + } +} + +bool operator==(const TESTEXA::MultiVariablesProposalExtraData& p1, const TESTEXA::MultiVariablesProposalExtraData& p2) +{ + return memcmp(&p1, &p2, sizeof(p1)) == 0; +} + + +TEST(ContractTestEx, QpiReleaseShares) +{ + ContractTestingTestEx test; + + const Asset asset1{ USER1, assetNameFromString("BLOB") }; + const sint64 totalShareCount = 1000000000; + const sint64 transferShareCount = totalShareCount/4; + + // make sure the entities have enough qu + increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(USER2, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(USER3, test.qxFees.assetIssuanceFee * 10); + + // issueAsset with QX + EXPECT_EQ(test.issueAssetQx(asset1, totalShareCount, 0, 0), totalShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount); + + // run ownership/possession transfer in QX -> should work (has management rights from issueAsset) + EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, asset1.issuer, USER2, transferShareCount), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); + + // run ownership/possession transfer in TESTEXA -> should fail (requires management rights) + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, USER2, USER3, transferShareCount), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + + //////////////////////////////////// + // RELEASE FROM QX TO TESTEXA + + // invoke release of shares in QX to TESTEXA -> fails because default response of TESTEXA is to reject + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostAcquireCounter(0); + + // enable that TESTEXA accepts transfer for 0 fee + test.setPreAcquireSharesOutput(true, 0); + + // invoke release of shares in QX to TESTEXA -> succeed + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostAcquireCounter(1); + checkPreManagementRightsTransferInput( + test.getStateTestExampleA()->getPreAcquireInput(), + { asset1, USER2, USER2, transferShareCount, 0, QX_CONTRACT_INDEX }); + checkPostManagementRightsTransferInput( + test.getStateTestExampleA()->getPostAcquireInput(), + { asset1, USER2, USER2, transferShareCount, 0, QX_CONTRACT_INDEX }); + + // run ownership/possession transfer in TESTEXA -> should work + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, USER2, USER3, transferShareCount), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), transferShareCount); + + // release shares error case: too few shares -> fail + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, 0, TESTEXA_CONTRACT_INDEX, 100), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostAcquireCounter(1); + + // release shares error case: more shares than available -> fail + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, totalShareCount + 1, TESTEXA_CONTRACT_INDEX, 100), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostAcquireCounter(1); + + // release shares error case: fee requested by TESTEXA is too high to release shares (QX expects 0) -> fail + test.setPreAcquireSharesOutput(true, 1000); + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostAcquireCounter(1); + + // release shares error case: fee requested by TESTEXA is invalid + test.setPreAcquireSharesOutput(true, -100); + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); + test.setPreAcquireSharesOutput(true, MAX_AMOUNT + 1000); + EXPECT_EQ(test.transferShareManagementRightsQx(asset1, USER1, transferShareCount, TESTEXA_CONTRACT_INDEX), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostAcquireCounter(1); + + //////////////////////////////////// + // RELEASE FROM TESTEXA TO TESTEXB + + // invoke release of shares in TESTEXA to TESTEXB with fee -> succeed + test.getStateTestExampleB()->checkPostAcquireCounter(0); + sint64 balanceUser3 = getBalance(USER3); + sint64 balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); + sint64 balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); + test.setPreAcquireSharesOutput(true, 1000); + EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, transferShareCount, TESTEXB_CONTRACT_INDEX, 1500), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); + test.getStateTestExampleB()->checkPostAcquireCounter(1); + checkPreManagementRightsTransferInput( + test.getStateTestExampleB()->getPreAcquireInput(), + { asset1, USER3, USER3, transferShareCount, 1500, TESTEXA_CONTRACT_INDEX }); + checkPostManagementRightsTransferInput( + test.getStateTestExampleB()->getPostAcquireInput(), + { asset1, USER3, USER3, transferShareCount, 1000, TESTEXA_CONTRACT_INDEX }); + EXPECT_EQ(getBalance(USER3), balanceUser3 - 1500); + EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 1500 - 1000); + EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 1000); + + //////////////////////////////////// + // RELEASE FROM TESTEXB TO QX + + // run ownership/possession transfer in QX -> should fail (requires management rights) + EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, USER3, USER2, transferShareCount), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); + + // invoke release of shares from TESTEXB to QX with 0 qu -> fails because transfer fee is required by QX + EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, transferShareCount, QX_CONTRACT_INDEX, 0), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); + + // invoke release of shares from TESTEXB to QX with sufficient amount but too many shares -> should fail + EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, totalShareCount, QX_CONTRACT_INDEX, test.qxFees.transferFee), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount); + + // invoke release of shares from TESTEXB to QX with sufficient amount and correct shares -> should work + EXPECT_EQ(test.transferShareManagementRights(asset1, USER3, transferShareCount, QX_CONTRACT_INDEX, test.qxFees.transferFee), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), 0); + + // run ownership/possession transfer in QX -> should work again + EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, USER3, USER2, transferShareCount), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); +} + +TEST(ContractTestEx, QpiAcquireShares) +{ + ContractTestingTestEx test; + + const Asset asset1{ USER1, assetNameFromString("BLURB") }; + const sint64 totalShareCount = 100000000; + const sint64 transferShareCount = totalShareCount / 4; + + // make sure the entities have enough qu + increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(USER2, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(USER3, test.qxFees.assetIssuanceFee * 10); + + // issueAsset with TestExampleA + EXPECT_EQ(test.issueAssetTestExA(asset1, totalShareCount, 0, 0), totalShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount); + + // run ownership/possession transfer in TestExampleA -> should work (has management rights from issueAsset) + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, USER2, transferShareCount), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + + // run ownership/possession transfer in QX -> should fail (requires management rights) + EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset1, USER2, USER3, transferShareCount), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), 0); + + + ////////////////////////////////////////////// + // TESTEXB ACQUIRES FROM TESTEXA + + // TestExampleB tries / fails to acquire management rights (negative shares count) + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, -100, TESTEXA_CONTRACT_INDEX, 0), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (more shares than available) + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount + 1, TESTEXA_CONTRACT_INDEX, 0), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (negative offered fee) + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, -100), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (rejected by TESTEXA) + test.setPreReleaseSharesOutput(false, 0); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 4999), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (requested fee is negative) + test.setPreReleaseSharesOutput(true, -5000); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 5000), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (offered fee lower than requested fee) + test.setPreReleaseSharesOutput(true, 5000); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 4999), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (not enough QU owned to pay fee) + test.setPreReleaseSharesOutput(true, test.qxFees.assetIssuanceFee * 11); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, test.qxFees.assetIssuanceFee * 11), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB tries / fails to acquire management rights (with wrong originator USER3) + test.setPreReleaseSharesOutput(true, 1234); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount, TESTEXA_CONTRACT_INDEX, 1234, USER3), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), 0); + test.getStateTestExampleA()->checkPostReleaseCounter(0); + + // TestExampleB acquires management rights (success) + test.setPreReleaseSharesOutput(true, 1234); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount / 4, TESTEXA_CONTRACT_INDEX, 1239), transferShareCount / 4); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount * 3 / 4); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount / 4); + test.getStateTestExampleA()->checkPostReleaseCounter(1); + checkPreManagementRightsTransferInput( + test.getStateTestExampleA()->getPreReleaseInput(), + { asset1, USER2, USER2, transferShareCount / 4, 1239, TESTEXB_CONTRACT_INDEX }); + checkPostManagementRightsTransferInput( + test.getStateTestExampleA()->getPostReleaseInput(), + { asset1, USER2, USER2, transferShareCount / 4, 1234, TESTEXB_CONTRACT_INDEX }); + + // TestExampleB acquires management rights (success) + test.setPreReleaseSharesOutput(true, 10); + sint64 balanceUser2 = getBalance(USER2); + sint64 balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); + sint64 balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount * 3 / 4, TESTEXA_CONTRACT_INDEX, 15), transferShareCount * 3 / 4); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 0); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount); + test.getStateTestExampleA()->checkPostReleaseCounter(2); + checkPreManagementRightsTransferInput( + test.getStateTestExampleA()->getPreReleaseInput(), + { asset1, USER2, USER2, transferShareCount * 3 / 4, 15, TESTEXB_CONTRACT_INDEX }); + checkPostManagementRightsTransferInput( + test.getStateTestExampleA()->getPostReleaseInput(), + { asset1, USER2, USER2, transferShareCount * 3 / 4, 10, TESTEXB_CONTRACT_INDEX }); + EXPECT_EQ(getBalance(USER2), balanceUser2 - 15); + EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 10); + EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 15 - 10); + + // run ownership/possession transfer in TestExampleB -> should work now (after acquiring management rights) + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, USER2, USER3, transferShareCount / 2), transferShareCount / 2); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount / 2); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount / 2); + + ////////////////////////////////////////////// + // TESTEXA ACQUIRES FROM TESTEXB + + // TestExampleA acquires management rights from TestExampleB of shares owned by USER2 (success) + test.getStateTestExampleB()->checkPostReleaseCounter(0); + test.setPreReleaseSharesOutput(true, 13); + balanceUser2 = getBalance(USER2); + balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); + balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER2, transferShareCount / 10, TESTEXB_CONTRACT_INDEX, 42), transferShareCount / 10); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), transferShareCount / 10); + EXPECT_EQ(numberOfShares(asset1, { USER2, TESTEXB_CONTRACT_INDEX }, { USER2, TESTEXB_CONTRACT_INDEX }), transferShareCount * 4 / 10); + test.getStateTestExampleB()->checkPostReleaseCounter(1); + checkPreManagementRightsTransferInput( + test.getStateTestExampleB()->getPreReleaseInput(), + { asset1, USER2, USER2, transferShareCount / 10, 42, TESTEXA_CONTRACT_INDEX }); + checkPostManagementRightsTransferInput( + test.getStateTestExampleB()->getPostReleaseInput(), + { asset1, USER2, USER2, transferShareCount / 10, 13, TESTEXA_CONTRACT_INDEX }); + EXPECT_EQ(getBalance(USER2), balanceUser2 - 42); + EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 42 - 13); + EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 13); + + // TestExampleA acquires management rights from TestExampleB of shares owned by USER3 (success) + test.setPreReleaseSharesOutput(true, 123); + sint64 balanceUser3 = getBalance(USER3); + balanceTestExA = getBalance(TESTEXA_CONTRACT_ID); + balanceTestExB = getBalance(TESTEXB_CONTRACT_ID); + EXPECT_EQ(test.acquireShareManagementRights(asset1, USER3, transferShareCount * 2 / 10, TESTEXB_CONTRACT_INDEX, 124), transferShareCount * 2 / 10); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXA_CONTRACT_INDEX }, { USER3, TESTEXA_CONTRACT_INDEX }), transferShareCount * 2 / 10); + EXPECT_EQ(numberOfShares(asset1, { USER3, TESTEXB_CONTRACT_INDEX }, { USER3, TESTEXB_CONTRACT_INDEX }), transferShareCount * 3 / 10); + test.getStateTestExampleB()->checkPostReleaseCounter(2); + checkPreManagementRightsTransferInput( + test.getStateTestExampleB()->getPreReleaseInput(), + { asset1, USER3, USER3, transferShareCount * 2 / 10, 124, TESTEXA_CONTRACT_INDEX }); + checkPostManagementRightsTransferInput( + test.getStateTestExampleB()->getPostReleaseInput(), + { asset1, USER3, USER3, transferShareCount * 2 / 10, 123, TESTEXA_CONTRACT_INDEX }); + EXPECT_EQ(getBalance(USER3), balanceUser3 - 124); + EXPECT_EQ(getBalance(TESTEXA_CONTRACT_ID), balanceTestExA + 124 - 123); + EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), balanceTestExB + 123); + + // Some count final checks + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byManagingContract(QX_CONTRACT_INDEX)), 0); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byManagingContract(TESTEXA_CONTRACT_INDEX)), totalShareCount - transferShareCount * 7 / 10); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byManagingContract(TESTEXB_CONTRACT_INDEX)), transferShareCount * 7 / 10); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byOwner(USER1)), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byOwner(USER2)), transferShareCount / 2); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::byOwner(USER3)), transferShareCount / 2); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(QX_CONTRACT_INDEX)), 0); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(TESTEXA_CONTRACT_INDEX)), totalShareCount - transferShareCount * 7 / 10); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byManagingContract(TESTEXB_CONTRACT_INDEX)), transferShareCount * 7 / 10); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(USER1)), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(USER2)), transferShareCount / 2); + EXPECT_EQ(numberOfShares(asset1, AssetOwnershipSelect::any(), AssetPossessionSelect::byPossessor(USER3)), transferShareCount / 2); + EXPECT_EQ(numberOfShares(asset1), totalShareCount); +} + +TEST(ContractTestEx, GetManagementRightsByInvokingOtherContractsRelease) +{ + ContractTestingTestEx test; + + const Asset asset1{ USER1, assetNameFromString("BLURB") }; + const sint64 totalShareCount = 1000000; + const sint64 transferShareCount = totalShareCount / 5; + + // make sure the entities have enough qu + increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(USER2, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(USER3, test.qxFees.assetIssuanceFee * 10); + + // issueAsset with TestExampleA + EXPECT_EQ(test.issueAssetTestExA(asset1, totalShareCount, 0, 0), totalShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount); + + /////////////////////////////////////////////////////////////////////////// + // TESTEXB ACQUIRES FROM TESTEXA BY INVOKING TESTEXA PROCEDURE (QX PATTERN) + + // run ownership/possession transfer to TestExB in TestExampleA -> should work (has management rights from issueAsset) + // (TestExB needs to own/possess shares in order to invoke TestExA for transferring rights to TestExB in the next step) + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, TESTEXB_CONTRACT_ID, transferShareCount), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); + + // Transfer rights to TestExB using the QX approach + // -> Test that we don't get a deadlock in the following case: + // invoke procedure of TestExB, which invokes procedure of TestExA for calling qpi.releaseShares(), which runs + // callback PRE_ACQUIRE_SHARES of TestExB + // Attempt 1: fail due to forbidding by default + EXPECT_EQ(test.getTestExAsShareManagementRightsByInvokingTestExB(asset1, USER1, transferShareCount, 0), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); + + // Attempt 2: allow -> fail because requested fee > offered fee + test.setPreAcquireSharesOutput(true, 13); + EXPECT_EQ(test.getTestExAsShareManagementRightsByInvokingTestExB(asset1, USER1, transferShareCount, 0), 0); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); + + // Attempt 2: allow -> success + EXPECT_EQ(test.getTestExAsShareManagementRightsByInvokingTestExB(asset1, USER1, transferShareCount, 15), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { TESTEXB_CONTRACT_ID, TESTEXB_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, TESTEXB_CONTRACT_INDEX }), transferShareCount); +} + +// Test stopping + cleanup of contract functions execution for recursive function leading to stack overflow +TEST(ContractTestEx, AbortFunction) +{ + ContractTestingTestEx test; + + // Successfully run function + TESTEXA::QueryQpiFunctions_input input{}; + TESTEXA::QueryQpiFunctions_output output{}; + EXPECT_EQ(test.callFunctionOfTestExampleAFromTextExampleB(input, output, true), NoContractError); + + // Check that error handling works when error is supposed to happen + EXPECT_EQ(test.callErrorTriggerFunction(), ContractErrorAllocLocalsFailed); +} + +static id getUser(unsigned long long i) +{ + return id(i, i / 2 + 4, i + 10, i * 3 + 8); +} + +static void concurrentContractFunctionCall(ContractTestingTestEx* test) +{ + // This calls a user function in contract TestExampleA that calls a function in TestExampleB. + // When running concurrently with a management rights transfer, this may trigger a deadlock + // that needs to be resolved. + for (int i = 0; i < 3; ++i) + { + TESTEXA::QueryQpiFunctions_input queryQpiFuncInput; + TESTEXA::QueryQpiFunctions_output qpiReturned; + setMemory(utcTime, 0); + utcTime.Year = 2022; + utcTime.Month = 4; + utcTime.Day = 13; + utcTime.Hour = 12; + updateQpiTime(); + bool expectSuccess = false; + unsigned int errorCode = test->callFunctionOfTestExampleAFromTextExampleB(queryQpiFuncInput, qpiReturned, expectSuccess); + ASSERT(errorCode == ContractErrorStoppedToResolveDeadlock || errorCode == NoContractError); + if (errorCode == NoContractError) + { + EXPECT_EQ(qpiReturned.qpiFunctionsOutput.year, 22); + EXPECT_EQ(qpiReturned.qpiFunctionsOutput.month, 4); + EXPECT_EQ(qpiReturned.qpiFunctionsOutput.day, 13); + EXPECT_EQ(qpiReturned.qpiFunctionsOutput.hour, 12); + } + } +} + +TEST(ContractTestEx, ResolveDeadlockCallbackProcedureAndConcurrentFunction) +{ + // deadlock pattern (with index of TestExC > TestExB > TestExA): + // 1. TestExC procedure invokes TestExA procedure, which runs qpi.releaseShares() to TestExC leading to a call of + // PRE_ACQUIRE_SHARES in TestExC + // -> solution to resolve deadlock: reusing lock + // 2. PRE_ACQUIRE_SHARES in TestExC invokes a TestExA procedure (this runs a lot of computation to wait for + // concurrent execution of contract function needed for test 3) + // -> solution to resolve deadlock: reusing lock + // 3. PRE_ACQUIRE_SHARES in TestExC tries to invoke a procedure of TestExB; this leads to a deadlock if a contract + // function of TextExB is running in parallel that tries to run a function of TextExA: + // -> contract function of TestExB running in request processor is waiting for a read lock of TestExA + // -> read lock of TestExA cannot be acquired before qpi.releaseShares() returns, which it doesn't because + // PRE_ACQUIRE_SHARES is waiting for a write lock of TestExB, which cannot be acquired before the contract + // function of TestExB is finished + // -> solution to resolve deadlock: cancel contract function of TestExB + + ContractTestingTestEx test; + + const Asset asset1{ USER1, assetNameFromString("WOBBL") }; + const sint64 totalShareCount = 100000000; + const int numberOfUsers = 500; + const sint64 transferShareCount = totalShareCount / numberOfUsers / 10; + + // populate spectrum + increaseEnergy(USER1, test.qxFees.assetIssuanceFee * 10); + increaseEnergy(TESTEXC_CONTRACT_ID, test.qxFees.assetIssuanceFee * 10); + for (int i = 0; i < numberOfUsers; ++i) + increaseEnergy(getUser(i), test.qxFees.assetIssuanceFee * (i % 1000 + 1)); + + // issueAsset with TestExampleA + EXPECT_EQ(test.issueAssetTestExA(asset1, totalShareCount, 0, 0), totalShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount); + + // run ownership/possession transfer to TestExC in TestExA (needed to run deadlock test below) + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, TESTEXC_CONTRACT_ID, transferShareCount), transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { USER1, TESTEXA_CONTRACT_INDEX }, { USER1, TESTEXA_CONTRACT_INDEX }), totalShareCount - transferShareCount); + EXPECT_EQ(numberOfShares(asset1, { TESTEXC_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }, { TESTEXC_CONTRACT_ID, TESTEXA_CONTRACT_INDEX }), transferShareCount); + + // populate universe + for (int i = 0; i < numberOfUsers; ++i) + EXPECT_EQ(test.transferShareOwnershipAndPossession(asset1, asset1.issuer, getUser(i), transferShareCount), transferShareCount); + + // start procedure call for deadlock test (baseline without concurrent function call) + { + std::cout << "Test callback procedure without concurrent function ..." << std::endl; + auto startTime = std::chrono::high_resolution_clock::now(); + test.getTestExAsShareManagementRightsByInvokingTestExC(asset1, TESTEXC_CONTRACT_ID, 1, 0); + auto endTime = std::chrono::high_resolution_clock::now(); + auto durationMilliSec = std::chrono::duration_cast(endTime - startTime).count(); + std::cout << "Run-time of procedure without concurrent function: " << durationMilliSec << " milliseconds\n" << std::endl; + } + + // start function call (baseline without concurrent procedure call) + { + std::cout << "Test function without concurrent procedure ..." << std::endl; + auto startTime = std::chrono::high_resolution_clock::now(); + TESTEXA::QueryQpiFunctions_input input; + TESTEXA::QueryQpiFunctions_output output; + test.callFunctionOfTestExampleAFromTextExampleB(input, output, true); + auto endTime = std::chrono::high_resolution_clock::now(); + auto durationMilliSec = std::chrono::duration_cast(endTime - startTime).count(); + std::cout << "Run-time of function without concurrent procedure: " << durationMilliSec << " milliseconds\n" << std::endl; + } + + // deadlock test with procedure and concurrent function call + for (int i = 0; i < 3; ++i) + { + std::cout << "Test callback procedure with concurrent function ..." << std::endl; + auto startTime = std::chrono::high_resolution_clock::now(); + + auto lambda = [](ContractTestingTestEx* test, const Asset& asset1) { test->getTestExAsShareManagementRightsByInvokingTestExC(asset1, TESTEXC_CONTRACT_ID, 1, 0); }; + + auto userProcedureThread = std::thread(lambda, &test, asset1); + auto userFunctionThread = std::thread(concurrentContractFunctionCall, &test); + + userProcedureThread.join(); + userFunctionThread.join(); + + auto endTime = std::chrono::high_resolution_clock::now(); + auto durationMilliSec = std::chrono::duration_cast(endTime - startTime).count(); + std::cout << "Run-time of procedure with concurrent function: " << durationMilliSec << " milliseconds\n" << std::endl; + } +} + +TEST(ContractTestEx, QueryBasicQpiFunctions) +{ + ContractTestingTestEx test; + + // some simple QPI functions tests that are independent of the tick + test.beginEpoch(); + + id arbitratorPubKey; + getPublicKeyFromIdentity((const unsigned char*)ARBITRATOR, arbitratorPubKey.m256i_u8); + + // prepare data for qpi.K12() and qpi.signatureValidity() + TESTEXA::QueryQpiFunctions_input queryQpiFuncInput1; + id subseed1(123456789, 987654321, 1357986420, 0xabcdef); + id privateKey1, digest1; + getPrivateKey(subseed1.m256i_u8, privateKey1.m256i_u8); + getPublicKey(privateKey1.m256i_u8, queryQpiFuncInput1.entity.m256i_u8); + for (uint64 i = 0; i < queryQpiFuncInput1.data.capacity(); ++i) + queryQpiFuncInput1.data.set(i, static_cast(i - 50)); + KangarooTwelve(&queryQpiFuncInput1.data, sizeof(queryQpiFuncInput1.data), &digest1, sizeof(digest1)); + sign(subseed1.m256i_u8, queryQpiFuncInput1.entity.m256i_u8, digest1.m256i_u8, (unsigned char*)&queryQpiFuncInput1.signature); + + // Test 1 with initial time + setMemory(utcTime, 0); + utcTime.Year = 2022; + utcTime.Month = 4; + utcTime.Day = 13; + utcTime.Hour = 12; + updateQpiTime(); + numberTickTransactions = 123; + system.tick = 4567890; + system.epoch = 987; + auto qpiReturned1 = test.queryQpiFunctions(queryQpiFuncInput1); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.year, 22); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.month, 4); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.day, 13); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.hour, 12); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.minute, 0); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.second, 0); + EXPECT_EQ((int)qpiReturned1.qpiFunctionsOutput.millisecond, 0); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.dayOfWeek, 0); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.arbitrator, arbitratorPubKey); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.computor0, id::zero()); + EXPECT_EQ((int)qpiReturned1.qpiFunctionsOutput.epoch, (int)system.epoch); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.invocationReward, 0); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.invocator, id::zero()); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.numberOfTickTransactions, numberTickTransactions); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.originator, id::zero()); + EXPECT_EQ(qpiReturned1.qpiFunctionsOutput.tick, system.tick); + EXPECT_EQ(qpiReturned1.inputDataK12, digest1); + EXPECT_TRUE(qpiReturned1.inputSignatureValid); + + // K12 test for wrong signature: change data but not signature + TESTEXA::QueryQpiFunctions_input queryQpiFuncInput2 = queryQpiFuncInput1; + id digest2; + queryQpiFuncInput2.data.set(0, 0); + KangarooTwelve(&queryQpiFuncInput2.data, sizeof(queryQpiFuncInput2.data), &digest2, sizeof(digest2)); + EXPECT_NE(digest1, digest2); + + // Test 2 with current time + updateTime(); + updateQpiTime(); + numberTickTransactions = 42; + system.tick = 4567891; + system.epoch = 988; + broadcastedComputors.computors.publicKeys[0] = id(12, 34, 56, 78); + auto qpiReturned2 = test.queryQpiFunctions(queryQpiFuncInput2); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.year, utcTime.Year - 2000); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.month, utcTime.Month); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.day, utcTime.Day); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.hour, utcTime.Hour); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.minute, utcTime.Minute); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.second, utcTime.Second); + EXPECT_EQ((int)qpiReturned2.qpiFunctionsOutput.millisecond, utcTime.Nanosecond / 1000000); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.dayOfWeek, dayIndex(qpiReturned2.qpiFunctionsOutput.year, qpiReturned2.qpiFunctionsOutput.month, qpiReturned2.qpiFunctionsOutput.day) % 7); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.arbitrator, arbitratorPubKey); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.computor0, id(12, 34, 56, 78)); + EXPECT_EQ((int)qpiReturned2.qpiFunctionsOutput.epoch, (int)system.epoch); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.invocationReward, 0); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.invocator, id::zero()); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.numberOfTickTransactions, numberTickTransactions); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.originator, id::zero()); + EXPECT_EQ(qpiReturned2.qpiFunctionsOutput.tick, system.tick); + EXPECT_EQ(qpiReturned2.inputDataK12, digest2); + EXPECT_FALSE(qpiReturned2.inputSignatureValid); +} + +TEST(ContractTestEx, QpiFunctionsIPO) +{ + // test IPO functions with IPO of TESTEXD + ContractTestingTestEx test; + system.epoch = contractDescriptions[TESTEXD_CONTRACT_INDEX].constructionEpoch - 1; + constexpr long long initialBalance = 12345678; + increaseEnergy(USER1, initialBalance); + increaseEnergy(TESTEXB_CONTRACT_ID, initialBalance); + increaseEnergy(TESTEXC_CONTRACT_ID, initialBalance); + + // Test output of qpi functions for invalid contract index + for (int i = 0; i < NUMBER_OF_COMPUTORS + 2; ++i) + { + const auto bid = test.getIpoBid(contractCount, i); + EXPECT_TRUE(isZero(bid.publicKey)); + EXPECT_EQ(bid.price, -1); + } + + // Test output of qpi functions for contract that is not in IPO + for (int i = 0; i < NUMBER_OF_COMPUTORS + 2; ++i) + { + const auto bid = test.getIpoBid(TESTEXB_CONTRACT_INDEX, i); + EXPECT_TRUE(isZero(bid.publicKey)); + EXPECT_EQ(bid.price, -2); + } + + // Test output of qpi functions without any bids + for (int i = 0; i < NUMBER_OF_COMPUTORS + 2; ++i) + { + const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); + EXPECT_TRUE(isZero(bid.publicKey)); + EXPECT_EQ(bid.price, (i < NUMBER_OF_COMPUTORS) ? 0 : -3); + } + + // Test bids with invalid contract, price, and quantity + EXPECT_EQ(test.qpiBidInIpo(contractCount, 10, 100), -1); + EXPECT_EQ(test.qpiBidInIpo(TESTEXC_CONTRACT_INDEX, 10, 100), -1); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 0, 100), -1); + EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, MAX_AMOUNT, 100), -1); + EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 10, 0), -1); + EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 10, NUMBER_OF_COMPUTORS + 1), -1); + EXPECT_EQ(test.getIpoBid(TESTEXD_CONTRACT_INDEX, 0).price, 0); + + // Successfully bid in IPO + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 10, 100), 100); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); + EXPECT_EQ(bid.publicKey, (i < 100) ? TESTEXC_CONTRACT_ID : NULL_ID); + EXPECT_EQ(bid.price, (i < 100) ? 10 : 0); + } + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 100, 600), 600); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); + EXPECT_EQ(bid.publicKey, (i < 600) ? TESTEXB_CONTRACT_ID : TESTEXC_CONTRACT_ID); + EXPECT_EQ(bid.price, (i < 600) ? 100 : 10); + } + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 1000, 10), 10); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); + if (i < 10) + { + EXPECT_EQ(bid.publicKey, TESTEXC_CONTRACT_ID); + EXPECT_EQ(bid.price, 1000); + } + else if (i < 10 + 600) + { + EXPECT_EQ(bid.publicKey, TESTEXB_CONTRACT_ID); + EXPECT_EQ(bid.price, 100); + } + else + { + EXPECT_EQ(bid.publicKey, TESTEXC_CONTRACT_ID); + EXPECT_EQ(bid.price, 10); + } + } + + // Test too low bids + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 1, 10), 0); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 2, 10), 0); + + // Simulate end of IPO + finishIPOs(); + + // Check contract shares + Asset asset{NULL_ID, assetNameFromString("TESTEXD")}; + EXPECT_EQ(600, numberOfShares(asset, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX })); + EXPECT_EQ(76, numberOfShares(asset, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX })); + + // Check balances + const long long finalPrice = 10; + EXPECT_EQ(getBalance(TESTEXB_CONTRACT_ID), initialBalance - 600 * finalPrice); + EXPECT_EQ(getBalance(TESTEXC_CONTRACT_ID), initialBalance - 76 * finalPrice); +} + +//------------------------------------------------------------------- +// Test CallbackPostIncomingTransfer + +class ContractTestCallbackPostIncomingTransfer : public ContractTestingTestEx +{ +public: + // test qpi.transfer() on contract SrcStateStruct. DstStateStruct is another contract to check. + template + void testQpiTransfer(const id& dstPublicKey, sint64 amount, sint64 fee = 0, const id& originator = USER1) + { + const id srcPublicKey(SrcStateStruct::__contract_index, 0, 0, 0); + const sint64 originatorBalanceBefore = getBalance(originator); + const sint64 srcBalanceBefore = getBalance(srcPublicKey); + const sint64 dstBalanceBefore = getBalance(dstPublicKey); + const auto srcBefore = getIncomingTransferAmounts(); + const auto dstBefore = getIncomingTransferAmounts(); + + EXPECT_GE(originatorBalanceBefore, fee); + bool success = qpiTransfer(dstPublicKey, amount, fee, originator); + EXPECT_TRUE(success); + + if (success) + { + const sint64 originatorBalanceAfter = getBalance(originator); + const sint64 srcBalanceAfter = getBalance(srcPublicKey); + const sint64 dstBalanceAfter = getBalance(dstPublicKey); + EXPECT_EQ(originatorBalanceAfter, originatorBalanceBefore - fee); + if (srcPublicKey != dstPublicKey) + { + EXPECT_EQ(srcBalanceAfter, srcBalanceBefore + fee - amount); + EXPECT_EQ(dstBalanceAfter, dstBalanceBefore + amount); + } + else + { + EXPECT_EQ(srcBalanceAfter, srcBalanceBefore + fee); + } + + const auto srcAfter = getIncomingTransferAmounts(); + const auto dstAfter = getIncomingTransferAmounts(); + EXPECT_EQ(srcAfter.procedureTransactionAmount, srcBefore.procedureTransactionAmount + fee); + if (srcPublicKey != dstPublicKey) + { + EXPECT_EQ(dstAfter.procedureTransactionAmount, dstBefore.procedureTransactionAmount); + EXPECT_EQ(srcAfter.qpiTransferAmount, srcBefore.qpiTransferAmount); + } + if (dstPublicKey == id(DstStateStruct::__contract_index, 0, 0, 0)) + { + EXPECT_EQ(dstAfter.qpiTransferAmount, dstBefore.qpiTransferAmount + amount); + } + else + { + EXPECT_EQ(dstAfter.qpiTransferAmount, dstBefore.qpiTransferAmount); + } + EXPECT_EQ(srcAfter.standardTransactionAmount, srcBefore.standardTransactionAmount); + EXPECT_EQ(dstAfter.standardTransactionAmount, dstBefore.standardTransactionAmount); + EXPECT_EQ(srcAfter.qpiDistributeDividendsAmount, srcBefore.qpiDistributeDividendsAmount); + EXPECT_EQ(dstAfter.qpiDistributeDividendsAmount, dstBefore.qpiDistributeDividendsAmount); + EXPECT_EQ(srcAfter.revenueDonationAmount, srcBefore.revenueDonationAmount); + EXPECT_EQ(dstAfter.revenueDonationAmount, dstBefore.revenueDonationAmount); + EXPECT_EQ(srcAfter.ipoBidRefundAmount, srcBefore.ipoBidRefundAmount); + EXPECT_EQ(dstAfter.ipoBidRefundAmount, dstBefore.ipoBidRefundAmount); + } + } + + // test qpi.distributeDividends() on contract SrcStateStruct. DstStateStruct is another contract to check. + template + void testQpiDistributeDividends(sint64 amountPerShare, const std::vector>& shareholders, sint64 fee = 0, const id& originator = USER1) + { + // check number of shares + unsigned int totalShareCount = 0; + for (const auto& ownerShareCountPair : shareholders) + totalShareCount += ownerShareCountPair.second; + EXPECT_EQ(totalShareCount, NUMBER_OF_COMPUTORS); + + // get state before call and compute state expected after call + const id srcPublicKey(SrcStateStruct::__contract_index, 0, 0, 0); + const id dstPublicKey(DstStateStruct::__contract_index, 0, 0, 0); + std::map expectedBalances; + expectedBalances[originator] = getBalance(originator); + EXPECT_GE(expectedBalances[originator], fee); + expectedBalances[srcPublicKey] = getBalance(srcPublicKey); + expectedBalances[dstPublicKey] = getBalance(dstPublicKey); + for (const auto& ownerShareCountPair : shareholders) + expectedBalances[ownerShareCountPair.first] = getBalance(ownerShareCountPair.first); + expectedBalances[originator] -= fee; + expectedBalances[srcPublicKey] += fee - amountPerShare * NUMBER_OF_COMPUTORS; + auto expectedIncomingSrc = getIncomingTransferAmounts(); + auto expectedIncomingDst = getIncomingTransferAmounts(); + expectedIncomingSrc.procedureTransactionAmount += fee; + if (srcPublicKey == dstPublicKey) + expectedIncomingDst.procedureTransactionAmount += fee; + for (const auto& ownerShareCountPair : shareholders) + { + const sint64 dividend = amountPerShare * ownerShareCountPair.second; + expectedBalances[ownerShareCountPair.first] += dividend; + if (ownerShareCountPair.first == srcPublicKey) + expectedIncomingSrc.qpiDistributeDividendsAmount += dividend; + if (ownerShareCountPair.first == dstPublicKey) + expectedIncomingDst.qpiDistributeDividendsAmount += dividend; + } + + bool success = qpiDistributeDividends(amountPerShare, fee, originator); + EXPECT_TRUE(success); + + if (success) + { + for (const auto& idBalancePair : expectedBalances) + { + EXPECT_EQ(getBalance(idBalancePair.first), idBalancePair.second); + } + + const auto observedIncomingSrc = getIncomingTransferAmounts(); + const auto observedIncomingDst = getIncomingTransferAmounts(); + EXPECT_EQ(expectedIncomingSrc.standardTransactionAmount, observedIncomingSrc.standardTransactionAmount); + EXPECT_EQ(expectedIncomingDst.standardTransactionAmount, observedIncomingDst.standardTransactionAmount); + EXPECT_EQ(expectedIncomingSrc.procedureTransactionAmount, observedIncomingSrc.procedureTransactionAmount); + EXPECT_EQ(expectedIncomingDst.procedureTransactionAmount, observedIncomingDst.procedureTransactionAmount); + EXPECT_EQ(expectedIncomingSrc.qpiTransferAmount, observedIncomingSrc.qpiTransferAmount); + EXPECT_EQ(expectedIncomingDst.qpiTransferAmount, observedIncomingDst.qpiTransferAmount); + EXPECT_EQ(expectedIncomingSrc.qpiDistributeDividendsAmount, observedIncomingSrc.qpiDistributeDividendsAmount); + EXPECT_EQ(expectedIncomingDst.qpiDistributeDividendsAmount, observedIncomingDst.qpiDistributeDividendsAmount); + EXPECT_EQ(expectedIncomingSrc.revenueDonationAmount, observedIncomingSrc.revenueDonationAmount); + EXPECT_EQ(expectedIncomingDst.revenueDonationAmount, observedIncomingDst.revenueDonationAmount); + EXPECT_EQ(expectedIncomingSrc.ipoBidRefundAmount, observedIncomingSrc.ipoBidRefundAmount); + EXPECT_EQ(expectedIncomingDst.ipoBidRefundAmount, observedIncomingDst.ipoBidRefundAmount); + } + } +}; + +TEST(ContractTestEx, CallbackPostIncomingTransfer) +{ + // Tested types of incoming transfers (should be also tested in testnet): + // - TransferType::qpiTransfer (including transfer to oneself) + // - TransferType::qpiDistributeDividends (including dividends to oneself) + // - TransferType::procedureTransaction + // - TransferType::ipoBidRefund through qpi.bidInIpo() + // + // Important test: triggering callback from callback must be prevented (checked by ASSERTs in contracts) + // + // The following cannot be tested with Google Test at the moment and have to be tested in the testnet. + // - TransferType::standardTransaction + // - TransferType::revenueDonation + // - TransferType::ipoBidRefund through transaction + ContractTestCallbackPostIncomingTransfer test; + + increaseEnergy(USER1, 12345678); + increaseEnergy(USER2, 31427); + increaseEnergy(USER3, 218000); + increaseEnergy(TESTEXB_CONTRACT_ID, 19283764); + increaseEnergy(TESTEXC_CONTRACT_ID, 987654321); + + // qpi.transfer() to other contract + test.testQpiTransfer(TESTEXC_CONTRACT_ID, 100, 1000, USER1); + test.testQpiTransfer(TESTEXB_CONTRACT_ID, 2000, 200, USER1); + test.testQpiTransfer(TESTEXB_CONTRACT_ID, 300, 3000, USER1); + test.testQpiTransfer(TESTEXC_CONTRACT_ID, 4000, 400, USER1); + + // qpi.transfer() to self + test.testQpiTransfer(TESTEXB_CONTRACT_ID, 50, 500, USER1); + test.testQpiTransfer(TESTEXB_CONTRACT_ID, 600, 60, USER1); + test.testQpiTransfer(TESTEXC_CONTRACT_ID, 700, 7000, USER1); + test.testQpiTransfer(TESTEXC_CONTRACT_ID, 8000, 800, USER1); + + // qpi.transfer() to non-contract entity + test.testQpiTransfer(getUser(0), 900, 9000, USER1); + test.testQpiTransfer(getUser(1), 10000, 1000, USER1); + test.testQpiTransfer(getUser(2), 11000, 1100, USER1); + test.testQpiTransfer(getUser(3), 12000, 1200, USER1); + + // issue contract shares + std::vector> sharesTestExB{ {USER1, 356}, {TESTEXC_CONTRACT_ID, 200}, {TESTEXB_CONTRACT_ID, 100}, {TESTEXA_CONTRACT_ID, 20} }; + issueContractShares(TESTEXB_CONTRACT_INDEX, sharesTestExB); + std::vector> sharesTestExC{ {USER2, 576}, {USER3, 40}, {TESTEXC_CONTRACT_ID, 30}, {TESTEXB_CONTRACT_ID, 20}, {TESTEXA_CONTRACT_ID, 10} }; + issueContractShares(TESTEXC_CONTRACT_INDEX, sharesTestExC); + + // test qpi.distributeDividends() + test.testQpiDistributeDividends(1, sharesTestExB, 1234, USER1); + test.testQpiDistributeDividends(11, sharesTestExB, 9764, USER2); + test.testQpiDistributeDividends(3, sharesTestExB, 42, USER3); + test.testQpiDistributeDividends(2, sharesTestExC, 12345, USER1); + test.testQpiDistributeDividends(13, sharesTestExC, 98, USER2); + test.testQpiDistributeDividends(4, sharesTestExC, 9, USER3); + + // test refund in qpi.bidInIPO() and finalizeIpo() + system.epoch = contractDescriptions[TESTEXD_CONTRACT_INDEX].constructionEpoch - 1; + auto itaB1 = test.getIncomingTransferAmounts(); + auto itaC1 = test.getIncomingTransferAmounts(); + EXPECT_EQ(itaB1.ipoBidRefundAmount, 0); + EXPECT_EQ(itaC1.ipoBidRefundAmount, 0); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 20, NUMBER_OF_COMPUTORS, 42), NUMBER_OF_COMPUTORS); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 30, NUMBER_OF_COMPUTORS * 3 / 4, 13), NUMBER_OF_COMPUTORS * 3 / 4); + auto itaB2 = test.getIncomingTransferAmounts(); + auto itaC2 = test.getIncomingTransferAmounts(); + // -> in 75% 30 (C), in 25% 20 (B), refund 75% 20 (B) + EXPECT_EQ(itaB2.ipoBidRefundAmount, 20 * NUMBER_OF_COMPUTORS * 3 / 4); + EXPECT_EQ(itaC2.ipoBidRefundAmount, 0); + EXPECT_EQ(itaB2.procedureTransactionAmount, itaB1.procedureTransactionAmount + 42); + EXPECT_EQ(itaC2.procedureTransactionAmount, itaC1.procedureTransactionAmount + 13); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 50, NUMBER_OF_COMPUTORS / 2, 3), NUMBER_OF_COMPUTORS / 2); + auto itaB3 = test.getIncomingTransferAmounts(); + auto itaC3 = test.getIncomingTransferAmounts(); + // -> in 50% 50 (C), in 50% 30 (C), ex 25% 30 (C), ex 25% 20 (B) + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); + EXPECT_EQ(bid.publicKey, TESTEXC_CONTRACT_ID); + EXPECT_EQ(bid.price, (i < NUMBER_OF_COMPUTORS / 2) ? 50 : 30); + } + EXPECT_EQ(itaB3.ipoBidRefundAmount, itaB2.ipoBidRefundAmount + 20 * NUMBER_OF_COMPUTORS / 4); + EXPECT_EQ(itaC3.ipoBidRefundAmount, itaC2.ipoBidRefundAmount + 30 * NUMBER_OF_COMPUTORS / 4); + EXPECT_EQ(itaB3.procedureTransactionAmount, itaB2.procedureTransactionAmount); + EXPECT_EQ(itaC3.procedureTransactionAmount, itaC2.procedureTransactionAmount + 3); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 99, NUMBER_OF_COMPUTORS * 3 / 4, 14), NUMBER_OF_COMPUTORS * 3 / 4); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 9, NUMBER_OF_COMPUTORS / 2, 123), 0); + EXPECT_EQ(test.qpiBidInIpo(TESTEXD_CONTRACT_INDEX, 60, NUMBER_OF_COMPUTORS, 654), NUMBER_OF_COMPUTORS / 4); + auto itaB4 = test.getIncomingTransferAmounts(); + auto itaC4 = test.getIncomingTransferAmounts(); + // -> in 75% 99 (B), in 25% 60 (C), ex 75% 60 (C), ex 50% 50 (C), ex 50% 30 (C), ex 50% 9 (B) + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + const auto bid = test.getIpoBid(TESTEXD_CONTRACT_INDEX, i); + EXPECT_EQ(bid.publicKey, (i < NUMBER_OF_COMPUTORS * 3 / 4) ? TESTEXB_CONTRACT_ID : TESTEXC_CONTRACT_ID); + EXPECT_EQ(bid.price, (i < NUMBER_OF_COMPUTORS * 3 / 4) ? 99 : 60); + } + EXPECT_EQ(itaB4.ipoBidRefundAmount, itaB3.ipoBidRefundAmount + 9 * NUMBER_OF_COMPUTORS / 2); + EXPECT_EQ(itaC4.ipoBidRefundAmount, itaC3.ipoBidRefundAmount + 60 * NUMBER_OF_COMPUTORS * 3 / 4 + 50 * NUMBER_OF_COMPUTORS / 2 + 30 * NUMBER_OF_COMPUTORS / 2); + EXPECT_EQ(itaB4.procedureTransactionAmount, itaB3.procedureTransactionAmount + 14 + 123); + EXPECT_EQ(itaC4.procedureTransactionAmount, itaC3.procedureTransactionAmount + 654); + + // simulate end of IPO + finishIPOs(); + + // check contract shares + Asset asset{ NULL_ID, assetNameFromString("TESTEXD") }; + EXPECT_EQ(NUMBER_OF_COMPUTORS * 3 / 4, numberOfShares(asset, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX })); + EXPECT_EQ(NUMBER_OF_COMPUTORS * 1 / 4, numberOfShares(asset, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXC_CONTRACT_ID, QX_CONTRACT_INDEX })); + + // check refunds (finalPrice = 60) + auto itaB5 = test.getIncomingTransferAmounts(); + auto itaC5 = test.getIncomingTransferAmounts(); + EXPECT_EQ(itaB5.ipoBidRefundAmount, itaB4.ipoBidRefundAmount + NUMBER_OF_COMPUTORS * 3 / 4 * (99 - 60)); + EXPECT_EQ(itaC5.ipoBidRefundAmount, itaC4.ipoBidRefundAmount); + EXPECT_EQ(itaB5.procedureTransactionAmount, itaB4.procedureTransactionAmount); + EXPECT_EQ(itaC5.procedureTransactionAmount, itaC4.procedureTransactionAmount); + EXPECT_EQ(itaB5.standardTransactionAmount, itaB1.standardTransactionAmount); + EXPECT_EQ(itaC5.standardTransactionAmount, itaC1.standardTransactionAmount); + EXPECT_EQ(itaB5.qpiDistributeDividendsAmount, itaB1.qpiDistributeDividendsAmount); + EXPECT_EQ(itaC5.qpiDistributeDividendsAmount, itaC1.qpiDistributeDividendsAmount); + EXPECT_EQ(itaB5.qpiTransferAmount, itaB1.qpiTransferAmount); + EXPECT_EQ(itaC5.qpiTransferAmount, itaC1.qpiTransferAmount); + EXPECT_EQ(itaB5.revenueDonationAmount, itaB1.revenueDonationAmount); + EXPECT_EQ(itaC5.revenueDonationAmount, itaC1.revenueDonationAmount); +} + +TEST(ContractTestEx, BurnAssets) +{ + ContractTestCallbackPostIncomingTransfer test; + + increaseEnergy(USER1, 1234567890123llu); + increaseEnergy(TESTEXB_CONTRACT_ID, 19283764); + + { + // issue contract shares + Asset asset{ NULL_ID, assetNameFromString("TESTEXB") }; + std::vector> sharesTestExB{ {USER1, 356}, {TESTEXC_CONTRACT_ID, 200}, {TESTEXB_CONTRACT_ID, 100}, {TESTEXA_CONTRACT_ID, 20} }; + issueContractShares(TESTEXB_CONTRACT_INDEX, sharesTestExB); + EXPECT_EQ(356, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); + + // burning contract shares is supposed to fail + EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset, USER1, NULL_ID, 100), 0); + EXPECT_EQ(356, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); + } + + { + // issue non-contract asset shares + Asset asset{ USER1, assetNameFromString("BLOB") }; + EXPECT_EQ(test.issueAssetQx(asset, 1000000, 0, 0), 1000000); + EXPECT_EQ(1000000, numberOfShares(asset)); + EXPECT_EQ(1000000, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX })); + EXPECT_EQ(1000000, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); + + // burn non-contract shares + EXPECT_EQ(test.transferShareOwnershipAndPossessionQx(asset, USER1, NULL_ID, 100), 100); + EXPECT_EQ(1000000 - 100, numberOfShares(asset)); + EXPECT_EQ(1000000 - 100, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX })); + EXPECT_EQ(1000000 - 100, numberOfShares(asset, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX })); + } +} + +TEST(ContractTestEx, ShareholderProposals) +{ + ContractTestingTestEx test; + uint16 proposalIdx = 0; + + system.epoch = 200; + + increaseEnergy(USER1, 12345678); + increaseEnergy(USER2, 31427); + increaseEnergy(USER3, 218000); + increaseEnergy(USER4, 218000); + increaseEnergy(TESTEXA_CONTRACT_ID, 987654321); + increaseEnergy(TESTEXB_CONTRACT_ID, 19283764); + + // issue contract shares + std::vector> sharesTestExA{ + {USER1, 356}, + {USER2, 200}, + {TESTEXB_CONTRACT_ID, 100}, + {USER3, 20} + }; + issueContractShares(TESTEXA_CONTRACT_INDEX, sharesTestExA); + + // enable that TESTEXA accepts transfer for 0 fee + test.setPreAcquireSharesOutput(true, 0); + + // transfer management rights of some shares to other contract to cover case of multiple asset records of single possessor + const Asset TESTEXA_ASSET{ NULL_ID, assetNameFromString("TESTEXA") }; + EXPECT_EQ(test.transferShareManagementRightsQx(TESTEXA_ASSET, USER2, 50, TESTEXA_CONTRACT_INDEX), 50); + EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), 356); + EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 150); + EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER2, TESTEXA_CONTRACT_INDEX }, { USER2, TESTEXA_CONTRACT_INDEX }), 50); + EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXB_CONTRACT_ID, QX_CONTRACT_INDEX }), 100); + EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 20); + EXPECT_EQ(numberOfShares(TESTEXA_ASSET, { USER4, QX_CONTRACT_INDEX }, { USER4, QX_CONTRACT_INDEX }), 0); + + // fail: 4 options not supported with Yes/No proposals + test.setupShareholderProposalTestExA(USER2, ProposalTypes::FourOptions, false, 0, false, 0, false, 0, false); + + // fail: no right, because no shareholder + test.setupShareholderProposalTestExA(USER4, ProposalTypes::ThreeOptions, false, 0, false, 0, false, 0, false); + + // fail: transfer not allowed + test.setupShareholderProposalTestExA(USER2, ProposalTypes::TransferYesNo, false, 0, false, 0, false, 0, false); + + // fail: invalid value of variable + test.setupShareholderProposalTestExA(USER2, ProposalTypes::VariableYesNo, false, 0, false, 0, true, 120, false); + + // check that no active/inactive proposals + EXPECT_EQ(test.getShareholderProposalIndices(true).size(), 0); + EXPECT_EQ(test.getShareholderProposalIndices(false).size(), 0); + + // success: set var3 with single-var proposal + proposalIdx = test.setupShareholderProposalTestExA(USER2, ProposalTypes::VariableYesNo, false, 0, false, 0, true, 100, true); + + // check that no active/inactive proposals + auto proposalIndices = test.getShareholderProposalIndices(true); + EXPECT_TRUE(proposalIndices.size() == 1 && proposalIndices[0] == proposalIdx); + EXPECT_EQ(test.getShareholderProposalIndices(false).size(), 0); + + // fail: try to get non-existing proposal + auto fullProposalData = test.getShareholderProposal(proposalIdx + 1); + EXPECT_EQ(fullProposalData.proposerPubicKey, NULL_ID); + EXPECT_EQ((int)fullProposalData.proposal.type, 0); + + // success: get existing proposal + fullProposalData = test.getShareholderProposal(proposalIdx); + EXPECT_EQ(fullProposalData.proposerPubicKey, USER2); + EXPECT_EQ((int)fullProposalData.proposal.type, (int)ProposalTypes::VariableYesNo); + auto proposal = fullProposalData.proposal; + + // fail: try to get shareholder votes of user who is no shareholder + auto votes = test.getShareholderVotes(proposalIdx, USER4); + EXPECT_EQ((int)votes.proposalType, 0); + + // fail: try to get shareholder votes of non-existing proposal + votes = test.getShareholderVotes(proposalIdx + 1, USER1); + EXPECT_EQ((int)votes.proposalType, 0); + + // success: get shareholder votes of user who is no shareholder + votes = test.getShareholderVotes(proposalIdx, USER1); + EXPECT_EQ((int)votes.proposalType, (int)proposal.type); + EXPECT_EQ((int)votes.proposalIndex, (int)proposalIdx); + EXPECT_EQ(votes.proposalTick, proposal.tick); + checkVoteCounts(votes, {}); + + // set all votes of USER1 to option 0 with single-vote struct + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx, proposal, 0)); + + // get shareholder votes of user who is no shareholder and check that they are correct + votes = test.getShareholderVotes(proposalIdx, USER1); + EXPECT_EQ((int)votes.proposalType, (int)proposal.type); + checkVoteCounts(votes, { {0, 356} }); + + // set 50 votes of USER2 to option 0 and 150 to option 1 + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {0, 50}, {1, 150} })); + votes = test.getShareholderVotes(proposalIdx, USER2); + EXPECT_EQ((int)votes.proposalType, (int)proposal.type); + checkVoteCounts(votes, { {0, 50}, {1, 150} }); + + // fail: set 51 votes of USER2 to option 1 and 150 to option 0 (more votes than shares) + EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {1, 51}, {0, 150} })); + votes = test.getShareholderVotes(proposalIdx, USER2); + EXPECT_EQ((int)votes.proposalType, (int)proposal.type); + checkVoteCounts(votes, { {0, 50}, {1, 150} }); + + // set 20 votes of USER2 to option 0 and 30 to option 1 (some votes unused) + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {0, 20}, {1, 30} })); + votes = test.getShareholderVotes(proposalIdx, USER2); + EXPECT_EQ((int)votes.proposalType, (int)proposal.type); + checkVoteCounts(votes, { {0, 20}, {1, 30} }); + + // fail: try to get voting results of invalid proposal + auto results = test.getShareholderVotingResults(proposalIdx + 1); + EXPECT_EQ(results.totalVotesAuthorized, 0); + + // check voting results + results = test.getShareholderVotingResults(proposalIdx); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ((int)results.optionCount, 2); + EXPECT_EQ(results.optionVoteCount.get(0), 20 + 356); + EXPECT_EQ(results.optionVoteCount.get(1), 30); + EXPECT_EQ(results.totalVotesCasted, 20 + 356 + 30); + EXPECT_EQ(results.getAcceptedOption(), -1); + EXPECT_EQ(results.getMostVotedOption(), 0); + + // set 1 vote of USER3 to option 0 and 19 to option 1 + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdx, proposal, { {0, 1}, {1, 19} })); + + // change votes of USER1 + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx, proposal, { {0, 300}, {1, 50} })); + + votes = test.getShareholderVotes(proposalIdx, USER3); + EXPECT_EQ((int)votes.proposalType, (int)proposal.type); + checkVoteCounts(votes, { {0, 1}, {1, 19} }); + votes = test.getShareholderVotes(proposalIdx, USER2); + checkVoteCounts(votes, { {0, 20}, {1, 30} }); + votes = test.getShareholderVotes(proposalIdx, USER1); + checkVoteCounts(votes, { {0, 300}, {1, 50} }); + + results = test.getShareholderVotingResults(proposalIdx); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ((int)results.optionCount, 2); + EXPECT_EQ(results.optionVoteCount.get(0), 1 + 20 + 300); + EXPECT_EQ(results.optionVoteCount.get(1), 19 + 30 + 50); + EXPECT_EQ(results.totalVotesCasted, 1 + 20 + 300 + 19 + 30 + 50); + EXPECT_EQ(results.getAcceptedOption(), -1); + EXPECT_EQ(results.getMostVotedOption(), 0); + + // withdraw votes of USER1 and USER3 + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx, proposal, std::vector>())); + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdx, proposal, NO_VOTE_VALUE)); + + votes = test.getShareholderVotes(proposalIdx, USER3); + checkVoteCounts(votes, {}); + votes = test.getShareholderVotes(proposalIdx, USER2); + checkVoteCounts(votes, { {0, 20}, {1, 30} }); + votes = test.getShareholderVotes(proposalIdx, USER1); + checkVoteCounts(votes, {}); + + results = test.getShareholderVotingResults(proposalIdx); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 20); + EXPECT_EQ(results.optionVoteCount.get(1), 30); + EXPECT_EQ(results.totalVotesCasted, 20 + 30); + EXPECT_EQ(results.getAcceptedOption(), -1); + EXPECT_EQ(results.getMostVotedOption(), 1); + + // fail: try to set all votes of USER2 to invalid value with single-vote struct + // (uses Multi-Vote internally for testing compatibility, so votes of the user are reset) + EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, 4)); + votes = test.getShareholderVotes(proposalIdx, USER2); + checkVoteCounts(votes, {}); + + // fail: try to set votes of invalid proposal index + EXPECT_FALSE(test.setShareholderVotes(USER2, 0xffff, proposal, { { 0, 111 } })); + + // fail: try to set votes of inactive proposal + ++system.epoch; + EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 } })); + --system.epoch; + + // fail: try to set votes of with wrong proposal type + proposal.type = ProposalTypes::VariableThreeValues; + EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 } })); + proposal.type = ProposalTypes::VariableYesNo; + + // fail: try to set votes of with wrong proposal tick + ++proposal.tick; + EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 } })); + --proposal.tick; + + // fail: try to set votes for USER4 who is no shareholder + EXPECT_FALSE(test.setShareholderVotes(USER4, proposalIdx, proposal, { { 0, 111 } })); + + // success: set votes with duplicate values in array + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx, proposal, { { 0, 111 }, {1, 10}, {0, 22}, {1, 3} })); + votes = test.getShareholderVotes(proposalIdx, USER2); + checkVoteCounts(votes, { {0, 133}, {1, 13} }); + + // fail: try to set votes of USER2 to invalid value with multi-vote struct + // (votes of the user are reset) + EXPECT_FALSE(test.setShareholderVotes(USER2, proposalIdx, proposal, { {0, 12}, {1, 23}, {2, 34} })); + votes = test.getShareholderVotes(proposalIdx, USER2); + checkVoteCounts(votes, {}); + + // voting of TESTEXB as shareholder of TESTEXA (originator not checked by procedure) + // user procedure TESTEXB::setVotesInOtherContractAsShareholder + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdx, proposal, { {0, 10}, {1, 20}, {0, 70} })); + votes = test.getShareholderVotes(proposalIdx, TESTEXB_CONTRACT_ID); + checkVoteCounts(votes, { {0, 80}, {1, 20} }); + + results = test.getShareholderVotingResults(proposalIdx); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 80); + EXPECT_EQ(results.optionVoteCount.get(1), 20); + EXPECT_EQ(results.totalVotesCasted, 100); + EXPECT_EQ(results.getAcceptedOption(), -1); + EXPECT_EQ(results.getMostVotedOption(), 0); + + ////////////////////////////////////////////////////// + // create new shareholder proposal in TESTEXA as shareholder TESTEXB + TESTEXA::SetShareholderProposal_input setShareholderProposalInput2; + setShareholderProposalInput2.proposalData.type = ProposalTypes::MultiVariablesYesNo; + setShareholderProposalInput2.proposalData.epoch = system.epoch; + setMemory(setShareholderProposalInput2.multiVarData, 0); + + // fails to create proposal, because multiVarData is invalid (originator not checked by procedure) + uint16 proposalIdx2 = test.setProposalInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, setShareholderProposalInput2); + EXPECT_EQ((int)proposalIdx2, (int)INVALID_PROPOSAL_INDEX); + + // create proposal (originator not checked by procedure) + setShareholderProposalInput2.multiVarData.hasValueDummyStateVariable1 = true; + setShareholderProposalInput2.multiVarData.hasValueDummyStateVariable2 = true; + setShareholderProposalInput2.multiVarData.hasValueDummyStateVariable3 = true; + setShareholderProposalInput2.multiVarData.optionYesValues.dummyStateVariable1 = 1; + setShareholderProposalInput2.multiVarData.optionYesValues.dummyStateVariable2 = 2; + setShareholderProposalInput2.multiVarData.optionYesValues.dummyStateVariable3 = 3; + proposalIdx2 = test.setProposalInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, setShareholderProposalInput2); + + // get and check new proposal + auto fullProposalData2 = test.getShareholderProposal(proposalIdx2); + EXPECT_EQ(fullProposalData2.proposerPubicKey, TESTEXB_CONTRACT_ID); + EXPECT_EQ((int)fullProposalData2.proposal.type, (int)ProposalTypes::MultiVariablesYesNo); + auto proposal2 = fullProposalData2.proposal; + EXPECT_EQ(fullProposalData2.multiVarData, setShareholderProposalInput2.multiVarData); + + // cast votes + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdx2, proposal2, { {1, 90} })); + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdx2, proposal2, { {0, 50}, {1, 260} })); + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdx2, proposal2, { {0, 10}, {1, 160} })); + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdx2, proposal2, { {0, 1}, {1, 15} })); + results = test.getShareholderVotingResults(proposalIdx2); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 61); + EXPECT_EQ(results.optionVoteCount.get(1), 525); + EXPECT_EQ(results.getAcceptedOption(), 1); + EXPECT_EQ(results.totalVotesCasted, 61 + 525); + + // test proposal listing function (2 active, 0 inactive) + proposalIndices = test.getShareholderProposalIndices(true); + EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdx && proposalIndices[1] == proposalIdx2); + EXPECT_EQ(test.getShareholderProposalIndices(false).size(), 0); + + // test that variables are set correctly after epoch switch + test.getStateTestExampleA()->checkVariablesSetByProposal(0, 0, 0); + test.endEpoch(); + ++system.epoch; + test.getStateTestExampleA()->checkVariablesSetByProposal(1, 2, 3); + + // test proposal listing function (2 inactive by USER2/TESTEXB, 0 active) + proposalIndices = test.getShareholderProposalIndices(false); + EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdx && proposalIndices[1] == proposalIdx2); + EXPECT_EQ(test.getShareholderProposalIndices(true).size(), 0); + + // Setup proposal to change variable 1 + uint16 proposalIdxA1 = test.setupShareholderProposalTestExA(USER1, ProposalTypes::VariableYesNo, true, 13); + EXPECT_NE((int)proposalIdxA1, (int)INVALID_PROPOSAL_INDEX); + auto proposalDataA1 = test.getShareholderProposal(proposalIdxA1); + auto proposalA1 = proposalDataA1.proposal; + EXPECT_EQ((int)proposalA1.type, (int)ProposalTypes::VariableYesNo); + + // Setup proposal to change variable 2 and 3 + uint16 proposalIdxA2 = test.setupShareholderProposalTestExA(USER2, ProposalTypes::MultiVariablesYesNo, false, 0, true, 4, true, 5); + EXPECT_NE((int)proposalIdxA2, (int)INVALID_PROPOSAL_INDEX); + auto proposalDataA2 = test.getShareholderProposal(proposalIdxA2); + auto proposalA2 = proposalDataA2.proposal; + EXPECT_EQ((int)proposalA2.type, (int)ProposalTypes::MultiVariablesYesNo); + EXPECT_EQ(proposalDataA2.proposerPubicKey, USER2); + EXPECT_EQ(proposalDataA2.multiVarData.optionYesValues.dummyStateVariable2, 4); + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxA2, proposalA2, { {0, 3} })); + checkVoteCounts(test.getShareholderVotes(proposalIdxA2, USER2), { {0, 3} }); + + // Overwrite proposal to change variable 2 and 3 + proposalIdxA2 = test.setupShareholderProposalTestExA(USER2, ProposalTypes::MultiVariablesYesNo, false, 0, true, 1337, true, 42); + EXPECT_NE((int)proposalIdxA2, (int)INVALID_PROPOSAL_INDEX); + checkVoteCounts(test.getShareholderVotes(proposalIdxA2, USER2), {}); + + /////////////////////////////////////////////////////////////// + // Proposals in TestExB + + // issue contract shares + std::vector> sharesTestExB{ + {TESTEXA_CONTRACT_ID, 256}, + {USER2, 200}, + {USER3, 100}, + {USER4, 120} + }; + issueContractShares(TESTEXB_CONTRACT_INDEX, sharesTestExB); + const Asset TESTEXB_ASSET{ NULL_ID, assetNameFromString("TESTEXB") }; + EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { TESTEXA_CONTRACT_ID, QX_CONTRACT_INDEX }, { TESTEXA_CONTRACT_ID, QX_CONTRACT_INDEX }), 256); + EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER2, QX_CONTRACT_INDEX }, { USER2, QX_CONTRACT_INDEX }), 200); + EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER3, QX_CONTRACT_INDEX }, { USER3, QX_CONTRACT_INDEX }), 100); + EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER4, QX_CONTRACT_INDEX }, { USER4, QX_CONTRACT_INDEX }), 120); + EXPECT_EQ(numberOfShares(TESTEXB_ASSET, { USER1, QX_CONTRACT_INDEX }, { USER1, QX_CONTRACT_INDEX }), 0); + + // Create scalar variable proposal + TESTEXB::ProposalDataT proposalB1; + proposalB1.epoch = system.epoch; + proposalB1.type = ProposalTypes::VariableScalarMean; + proposalB1.data.variableScalar.variable = 0; + proposalB1.data.variableScalar.minValue = 0; + proposalB1.data.variableScalar.maxValue = MAX_AMOUNT; + proposalB1.data.variableScalar.proposedValue = 1000; + uint16 proposalIdxB1 = test.setShareholderProposal(USER2, { proposalB1 }); + EXPECT_NE((int)proposalIdxB1, (int)INVALID_PROPOSAL_INDEX); + auto proposalDataB1 = test.getShareholderProposal(proposalIdxB1); + proposalB1 = proposalDataB1.proposal; // needed to set tick + EXPECT_EQ((int)proposalDataB1.proposal.type, (int)ProposalTypes::VariableScalarMean); + EXPECT_EQ(proposalDataB1.proposerPubicKey, USER2); + EXPECT_EQ(proposalDataB1.proposal.data.variableScalar.maxValue, MAX_AMOUNT); + EXPECT_EQ(proposalDataB1.proposal.data.variableScalar.proposedValue, 1000); + + // Create multi-option variable proposal as shareholder TESTEXA + TESTEXB::ProposalDataT proposalB2; + proposalB2.epoch = system.epoch; + proposalB2.type = ProposalTypes::VariableFourValues; + proposalB2.data.variableOptions.variable = 1; + proposalB2.data.variableOptions.values.set(0, 100); + proposalB2.data.variableOptions.values.set(1, 1000); + proposalB2.data.variableOptions.values.set(2, 10000); + proposalB2.data.variableOptions.values.set(3, 100000); + uint16 proposalIdxB2 = test.setProposalInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, TESTEXB::SetShareholderProposal_input{ proposalB2 }); + EXPECT_NE((int)proposalIdxB2, (int)INVALID_PROPOSAL_INDEX); + auto proposalDataB2 = test.getShareholderProposal(proposalIdxB2); + proposalB2 = proposalDataB2.proposal; // needed to set tick + EXPECT_EQ((int)proposalDataB2.proposal.type, (int)ProposalTypes::VariableFourValues); + EXPECT_EQ(proposalDataB2.proposerPubicKey, TESTEXA_CONTRACT_ID); + EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.variable, 1); + EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(0), 100); + EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(1), 1000); + EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(2), 10000); + EXPECT_EQ(proposalDataB2.proposal.data.variableOptions.values.get(3), 100000); + + // cast votes in A1 + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdxA1, proposalA1, { {0, 60}, {1, 270} })); + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxA1, proposalA1, { {0, 15}, {1, 180} })); + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdxA1, proposalA1, { {1, 80}, {0, 15} })); + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxA1, proposalA1, { {0, 9}, {1, 11} })); + results = test.getShareholderVotingResults(proposalIdxA1); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 99); + EXPECT_EQ(results.optionVoteCount.get(1), 541); + EXPECT_EQ(results.getAcceptedOption(), 1); + EXPECT_EQ(results.totalVotesCasted, 99 + 541); + + // cast votes in A2 + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdxA2, proposalA2, { {0, 150}, {1, 150} })); + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxA2, proposalA2, { {0, 100}, {1, 100} })); + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER4, TESTEXA_CONTRACT_INDEX, proposalIdxA2, proposalA2, { {1, 50}, {0, 50} })); + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxA2, proposalA2, { {0, 10}, {1, 10} })); + results = test.getShareholderVotingResults(proposalIdxA2); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 310); + EXPECT_EQ(results.optionVoteCount.get(1), 310); + EXPECT_EQ(results.getAcceptedOption(), 0); + EXPECT_EQ(results.totalVotesCasted, 620); + EXPECT_TRUE(test.setShareholderVotes(USER1, proposalIdxA2, proposalA2, { {0, 0}, {1, 350} })); + results = test.getShareholderVotingResults(proposalIdxA2); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 160); + EXPECT_EQ(results.optionVoteCount.get(1), 510); + EXPECT_EQ(results.getAcceptedOption(), 1); + EXPECT_EQ(results.totalVotesCasted, 670); + + // cast votes in B1 + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB1, proposalB1, { {0, 10}, {100, 20}, {1000, 200}, {10000, 10}, {100000, 5}, {1000000, 5}, {10000000, 2}, {100000000, 2} })); + checkVoteCounts(test.getShareholderVotes(proposalIdxB1, TESTEXA_CONTRACT_ID), { {0, 10}, {100, 20}, {1000, 200}, {10000, 10}, {100000, 5}, {1000000, 5}, {10000000, 2}, {100000000, 2} }); + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxB1, proposalB1, { {100, 200} })); + checkVoteCounts(test.getShareholderVotes(proposalIdxB1, USER2), { {100, 200} }); + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxB1, proposalB1, { {150, 90}, {200, 10} })); + checkVoteCounts(test.getShareholderVotes(proposalIdxB1, USER3), { {150, 90}, {200, 10} }); + EXPECT_TRUE(test.setShareholderVotes(USER4, proposalIdxB1, proposalB1, { {300, 99}, {11974, 1} })); + checkVoteCounts(test.getShareholderVotes(proposalIdxB1, USER4), { {300, 99}, {11974, 1} }); + results = test.getShareholderVotingResults(proposalIdxB1); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ((int)results.optionCount, 0); + EXPECT_EQ(results.scalarVotingResult, 345381); + EXPECT_EQ(results.totalVotesCasted, 654); + + // cast votes in B2 + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB2, proposalB2, { {0, 10}, {1, 20}, {2, 30}, {3, 40} })); + checkVoteCounts(test.getShareholderVotes(proposalIdxB2, TESTEXA_CONTRACT_ID), { {0, 10}, {1, 20}, {2, 30}, {3, 40} }); + EXPECT_TRUE(test.setShareholderVotes(USER2, proposalIdxB2, proposalB2, { {0, 20}, {1, 30}, {2, 40}, {3, 50}, {4, 3} })); + EXPECT_TRUE(test.setShareholderVotes(USER3, proposalIdxB2, proposalB2, { {0, 5}, {1, 10}, {2, 15}, {3, 20}, {4, 2} })); + EXPECT_TRUE(test.setShareholderVotes(USER4, proposalIdxB2, proposalB2, { {0, 25}, {1, 20}, {2, 15}, {3, 10} })); + results = test.getShareholderVotingResults(proposalIdxB2); + EXPECT_EQ(results.totalVotesAuthorized, 676); + EXPECT_EQ(results.optionVoteCount.get(0), 60); + EXPECT_EQ(results.optionVoteCount.get(1), 80); + EXPECT_EQ(results.optionVoteCount.get(2), 100); + EXPECT_EQ(results.optionVoteCount.get(3), 120); + EXPECT_EQ(results.optionVoteCount.get(4), 5); + EXPECT_EQ(results.getAcceptedOption(), -1); + EXPECT_EQ(results.totalVotesCasted, 365); + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB2, proposalB2, { {0, 45}, {1, 50}, {2, 55}, {3, 50}, {4, 5} })); + results = test.getShareholderVotingResults(proposalIdxB2); + EXPECT_EQ(results.optionVoteCount.get(0), 95); + EXPECT_EQ(results.optionVoteCount.get(1), 110); + EXPECT_EQ(results.optionVoteCount.get(2), 125); + EXPECT_EQ(results.optionVoteCount.get(3), 130); + EXPECT_EQ(results.optionVoteCount.get(4), 10); + EXPECT_EQ(results.getAcceptedOption(), -1); + EXPECT_EQ(results.totalVotesCasted, 470); + EXPECT_TRUE(test.setVotesInOtherContractAsShareholder(USER1, TESTEXB_CONTRACT_INDEX, proposalIdxB2, proposalB2, { {0, 5}, {1, 5}, {2, 5}, {3, 240} })); + results = test.getShareholderVotingResults(proposalIdxB2); + EXPECT_EQ(results.optionVoteCount.get(0), 55); + EXPECT_EQ(results.optionVoteCount.get(1), 65); + EXPECT_EQ(results.optionVoteCount.get(2), 75); + EXPECT_EQ(results.optionVoteCount.get(3), 320); + EXPECT_EQ(results.optionVoteCount.get(4), 5); + EXPECT_EQ(results.getAcceptedOption(), 3); + EXPECT_EQ(results.totalVotesCasted, 520); + + // test proposal listing function in TESTEXA: 1 inactive by TESTEXB, 2 active by USER2/USER1 + proposalIndices = test.getShareholderProposalIndices(false); + EXPECT_TRUE(proposalIndices.size() == 1 && proposalIndices[0] == proposalIdx2); + proposalIndices = test.getShareholderProposalIndices(true); + EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdxA2 && proposalIndices[1] == proposalIdxA1); + + // test proposal listing function in TESTEXB: 0 inactive, 2 active by USER1/TESTEXA + proposalIndices = test.getShareholderProposalIndices(false); + EXPECT_TRUE(proposalIndices.size() == 0); + proposalIndices = test.getShareholderProposalIndices(true); + EXPECT_TRUE(proposalIndices.size() == 2 && proposalIndices[0] == proposalIdxB1 && proposalIndices[1] == proposalIdxB2); + + // test that variables are set correctly after epoch switch + test.getStateTestExampleA()->checkVariablesSetByProposal(1, 2, 3); + test.getStateTestExampleB()->checkVariablesSetByProposal(0, 0, 0); + test.endEpoch(); + ++system.epoch; + test.getStateTestExampleA()->checkVariablesSetByProposal(13, 1337, 42); + test.getStateTestExampleB()->checkVariablesSetByProposal(345381, 10000, 0); + + // test proposal listing function in TESTEXA: 3 inactive by TESTEXB/USER2/USER1, 0 active + EXPECT_TRUE(test.getShareholderProposalIndices(false).size() == 3); + EXPECT_TRUE(test.getShareholderProposalIndices(true).size() == 0); + + // test proposal listing function in TESTEXB: 2 inactive by USER1/TESTEXA, 0 active + EXPECT_TRUE(test.getShareholderProposalIndices(false).size() == 2); + EXPECT_TRUE(test.getShareholderProposalIndices(true).size() == 0); +} + +TEST(ContractTestEx, InterContractCallInsufficientFees) +{ + ContractTestingTestEx test; + increaseEnergy(USER1, 1000000); + + // First verify call works normally (TestExampleA has fees from constructor) + auto output1 = test.testInterContractCallError(); + EXPECT_EQ(output1.errorCode, QPI::NoCallError); + EXPECT_EQ(output1.callSucceeded, 1); + + // Save original fee reserve + long long originalFeeReserve = getContractFeeReserve(TESTEXA_CONTRACT_INDEX); + + // Drain TestExampleA's fee reserve + setContractFeeReserve(TESTEXA_CONTRACT_INDEX, 0); + + // Verify fee reserve is now 0 + EXPECT_EQ(getContractFeeReserve(TESTEXA_CONTRACT_INDEX), 0); + + // Try the call again - should fail with insufficient fees + auto output2 = test.testInterContractCallError(); + EXPECT_EQ(output2.errorCode, QPI::CallErrorInsufficientFees); + EXPECT_EQ(output2.callSucceeded, 0); + + // Restore fee reserve for other tests + setContractFeeReserve(TESTEXA_CONTRACT_INDEX, originalFeeReserve); +} + +TEST(ContractTestEx, SystemCallbacksWithNegativeFeeReserve) +{ + ContractTestingTestEx test; + + // Set TESTEXC fee reserve to negative value + setContractFeeReserve(TESTEXC_CONTRACT_INDEX, -1000); + EXPECT_EQ(getContractFeeReserve(TESTEXC_CONTRACT_INDEX), -1000); + + const auto initialIncomingC = test.getIncomingTransferAmounts(); + const sint64 initialBalanceC = getBalance(TESTEXC_CONTRACT_ID); + + // Give TESTEXB balance to make the transfer + increaseEnergy(TESTEXB_CONTRACT_ID, 10000); + increaseEnergy(USER1, 10000); + const sint64 transferAmount = 5000; + EXPECT_TRUE(test.qpiTransfer(TESTEXC_CONTRACT_ID, transferAmount, 1000, USER1)); + + // Verify callback executed and modified state + const auto afterIncomingC = test.getIncomingTransferAmounts(); + EXPECT_EQ(afterIncomingC.qpiTransferAmount, initialIncomingC.qpiTransferAmount + transferAmount); + EXPECT_EQ(getBalance(TESTEXC_CONTRACT_ID), initialBalanceC + transferAmount); + + // Verify TESTEXB not in error state + EXPECT_EQ(contractError[TESTEXB_CONTRACT_INDEX], NoContractError); + + // Verify TESTEXC fee reserve is still negative + EXPECT_LT(getContractFeeReserve(TESTEXC_CONTRACT_INDEX), 0); +} + +TEST(ContractTestEx, OracleQuery) +{ + ContractTestingTestEx test; + system.epoch = 200; + system.tick = 123456783; + + //------------------------------------------------------------------------- + // Test qpi.queryOracle() and generating message to oracle machine node + increaseEnergy(USER1, 100000000); + increaseEnergy(TESTEXC_CONTRACT_ID, 100000000); + + const id currencyBtc(Ch::B, Ch::T, Ch::C, 0, 0); + const id currencyUsd(Ch::U, Ch::S, Ch::D, 0, 0); + + uint64 expectedOracleQueryId = getContractOracleQueryId(system.tick, 0); + OI::Price::OracleQuery query = { NULL_ID, DateAndTime(2026, 1, 1), currencyBtc, currencyUsd}; + EXPECT_EQ(test.queryPriceOracle(USER1, 10, query), expectedOracleQueryId); + checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 10, query); + + expectedOracleQueryId = getContractOracleQueryId(system.tick, 1); + query.oracle = OI::Price::getCoingeckoOracleId(); + query.timestamp.addDays(20); + query.currency1 = currencyUsd; + query.currency2 = NULL_ID; + EXPECT_EQ(test.queryPriceOracle(USER1, 42, query), expectedOracleQueryId); + checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 42, query); + + expectedOracleQueryId = getContractOracleQueryId(system.tick, 2); + test.endTick(); + OI::Mock::OracleQuery mockQuery{ system.tick }; + ++system.tick; + checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 20000, mockQuery); + + expectedOracleQueryId = getContractOracleQueryId(system.tick, 0); + query.oracle = OI::Price::getMockOracleId(); + query.timestamp.addMillisec(123456); + query.currency1 = id(1, 23456, 7890, 42); + query.currency2 = currencyBtc; + EXPECT_EQ(test.queryPriceOracle(USER1, 13, query), expectedOracleQueryId); + checkNetworkMessageOracleMachineQuery(expectedOracleQueryId, 13, query); + + //------------------------------------------------------------------------- + // Test processing of oracle machine node reply message + struct + { + OracleMachineReply metadata; + OI::Price::OracleReply data; + } priceOracleMachineReply; + + priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; + priceOracleMachineReply.metadata.oracleQueryId = expectedOracleQueryId; + priceOracleMachineReply.data.numerator = 1234; + priceOracleMachineReply.data.denominator = 1; + + oracleEngine.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); +} diff --git a/test/contract_testing.h b/test/contract_testing.h new file mode 100644 index 000000000..dec34b1f7 --- /dev/null +++ b/test/contract_testing.h @@ -0,0 +1,228 @@ +#pragma once + +// Include this first, to ensure "logging/logging.h" isn't included before the custom LOG_BUFFER_SIZE has been defined +#include "logging_test.h" + +#include "gtest/gtest.h" + +// workaround for name clash with stdlib +#define system qubicSystemStruct + +// make test example contracts available in all compile units +#define INCLUDE_CONTRACT_TEST_EXAMPLES + +#include "contract_core/contract_def.h" +#include "contract_core/contract_exec.h" + +#include "contract_core/qpi_spectrum_impl.h" +#include "contract_core/qpi_asset_impl.h" +#include "contract_core/qpi_system_impl.h" +#include "contract_core/qpi_ticking_impl.h" +#include "contract_core/qpi_ipo_impl.h" +#include "contract_core/qpi_mining_impl.h" +#include "contract_core/qpi_oracle_impl.h" + +#include "test_util.h" + + +class ContractTesting : public LoggingTest +{ +public: + ContractTesting() + { + +#ifdef __AVX512F__ + initAVX512FourQConstants(); +#endif + commonBuffers.init(1); + initContractExec(); + initSpecialEntities(); + + contractStates[0] = (unsigned char*)malloc(contractDescriptions[0].stateSize); + setMem(contractStates[0], contractDescriptions[0].stateSize, 0); + } + + ~ContractTesting() + { + deinitSpecialEntities(); + deinitAssets(); + deinitSpectrum(); + commonBuffers.deinit(); + deinitContractExec(); + for (unsigned int i = 0; i < contractCount; ++i) + { + if (contractStates[i]) + { + free(contractStates[i]); + contractStates[i] = nullptr; + } + } + } + + void initEmptySpectrum() + { + initSpectrum(); + memset(spectrum, 0, spectrumSizeInBytes); + updateSpectrumInfo(); + } + + void initEmptyUniverse() + { + initAssets(); + memset(assets, 0, universeSizeInBytes); + as.indexLists.reset(); + } + + template + unsigned int callFunction(unsigned int contractIndex, unsigned short functionInputType, const InputType& input, OutputType& output, bool checkInputSize = true, bool expectSuccess = true) const + { + EXPECT_LT(contractIndex, contractCount); + EXPECT_NE(contractStates[contractIndex], nullptr); + QpiContextUserFunctionCall qpiContext(contractIndex); + if (checkInputSize) + { + unsigned short expectedInputSize = contractUserFunctionInputSizes[contractIndex][functionInputType]; + EXPECT_EQ((int)expectedInputSize, sizeof(input)); + } + unsigned int errorCode = qpiContext.call(functionInputType, &input, sizeof(input)); + EXPECT_EQ((int)qpiContext.outputSize, sizeof(output)); + if (expectSuccess) + { + EXPECT_EQ(errorCode, 0); + } + copyMem(&output, qpiContext.outputBuffer, sizeof(output)); + qpiContext.freeBuffer(); + return errorCode; + } + + template + bool invokeUserProcedure( + unsigned int contractIndex, unsigned short procedureInputType, const InputType& input, OutputType& output, + const id& user, sint64 amount, + bool checkInputSize = true, bool expectSuccess = true) + { + // check inputs and init output + EXPECT_LT(contractIndex, contractCount); + EXPECT_NE(contractStates[contractIndex], nullptr); + if (checkInputSize) + { + unsigned short expectedInputSize = contractUserProcedureInputSizes[contractIndex][procedureInputType]; + EXPECT_EQ((int)expectedInputSize, sizeof(input)); + } + setMemory(output, 0); + + // transfer amount (fee / invocation reward) + int userSpectrumIndex = spectrumIndex(user); + if (userSpectrumIndex < 0 || !decreaseEnergy(userSpectrumIndex, amount)) + return false; + increaseEnergy(id(contractIndex, 0, 0, 0), amount); + + // run callback for incoming transfer of amount / fee / invocation reward + if (amount > 0 && contractSystemProcedures[contractIndex][POST_INCOMING_TRANSFER]) + { + QpiContextSystemProcedureCall qpiContext(contractIndex, POST_INCOMING_TRANSFER); + QPI::PostIncomingTransfer_input input{ user, amount, QPI::TransferType::procedureTransaction }; + qpiContext.call(input); + } + + // run user procedure + QpiContextUserProcedureCall qpiContext(contractIndex, user, amount); + qpiContext.call(procedureInputType, &input, sizeof(input)); + + // check results, copy output and cleanup + EXPECT_EQ((int)qpiContext.outputSize, sizeof(output)); + if (expectSuccess) + { + EXPECT_EQ(contractError[contractIndex], 0); + } + copyMem(&output, qpiContext.outputBuffer, sizeof(output)); + qpiContext.freeBuffer(); + return true; + } + + void callSystemProcedure(unsigned int contractIndex, SystemProcedureID sysProcId, bool expectSuccess = true) + { + EXPECT_LT(contractIndex, contractCount); + EXPECT_NE(contractStates[contractIndex], nullptr); + QpiContextSystemProcedureCall qpiContext(contractIndex, sysProcId); + qpiContext.call(); + if (expectSuccess) + { + EXPECT_EQ(contractError[contractIndex], 0); + } + } +}; + +#define INIT_CONTRACT(contractName) { \ + constexpr unsigned int contractIndex = contractName##_CONTRACT_INDEX; \ + EXPECT_LT(contractIndex, contractCount); \ + const unsigned long long stateSize = contractDescriptions[contractIndex].stateSize; \ + EXPECT_GE(stateSize, max(sizeof(contractName), sizeof(IPO))); \ + contractStates[contractIndex] = (unsigned char*)malloc(stateSize); \ + setMem(contractStates[contractIndex], stateSize, 0); \ + REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(contractName); \ + setContractFeeReserve(contractIndex, 10000000); \ +} + +static inline long long getBalance(const id& pubKey) +{ + int index = spectrumIndex(pubKey); + if (index < 0) + return 0; + long long balance = energy(index); + EXPECT_GE(balance, 0ll); + return balance; +} + +// Update time returned by QPI functions based on utcTime, which can be set to current time with updateTime(). +static inline void updateQpiTime() +{ + etalonTick.millisecond = utcTime.Nanosecond / 1000000; + etalonTick.second = utcTime.Second; + etalonTick.minute = utcTime.Minute; + etalonTick.hour = utcTime.Hour; + etalonTick.day = utcTime.Day; + etalonTick.month = utcTime.Month; + etalonTick.year = utcTime.Year - 2000; +} + +// Check that the contract execution system state is clean (before / after running contracts). +static inline void checkContractExecCleanup() +{ + for (unsigned int i = 0; i < contractCount; ++i) + { + EXPECT_EQ(contractStateLock[i].getCurrentReaderLockCount(), 0); + } + + for (unsigned int i = 0; i < NUMBER_OF_CONTRACT_EXECUTION_BUFFERS; ++i) + { + EXPECT_EQ(contractLocalsStack[i].size(), 0); + EXPECT_EQ(contractLocalsStackLock[i], 0); + } + EXPECT_EQ(contractLocalsStackLockWaitingCount, 0); + EXPECT_EQ(contractCallbacksRunning, NoContractCallback); +} + +// Issue contract shares and transfer ownership/possession of all shares to one entity +static inline void issueContractShares(unsigned int contractIndex, std::vector>& initialOwnerShares, bool warnOnTooFewShares = true) +{ + int issuanceIndex, ownershipIndex, possessionIndex, dstOwnershipIndex, dstPossessionIndex; + EXPECT_EQ(issueAsset(m256i::zero(), (char*)contractDescriptions[contractIndex].assetName, 0, CONTRACT_ASSET_UNIT_OF_MEASUREMENT, NUMBER_OF_COMPUTORS, QX_CONTRACT_INDEX, &issuanceIndex, &ownershipIndex, &possessionIndex), NUMBER_OF_COMPUTORS); + + int totalShareCount = 0; + for (const auto& ownerShareCountPair : initialOwnerShares) + totalShareCount += ownerShareCountPair.second; + EXPECT_LE(totalShareCount, NUMBER_OF_COMPUTORS); + if (totalShareCount < NUMBER_OF_COMPUTORS) + { + if (warnOnTooFewShares) + std::cout << "Warning: issueContractShares() called with " << NUMBER_OF_COMPUTORS - totalShareCount << " less then expected shares, adding remaining shares to first owner." << std::endl; + initialOwnerShares[0].second += NUMBER_OF_COMPUTORS - totalShareCount; + } + + for (const auto& ownerShareCountPair : initialOwnerShares) + { + EXPECT_TRUE(transferShareOwnershipAndPossession(ownershipIndex, possessionIndex, ownerShareCountPair.first, ownerShareCountPair.second, &dstOwnershipIndex, &dstPossessionIndex, true)); + } + EXPECT_EQ(numberOfShares({ m256i::zero(), *(uint64*)contractDescriptions[contractIndex].assetName }), NUMBER_OF_COMPUTORS); +} diff --git a/test/custom_mining.cpp b/test/custom_mining.cpp new file mode 100644 index 000000000..9a68f8dcf --- /dev/null +++ b/test/custom_mining.cpp @@ -0,0 +1,279 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#ifndef SOLUTION_SECURITY_DEPOSIT +#define SOLUTION_SECURITY_DEPOSIT 1000000 +#endif + +#ifndef MAX_NUMBER_OF_TICKS_PER_EPOCH +#define MAX_NUMBER_OF_TICKS_PER_EPOCH 600000 +#endif + +#ifndef MAX_NUMBER_OF_PROCESSORS +#define MAX_NUMBER_OF_PROCESSORS 4 +#endif + +#include "src/mining/mining.h" + +TEST(CustomMining, TaskStorageGeneral) +{ + constexpr unsigned long long NUMBER_OF_TASKS = 100; + CustomMiningTaskV2Storage storage; + + storage.init(); + + for (unsigned long long i = 0; i < NUMBER_OF_TASKS; i++) + { + CustomMiningTaskV2 task; + task.taskIndex = NUMBER_OF_TASKS - i; + + storage.addData(&task); + } + + // Expect the task are sort in ascending order + for (unsigned long long i = 0; i < NUMBER_OF_TASKS - 1; i++) + { + CustomMiningTaskV2* task0 = storage.getDataByIndex(i); + CustomMiningTaskV2* task1 = storage.getDataByIndex(i + 1); + EXPECT_LT(task0->taskIndex, task1->taskIndex); + } + EXPECT_EQ(storage.getCount(), NUMBER_OF_TASKS); + + storage.deinit(); +} + +TEST(CustomMining, TaskStorageDuplicatedItems) +{ + constexpr unsigned long long NUMBER_OF_TASKS = 100; + constexpr unsigned long long DUPCATED_TASKS = 10; + CustomMiningTaskV2Storage storage; + + storage.init(); + + // For DUPCATED_TASKS will only recorded 1 task + for (unsigned long long i = 0; i < DUPCATED_TASKS; i++) + { + CustomMiningTaskV2 task; + task.taskIndex = 1; + + storage.addData(&task); + } + + for (unsigned long long i = DUPCATED_TASKS; i < NUMBER_OF_TASKS; i++) + { + CustomMiningTaskV2 task; + task.taskIndex = i; + + storage.addData(&task); + } + + // Expect the task are not duplicated + EXPECT_EQ(storage.getCount(), NUMBER_OF_TASKS - DUPCATED_TASKS + 1); + + storage.deinit(); +} + +TEST(CustomMining, TaskStorageExistedItem) +{ + constexpr unsigned long long NUMBER_OF_TASKS = 100; + constexpr unsigned long long DUPCATED_TASKS = 10; + CustomMiningTaskV2Storage storage; + + storage.init(); + + for (unsigned long long i = 1; i < NUMBER_OF_TASKS + 1 ; i++) + { + CustomMiningTaskV2 task; + task.taskIndex = i; + storage.addData(&task); + } + + // Test an existed task + CustomMiningTaskV2 task; + task.taskIndex = NUMBER_OF_TASKS - 10; + storage.addData(&task); + + EXPECT_EQ(storage.dataExisted(&task), true); + unsigned long long idx = storage.lookForTaskGE(task.taskIndex); + EXPECT_EQ(storage.getDataByIndex(idx) != NULL, true); + EXPECT_EQ(storage.getDataByIndex(idx)->taskIndex, task.taskIndex); + EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); + + // Test a non-existed task whose the taskIndex is greater than the last task + task.taskIndex = NUMBER_OF_TASKS + 10; + EXPECT_EQ(storage.dataExisted(&task), false); + idx = storage.lookForTaskGE(task.taskIndex); + EXPECT_EQ(idx, CUSTOM_MINING_INVALID_INDEX); + + // Test a non-existed task whose the taskIndex is lower than the last task + task.taskIndex = 0; + EXPECT_EQ(storage.dataExisted(&task), false); + idx = storage.lookForTaskGE(task.taskIndex); + EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); + + + storage.deinit(); +} + +TEST(CustomMining, TaskStorageOverflow) +{ + constexpr unsigned long long NUMBER_OF_TASKS = CUSTOM_MINING_TASK_STORAGE_COUNT; + CustomMiningTaskV2Storage storage; + + storage.init(); + + for (unsigned long long i = 0; i < NUMBER_OF_TASKS; i++) + { + CustomMiningTaskV2 task; + task.taskIndex = i; + storage.addData(&task); + } + + // Overflow. Add one more and get error status + CustomMiningTaskV2 task; + task.taskIndex = NUMBER_OF_TASKS + 1; + EXPECT_NE(storage.addData(&task), 0); + + storage.deinit(); +} + +TEST(CustomMining, SolutionStorageGeneral) +{ + constexpr unsigned long long NUMBER_OF_TASKS = 100; + CustomMiningSolutionStorage storage; + + storage.init(); + + for (unsigned long long i = 0; i < NUMBER_OF_TASKS; i++) + { + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = NUMBER_OF_TASKS - i; + + storage.addData(&entry); + } + + // Expect the task are sorted in ascending order + for (unsigned long long i = 0; i < NUMBER_OF_TASKS - 1; i++) + { + CustomMiningSolutionStorageEntry* entry0 = storage.getDataByIndex(i); + CustomMiningSolutionStorageEntry* entry1 = storage.getDataByIndex(i + 1); + EXPECT_LT(entry0->taskIndex, entry1->taskIndex); + } + EXPECT_EQ(storage.getCount(), NUMBER_OF_TASKS); + + storage.deinit(); +} + +TEST(CustomMining, SolutionStorageDuplicatedItems) +{ + constexpr unsigned long long NUMBER_OF_SOLS = 100; + constexpr unsigned long long DUPLICATED_SOLS = 10; + CustomMiningSolutionStorage storage; + + storage.init(); + + // For DUPCATED_TASKS will only recorded many + for (unsigned long long i = 0; i < DUPLICATED_SOLS; i++) + { + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = 1; + entry.nonce = i; + + storage.addData(&entry); + } + + for (unsigned long long i = DUPLICATED_SOLS; i < NUMBER_OF_SOLS; i++) + { + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = i; + entry.nonce = i; + + storage.addData(&entry); + } + + // Expect all elements are added + EXPECT_EQ(storage.getCount(), NUMBER_OF_SOLS); + + // Data is still in ascending order + int duplicatedDataCount = 0; + for (unsigned long long i = 0; i < NUMBER_OF_SOLS - 1; i++) + { + CustomMiningSolutionStorageEntry* entry0 = storage.getDataByIndex(i); + CustomMiningSolutionStorageEntry* entry1 = storage.getDataByIndex(i + 1); + EXPECT_LE(entry0->taskIndex, entry1->taskIndex); + if (entry0->taskIndex == entry1->taskIndex) + { + duplicatedDataCount++; + } + } + + storage.deinit(); +} + +TEST(CustomMining, SolutionStorageExistedItem) +{ + constexpr unsigned long long NUMBER_OF_SOLS = 100; + constexpr unsigned long long DUPCATED_SOLS = 10; + CustomMiningSolutionStorage storage; + + storage.init(); + + for (unsigned long long i = 1; i < NUMBER_OF_SOLS + 1; i++) + { + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = i; + entry.nonce = i; + + storage.addData(&entry); + } + + // Test an existed task + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = NUMBER_OF_SOLS - 10; + storage.addData(&entry); + + EXPECT_EQ(storage.dataExisted(&entry), true); + unsigned long long idx = storage.lookForTaskGE(entry.taskIndex); + EXPECT_EQ(storage.getDataByIndex(idx) != NULL, true); + EXPECT_EQ(storage.getDataByIndex(idx)->taskIndex, entry.taskIndex); + EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); + + // Test a non-existed task whose the taskIndex is greater than the last task + entry.taskIndex = NUMBER_OF_SOLS + 10; + EXPECT_EQ(storage.dataExisted(&entry), false); + idx = storage.lookForTaskGE(entry.taskIndex); + EXPECT_EQ(idx, CUSTOM_MINING_INVALID_INDEX); + + // Test a non-existed task whose the taskIndex is lower than the last task + entry.taskIndex = 0; + EXPECT_EQ(storage.dataExisted(&entry), false); + idx = storage.lookForTaskGE(entry.taskIndex); + EXPECT_NE(idx, CUSTOM_MINING_INVALID_INDEX); + + + storage.deinit(); +} + +TEST(CustomMining, SolutionsStorageOverflow) +{ + constexpr unsigned long long NUMBER_OF_SOLS = CUSTOM_MINING_SOLUTION_STORAGE_COUNT; + CustomMiningSolutionStorage storage; + + storage.init(); + + for (unsigned long long i = 0; i < NUMBER_OF_SOLS; i++) + { + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = i; + entry.nonce = i; + storage.addData(&entry); + } + + // Overflow. Add one more and get error status + CustomMiningSolutionStorageEntry entry; + entry.taskIndex = NUMBER_OF_SOLS + 1; + EXPECT_NE(storage.addData(&entry), CustomMiningSolutionStorage::OK); + + storage.deinit(); +} diff --git a/test/data/custom_revenue.eoe b/test/data/custom_revenue.eoe new file mode 100644 index 0000000000000000000000000000000000000000..ef2aac9cc1562c9b9f100b1ce3e70c0161e70360 GIT binary patch literal 43264 zcmeI5d0fq3_vk-5=OmeuRH&2;MM%+HLLrh+gd`~?N*YL#Qj`p3N)pmQ2q8o?7*Z4= z2}!0zA%q6Q-FvTf?(2KpYrz4qE`uf6vE>`&+8`?za!?0*(o zAk}q1h7AIF9{DJglk|X3>JRdg21xT^AP)=&`AidJ8A@4{Q_#+N2(1$hO>`;P6)P+DTU4*IXadF7zLW^BKNJQwwzD4$}yFEJ0QIF110 z3PC?R(S9I~8;bF7MY}qbH_%TU_Pd8X3FDiOaWtYli~hb~9Q#lzVY@Dl`w0CVLLQCl zaZeZW@wogN8f$Q#v@lM3}mf$>wV*6pVUyboi!SSn5 z?~eTgv0pjL4(QJo$DfLRLNM=B(XJzMW8`1az5t~Tj^l*$S&Y0Nas|}walE6bJ7E5n zsX?9!2ZEf8^){s+=th{Y2=rH_0z44=O9|^u9eBC{NIA^IT;#KHeR|;d2hhJV&g=FF z=;wvp3g@4V^Vh++hGX7qa6SXkzc=z%XrI~#{FR~qR2)|m>q-^l_r|;#(P;W)7vXCtoH7_>hk%mezjLjT(^es64dLi=U7?ndZ06YZTb&%v0-JglE- z7|&eH{{$tN{{{5B2-~wTKdaDxJ?5<({ij(#zaVpv>S#CB3iL>>@*7O-eJAmLBBOPel^zH3AEpg_C{Er^_a&fagM}s z24lRI7@s1_McA%~?H)L;FV3qC^}W#{UqX7uPY4t7D+f%J7`+jqAP*hak5UukF~j~+n3r%IXA<69#^bz_(e5Dn z)k7J8`T2(PS%H3zpuB+NtU>$js5hcNVZ14*&qlixv{OU>TQDvj)>k#!aoBG=>XsPy zZM0v9btQ@MJjHrx#rk?I92eJXI?h7^?XP1TJJ4Sdw)Y5%xewI&1P`mM(P`_XR!?#BuoF9r2_wD(7U zsn|~%`+dQAxuJbu97h88Uh}Zs3wb_{a~#KC zhdc)7dtA5=2k->}B@8;#>=<2t^>_*!xPz0hx899I?fe4M|~-V6J6#c{r2UQ)2%OKe|{ zdi)aUM0; ze=zzLuK#4LD>>vgD23}Bgn4VmardD8Ow5A~`j5eO;XWw9@!fG=vlQVx6u#dZV*iF7 zz{{{sPvH3?FArRR_odHxzO=`D+u-v{`1h&zIKK*vcZ{+EbFl4lcP3q}yE1v9H}EBS zz_&~RK1U6B#1mO&H()aGSoE`22KeKNz_Vf@z)RZQnEekc;OhN>FRqqhd@b^Gw_u`f zX~4fC&&rcycGVk!{~Ql~OrJn}Q9-~>EPy8(0`KVs{K#0~p9>+rMmOLYXdk^1xQ8R~ z4QTJQ7`W36;1A8gkNiUf8dkQK7a?jP969h%0z*qGK?$#T4@<-s7X#cnu@DU$?JIRCn_MX6ZzXQIu7x1}?!1Ee_ zALC*Zg5 z03Y88_$%Zdw}1z91|B8__Wf@I?}PT50^ncJ{sP*YNCMxE+*1ntsGg% z>lFe&C;f1HoZ}`A#+khKh25yY@Cau83^MH3j`&8uLbAT74y-qXOS6>8v zqaFC4`VDx+dEkq9z;}HCzT+J5fgEuCCg7fDfS>*W^RfL1+zt&A<2T^9YJtBx4qUGZ_)X;fkxxN;`Bz}S1$oCWU_bi>@VO_z zPsV59*3W@E9s|A<`TiQ<4e7uSeFFQYr@#x20zdH{xM3A=>%+i{J^=6k7k z$fq9yd)tp-FZT%S1CSS^y%h3g$iKeBcq+i&X+QY6Qx81v0q{fnfWJnbkM@HO06+8w z>}QpOea}ST59@&I-v_>Y4{#T>H!lO;D*<@V*I>`T4gAI~;0m?CSCs;PfV?l-Un&89 zWGC3qLwnsDz!kRvAN&&h*c1Y<*a2MTIq(%lz}?W^@CERx$Oj=edj{MT{e0RAeu}Dq z@4Nzj3SxmzK|Zno_?|7m&pZbE^nBpF7~s4|z;iDFx8Df7!&Bhhkbm9)yaDYMbHKiB zy{P@33&5q(UK__fbPo7Vw2yxX{=a7dPmKUSF3A1SzA6;>mj_^9cM9xpg#&j&e(X5# zBD9~3el{EfekK^~2UUQd4o863tOmXv{U{s-ZW#nzxf1Npp#5^>g~+EI2K!wr!M?m4 z?4KS4-ntyP$z9+(kf--H_>cL( z8w-Gcj0T?J3Vh>b;JIk8H4pd{v|o+9{T$%KbHV=X2JoXd1NhoYz^9}C#mIv$053&9 zz3jkV8~sc}KF$_+E!w|b5B?>Q`y?^Xte>}!>c_Q$_bHLw6g8gYL;JdSc zcSo)`0XXk0@Kfu+-Vgb;OyDQOfoG2e`+!rxm#qb!X9;}p3E#{$d@ja$7Wq7V;Kmr|ks$CN zs0Z8(`MZ_CC+PrRbsWZhfcBb0fR`cP5(xGek=tVYRcQZZ5ZG5^oF@aozAtk3bnxHK z7x+C5u0=cs%*w5+%{=1?dH{@Bpfoox$?&zlr^2`IG{`V_@{mew* zS?=J!y*zO3J;1*$1a6AlZ#VF8SKv!!!QN{x@LD(Eo?U@Ep?_uMwOxQmCV+hg`YA%* zK39z&=!o!KO6;| zKOT76SK#Z`17AN1_`}b@vm${HHv|6S6Yz`Sz!bf_^y7v6Wi9ym z>IC*7YG7~w0(c)s;PHKc-$foW3wWq1@TX6}ez5~^MP=Z(9s}322d=3EJg)-%PY3Sb z6S(s|;BTh^KdcB``!?_i=qCbsSqbphQ^8)K0QU05zzdN(_Wd>9}0lyn1g+O2jD@MfzLJrevb#dYcBASM!uc)JqdCr$(3Fa-FJ8IsJ;k)y!R4+K8f8h8crtOkf9bvW?D>0rOk z5xDLl(6f_4TI>b6$R6Y*yoXKbD#6A}jA_SYSs+N>c#z67LC#$Tva2J=!EPY?p}lVi z=<{4b8lrvO7|?aMfHXz>?+&0(M``a3`hx`^w}gSL3;^kbfYg}=vL54pUmbM^TQfO*_|2=v~V=O&CVz!$h0`X847y6--a zfw)e4F|Ic3=eHW#BO*b1ZUWhid5p(-dh7ze6!YMV+ywJ>4f7U=`AGEv`?qUA_VfT* zjP1LZfPOUyqzcA43*#Q|2|O76jK=kISp>Wm<8{C~s9p=)5A*WF0Q6*QkdfO!PFW7} z0?tE#^X!Z3Q-$mP7VBp(`b|UoQXFqS#&OvS{Fz{#g>DCZ0QMUj2f7E2%ijt58(hyQ ztb^8xz+*x|I$-@D#JtwyI{BMG`z+j-I#|bwn6GKLKknf^*226x?S+0#;UIh9I6W|4 zXUuc%C}`KkdJ*oAWL&R#=vN!#m^KCMmtftU!2GFVUH6NHcG+N%8*%?M;e2l3zRrn< z_Kz6fRjhl{*}$JH1i1n0XN5cHCYV`>tasVD(C&=u8H@2)Vtzfb|3qBB&3Ha# zV?D`Z9hG3cOg036I}$;zzbu+WX_W?Zdn+#rco5h4$wzAa`Ki-(a4!a9`cR zc>J*6Q`~=>Fn)LJw?hN`Ove2`e*);&ux?9nJV(s;aC>M!gY_DQ^;3oO>^l?MM`3&; zalGeP$L+DcY;gSH824+;Zv&3g2jkAbbCSdTdJ^;MfVw>PKaKG?;dnoBJr`g+FR>ny zW*rx9A^{Ok2>a80@rsHju(#SWCyILF<6gT zxIa7(Ks;4A-y_>VzlwD&iSb(Dyj9WvXPozv(O~}x>&q0!)5H2Qz;#UU1-sF^LC)F= za?4bZE3v)W8uWVXciS2C8MtnDa6KMH19!*vWQ->+4ETGD^V0~>@8Z7yJQ?(vINmGF zdjLP^Ol5m z7cu`gaX(KB1wXQwC*wfSm*YAp>4Tmz8qQH8+((ncfxBT`MwpLrn1`v!(B2#4h=~P# zNDRm}tYdGiTc_i|7vXwd!F@4%7jSp1y9TV22s}^9aQt}8ixaMA{#fWY4d)+>^L~%< z8RCA^!*jV7>w6#eKZX0%758I|5%{wX0eJ`G^uoFx?g)Gi)*%n~O&p#Fu~?UO*xwx2 z>ov~HWj^$;#rYh^b&yyA{1n#n_r;)}#Co%iY=jHd|KcOL2zo1uL%&QCf4^d$6~h<>avKdCsM zzPL{xV!b+ITzJzbctF2m%yTK`Wz!+xb8$X(c<=a#dFYC9O~H6Gc7na^P>=^P zp6QXG%j0|{hk!m2^EDFpsh1t_nk^tb@Ep*<^&Y+fxB|xe2G>>pAn>PnuI^>Dc>eFl{A|E@1ihfY zZU)Fe99M5M=o*-}8MvS1aNi!lyjtM;S)2v?C|r*=%&!vG^&E_6I<8A1=DT(p^sC4D zs9_yW!u%Uyy_eg9-6E_D;rm4_uDcoLeU%-YqtaN9G#mOGMuEJ7^XrTIr(rbkTi9NK`LCP@{3x#Db{sbz z&-Dp-ez#)0QCN?=(5@@4&qd6O7uMHH^e=&RF&6956`$`(xNfSOVEnvfkkfGf4=h2K zoCz`(_pvADRro$ph<0W;uS{%zkNJ+?2L0u+E_&hmYGHl7!2M{s5$qIjeGcF`A^dxX z@O^U>K2N)Zf&EDICo>-OBRIbfn5V7wz#pNUgzIF5@jk$N!dA4ugL)$7r3%lf?`Yo> z&+mT7!?1qj(Qh*5X%E)Pk2n}#SO<#OzZutQF|Om6tzc(|>v#{(nKrDm+|$q=kNFvb z^FN2}KDcgXxZXNge~<9|@x(eB9|V5xVtm5qyCjaQhI!IJ>An!`qjBFhqQ2MyxFyzE z0@leE%u5uW6NZPt?mXt@>uAu2;=VXA4s`XkAdB%{cs~Sm;d9vv=Vyg}^|GLS&@_10J#~@YZsj7J>360v0e1 z82>Lkuj3-XUUz$e1rw%m&dLPrS_|yc!g4KzHsdcs!u${yG-@& z6DHrIQdRDL1La>{FML3C*{&%WR4&MOlA(U`Vm$iM_NFo?HmK(On>!{3M-=!DjIy)}tQd#zVoD`L3H3CLa z+0;Y!2(?o>e`+O_&dUbGP`<%#>3gbY7CvX~HWdN8C?9%q$~MYlO2^1i-FDu_$5i+6 zEp(>3-#f2ERJT*Vrb%_5p@nm)G+!UPmh$%{7Bi{d<%)k9)laPnIY6cJz3r?XHc08@ zQ@w23=@V2&yt}cO+ArKaYYCN#UCY>d-Lq(Ad3Jj@Y!kIhEDpF%S>mRc`4=oYX>r3c}JS9m;R+Q$0)B+ahpZ`NF|hNQCYW3xs~pm z?Cg6tXuEWxvnQ3&vikk0OqT0%k=pru>A><|o*HwP+AH!TqA9P8P+|4weP*6AwYQZ> zZl>JTu>X3h$DGoTr*eY-mw3w0DtNKF^(#=&q}*P2tqeqIg+9~R*np64edA}Q!hyE;kMs-i$Ub0mB9^P<^@+Ff}?@(Q8t0>Rf z)A1+e3r$9QQJFvDF59~M|G=}o2j*|YAPx&s* z=9N04RD$_QGdNCp(!Li*sc!DB&*mGr{Q6VMs|*T4Dfc?`U6RV9Y^oQa@#V-B`VU$_#U$ zylnRoU&>`Z7<{DqHpShlRGymd!{XO??8WL+DNl(Lw2DS^cP7?qEfETKVBZ$}_pE>EocgPuP|A_Cz_Ep);lO1ULlAd!5=y`afU5^e_R!s2jP3_EN0_6lm zu2;JsMfFYG(tXrk^0m%*YWGZF6HIx{;xGA>=LcPFr1~iLc$SZtvCUU05BbsSIpxQ$ z2TY|hGpT^>FSioaRn(8w@s z*(ZE^J=LS$E>EITYSemm{?4%Cim4vcQ-;;=QOl82sGdD1Rf+m36|jGp4w$}-OcKBndFE0)K9ecPIj(3^zdQ%kh-#y zahFx^YN)-3!_piok1q`xNbSGx)7Ve_ZCw6}J*PSw-eUDRmVbvm2e{`WSe!=5-}X>D zqpAxwRBHU1-b(%SExwvd^(*!JgQ!f?vSsIWlvDe=RNuzm!OlnXFu``JfAw^eqT?zy z`?I`ToKs=@&^qH9J7?~7eQwA6=v1(Mc%j}ggzB!vrdOzJTX(J-l}V>d1k_Jdmxs@3 z96w5JhEmRD-yTMJuO)IU?lJ9#v2}?q_kBU_%;(%(LhTNp*Bs3Jxv!L__Q9&2?40N@ z?}{wt{>K6?P$_%s-a9I98&B4v(r#(>M=GTRX{`RPMlRH&deX9ytPZE!4rTSXaYF>- zPqgD%y?uKVsY>k(R6_<($$wsShK_sJv7(jgPP*ymXnZx5OIZFrAJ64ce(b1fM=DL9 zw@jt>BLX(E`-Ah&`$g0~+bgjL<=tjA=~F%Q*Lil2xcs${)l1`ryN0y?INP5pR9?Cn zGmOgg%RSh==4EDPG3}?gw3gM?Y_2_9XJ_LxW2l|>-i`;ToPK1y5tT&^uBKG>>o{sQ z9XB)RHG96D;D@vJO0Vl|-^srX@uqefk~*wTJ0w|IQhN*C;q08B_8^5lzg3s+W$SwW zv`HT_Or63dg>8d;X#rpd?HdD-;56T6p08(Tl4{kp9=5J}~z-B}B$9B-4#p0jg&E-s~d z(g`hAFY(+w4LWXo$zfKXm1B##u=atsQ>h#mekp^>S7uGD{w*#`KA^ga@4z%FN7WCE zpn0}CazT^&X)Afl>VC!|Pqu$wJ&x~9xn0gJ7LUxDc3s%M0PIwlzVQc`pomE*!pKKw%kDdH<~W|QoPzYD968?WlwedlUp$L`cF ze|y|5YNwm|y^?Yhh2vVO8`B;|M8 zEScShSIvB?tG4W6`7``syN>yNVtawgjFv+aX!|q;3wFQMxhxq>xw&#uBbAr>KWEP~ zCxcb&-qyTI)0x`0iyHQZwi}IFKA6gL_X8eM`N*J1nzr{n`@Deavc@LnR3BRyd!FjL z2OhBe7$|xzq&)bdc`qvE^gHdM?Gu#3*t{Nj7_;$wg0`1aKA@Wc+kelbDmqa6+V=Wv z-uItrvvcc5%dsqK*K{l6B$djaeA&4+ND#p8V_bC5HOlJ>6(Mg+P1!vrrKbYx_s#Uu z1?u;?lCKPvR+p!r0fT6SBT z>OPvPPpE#^>KmI!ZO&wN9jDUa)E%l2uaSyz_l%SZXKluPVVZKEs^`yI*+Bi@sciP5I+uTUIF+M&S$?G4yR4Yqr`|Pn`$F}~)Wk9> zb!=VTsNIna7+?PU0d=a!j~d6`TN+%4+@y9_9}Ui?GCTbn+c(3xSSxB5wNkD>m5p0f z6sf$OJ6M8BjUjs0R2J$qrBK=6+r0~w{knW*?FR-PGNt;|oBVcEW}1BKNu`5ND66mN z?HA`!U64`2;++<#W=Qo-hcjN%I6}q^V*XcK7Uola{G;g|%3IYtuyHP*U%~oEyf$F> z^-Rr$dep9Y$Dox|HeBa4sPrAv`8e$tc6It*sxLZuhu!13*tBbuUs&lenR1_jw#;5L z|2=CTVD_G^uk}`2X4gD8^&+*?SbY2$l{2^P??a{b+KX&GHhnt8-rsMAooDOOxc7Q4q%ol9AMCIEP zaSN%`e86XQ+nm&m)lH8H(qn0zJC?Sw_ktnLO*5(8;~V%pYvj-NOTQYg3d-eOS8t}WtijHeN^^zciL}3yhaB5yeR(t3x{q6I!~7(s zyR!AEtWs>C_MD38FDm7}sIm98{2hh9R8Kl>vXe@!ZiUHIS|{bPb@x>2Iga`tKIItO z@3O5)P!DR8*m=?6_^V=SZ+t|X#Z~7}%I*tVv9H-W>A96Ke+vpzSpTabuWnKMmr)hQ zRQ4Nsvp@A;v*s2%XL6epMp6E0{%n@7gl2!1?^(OQZKPbzGKoDWB0P=Z9Ctmz{M}Mt zH-h@{+k9v+wQGEsyoO4L?~MUe{(Ln3K9!u}%HCA^RXtIdS)KTlC|FSLIDcm< zmHF1O%bEPv%+~kbi}V*%*WDe=&drii_wTgMlRiGtr{n&deU{aA#H>@E)J}ClF6*zI zX2|kf&M(cNJoZb%2PzfR)_V=Vg~W_m(SI+b;?lUP3W4I}4JeVx%mwvVoLG%%xnk~$f%dN6;{ zKaRE+1(vfs$gV4kpn7nJ5_WEGJN#3D+MCFoV0o7~FdEL8aWT2nu4{)dR_EK@4mZ<0 zd>y04-Z$3Y`?Z3$zvx^yn#vE+E9Te zp}lv+bFuyp8V8EyZh93~;`~fCZyYO*w}uX4|7A7dctYv_c%R7ry`?dm<>knac4Ehs zQ`emMPygn9OW3@g>a~vkC!b!C#{7KJSs^lC*JKrDKO!o8%s=gg@xJ%>Y(E zYdqt{+Ut*t5F2M#)vwm#{HNW%DY7oy;l0d%vy@_o@tI zb=ltKk-4~Wy%$c=6UQH}h!V-quFnc=UtPAT67et0&!42_yoP^kU;L@RkZ(EfC)Q6n z?}^B|3+?~PfA9JEH&6e6=jn^`jS1rR|KGa!f1@r;47Z5A|NLov6c^1Edw=+oy_Wpp zN&j5`{10oz-qYqkS@gg4emn6~tyq6`H7+dvsPmUZ>hjud2eJ3eKh5iRK6CeQcK^8W zvG(7}&pGpZV(p$x$`ZRCOE;9Tdtr~z@c$^zH)h)7|C#^aw%)dQcK?sY z<&F0jyS^38d&SmE^^NDOE`F61v-@mnQ5SZvTe9_qE?xJ`m7eV0=RIQcWU2>EKhN%~ zea30qP<>5%PnPGXU;Eg7EPn1L7H_A=rtIGHxxYQD>)X7?Oy51Ek=;98k0_6#{dX)l z%f`)X;@Ezw=jAZoztErMY56IAb80s%Y9o7JP<@uj#w{^}@zouMF@K|jPOgQ_eQ@#_7H`P9S8P667hPDp?CJw7AF3)#Slr5fzgYW7 zxpk~w7G&o!-CFOH$o*4x#tW98rAbrF>A369G_v`ezN&0SbyL0hBInqY5lL)5Z*O|C zan!>fvHs2G8`=G}qQ@@QzvXa$ww|F^a#`GWQkC_ozn!v0EI&T=yBHr-sLtly=jR4C z&e0D=jNeS%!(>^-eU|6V&lH)RFfT&+rTi#c=XZyWi@eu#{!q-;W393h%Zt$dY~n;# zC%3eDV(aRlWSH3cw=X;*_B@#ua9X6!H=G~D=F!9XB#U1-?(gzfzpJbs-hb`H;u$h{ zuGsyxkM~mSd2pccv)K2A^q5^D^Qs8QXa0Nj3}WN`9*50hb91^bLVm^MhS)fS?dm_L zv;Dq5HJ8oX?P?IqZ|BX)BF`0}{mT9O#PW4XXIWfAyLSg*odi2piL95f-6P6M?73Ie z_Zs69g5f<%Xx}4ji-`YAik#Sa#l`BebuE40!s@m1QI5!Y)_7Q5X^T*C&b|Yq-64^(C*Ii}tkBzJnsq+`* zDQureyTyp`DeL*?zBGZ+Gesi z3O>wc`>M9(WH-u%^GrCB$CvTZe>6UOc5yb>fvb5K zy}`*|z~!&%v$f5G&kgNVo1I$A=Nu)g9-rRN=iZh`&wE+V<6fIRwDLGA$-TT8(YozE zk88drbMnP@9{HiKTKCvIN$zLEF`i@%pIfkK&Zv`Fgbyl~?Rk*T<;K>#4V}p6BIU+C z7LYGvLT}&j?>$k#EnU(@#ff~$(t7d4vMln8c>gNLe#!?0+^5zdzH4&$T+JQ7<3peD zIA?`BqcfxgoTG}8dtn5Ri&D<3JgC6uyp(6=uAV0#Kg5*>-X!s)_xU*C)h{0T?#gcF zvJyU*v~zI4o^E_DSX#k2W+9*Zb*9IF{))tYhPHJ7am4?4)fa|we6I0wUj22_&ydI!sHJ@RZO@&Xsr%;mcjDi4lx64-9+x?^ zi@y3HK36b%^4kOCiM(2xr@e6A$9-n^}i$AxZP+Zytf z&o#xaycoQl&vktD?MstBpEGI~f2L0hkIPzFYI!q|$9*W#*C=);{mvJTw8$gte6T+B zAo=2``)wWPL(9oLR{C{%T}bL+-zGczr+iLkZGmn-@{2`f_S~cm#Gh}Wm5PK8k6YVs z&xQu_1%S&~Z>0oF0ax?Bd6+thFZ0W%#n}o5n1Qt0ceB7(_H0>XLZ; zbKI3a+~jk9ceg#UA-`-jjBecid-!;#ZyMb3{XuOMgc`k=snxZ_n;kW9~_i{r${nk)eRQswSg6n5@6~IIFy` z#82qL+>L#l`CMZ8;XM919#@iL8~v+2k&i>I$M+O)`&`>;7q;>_?O#5hPLVGSKE*x= z{;-bE*#%3Q<&k}&r1ZS+kshS}vp*;#%_8+=Q+4mN7OB568l`=D@;Hs{lUH0nCEzmm z7F$VN;mInWPN%ic8CwW;IZr5ZRgRlxRuR+BUPn${I?%I~HS&GliQ0n?N zkgUJ+pr&>vT?Aa2@tm%i`%*FV~?-pJbLx3b@Mm z8PAS33%H1EeyDD-1gErVnonsEsjE$~?V=u$yo|NDo<@FY-L`4wM~^Z-r=qr2?;+Wj zHE+K9UulxyE`_bp-7}x$N8Tbc_YC2K(~NJBdXpJ&?`@YZ?YYb0VV+YP`CN8*tHZ~3 z0`9K2VoKJ;_T>9yU1P6rWL=&OX?K4*IY*Zs$>Hr1aJurYa}&tA<=>s4*_(WMnxE6U z=_e=2bvpB0-cf>_6I#VN`^Y{@En6IUkDMpjo#S{5!pQ#3xZqVme(~Kkz|UH7m?UTW zMnCo;+5ZX|Z_cEVFT6H2UL3KlTELmN)m03CMb@|FS^mc9d`|Jwi&GbQl3dDXg~prT zc-%x|otU|fWIfLw_6YnU;BN2g+O^hIz`69FG;d%4pEFZB9yxL(S(nqV#{Zfu;NEur z`1$y3a{ie-_7+{=|eRKukO96#{T-NR}m zU#DBF;>o$Jy{z5CGsS#P+U2<6ygU zqoIJ)kR7pcT&#eTnqHl_lhj?^?zO62wFTVIAM-P_-t)O0dwewp~OLFV&+unMUy4D`rPCMl* zS(mIadrZmm(ZA36mNc>tN;G?`DU$eAC409kC3)B1cKLBAiI=Nux5s)ApRG8wAq%R@oWP*xLFnO-XyiJe0xslYZ>6~A8 zKag{9aCGx=B>~qkr}2`*QUMpWV0=hM6&d$*Mn~g=d@e)&$Xlmq0r^hhOw9TZe2%}u z*>M^rpNb4T@;?L9e-NDX}1rZ*yk~Mt|zRmzPy0s zf%{O`luGQ5Omr#TP0oKSkH9;Yg+`)=76RQ@JbxC%ATbL){ z;xFHj8Y3g%tk(L}7LezRUAx4APL%@g`>%paqlXH(2fm+TBgyl)GRRV2k<^95&*X;^ z%gA$o{LXJ~UZj3*4s}0G&gU!hHn$}V5O7XiHhZKt@wxh_#S=VN2soSVeq9wD$a73_ zj>$!GE@y|&I`24Fl8abqE4jIp&nZ3jh)F8uaY-g#0b8!|Imv7Mk{5Hx^VH~9R2=z| z_~)6u9VfPvpamrl+Nw;5h}Rc_>*%@P>zCSRJCEY7;^ zL7q#YX$DT|`Q-WUVPGLe@_$%yroxn3(odu9>34qt7rdr>{)Z4g7ZusPv15UNbJpGH z7D@77BhxAN@EKAs@fYH4$$8?lx^y02K%Ngv73{Z~QGM};qjyPtb&@Do+r;NCuQQd1 zCiRm!U-DtoXaTo2x5Y{>mhAUv&+LQboGMN^W)n`y2dvbq}b-Z{+Rg%m6bSVA_c|JBS z)lN!NYtLD{8@Fn|zJOD%Km1;Crht2VYv67Tat_FIBObgXaX#;>J}m1r$@86B?X%>( zlM1X`P+=g+)fKHz=&wi4^&KBCY`;qK)bnwp>{gYNr!hoII=EB5A>|QE#O=isMW`i`;3X4BX-Tv`L{k1|M9JB z{D+czhhgwj!*j{`Q=-~;2ay@Q)~EIzOZI%gwV3Qr$InXNy!Vj(dps|0EBP`w|AW@uZlq2#B%U}--ofMK zx-E$`n$G8{E}!@mmnOkAj+{EE2lh8?bILnOC}QKzTBsyX$&d?G||s2F7dIj=jp~dfGLP>`uO1_DfA497678 z<`p&hY9#)~_Z9MohLC;LVzjk6iOj!1>rA^WvOcSyMvd1a=SGLmo!(dTIGw2u6N6Tg zdtLDl_p(mpe5`w$BYlL&?X);N^(%S4=6z0DXQ4>m2aac_)sTFgve!>YCv_gt;g`v> znIvwBI|*@Dd7PA|^2&u|y<>MhZZ~)Vx&O4+p4MDS?z4l#J3c4pU2C+()@7uu){tlwD_FX9l!BaBwyA!?Jcy-$v&UiyW%xDZ!)))hP#sIU6gum z+^s9*JR7>Eo%#&2uZ}gWd2PYxuFlQA?@r!Bp3hq;Pyc0voyURFxrce&yxMDlk>t6y z@Z32W1M>dkoY(qzZ)Z}s16;?Q>qMU8SBgHo97y)RU-e$kA|4lHGNjwxR`ML)c)RG- zI`Teps&8tOA$bpUv_E^E+hxvzc?ZscOEC1wL|4fGmqmxj7{E4>Qhhc%9-&30jE>4W>Xlc$HuKIilxYVkn`o} z#uuweU7Xnb_B)A_JG{@Wuc-uQv?uB6L-K`Rp~4x{&bxTr{OXSrV)b~O|EF!Om;Q| zZzVE+nZ0H|^2Js}kZmQO)Fb(qlV$hF7y41nS-!izN^pkGpDrbi+5&9>sk~PDv&2uiIu_>_fh&Te8$oa%;E* zm#H?Z-@zW_ybKJjUqqR+F9;20PkEWwhsP2Ro+b|;6@?wpIu|^eS-K~RsCo)*%z9r=gwP_b1B_d&f(`xvadcH z?snGYaU;(EsOm}X@nxBju?xxl%)tKr>oy+gcVWScF-<(~;(>rp<1dl(J# z{dlu$HlNcwl{4msG^yiPZR=+&;BgkGPwLl_d#$tM>^@)Kle}J!kp0V2G{^8Y9 zeRCzbhr2FZwDoigdC%%P!6lf?FWW1nYb@E{hhKCZBSZd9AQe(PvW?uod@kHDn?>SX zJMYeCOA>##-8MxV49NMQmJ^Y7o#gNH)XJjyqfb0^#I;0No+YIcziPY6Ji}iPp5_^@$Is?hSFllq@R8U6V>mPgiFFjjE=DDRL zYa#hUHQzIK+(Gi(%ssZHGQ1a$>ts16Y$VC2YE!_T(qs~cjDKkcd5(DOU%K4nE{S*j zT9YC*J{M!!u0D>Q2T^9#wWRLsJ_d&PkiUBvAK zQKb77@;u2O+7>Upl*gTNi7yNy|DwnFeAT5MIXq5Uz_-aI@p=0P&N2BU!R5Mi+9GXC z?lnWk#|Ca7_lBtKVWFfxO9PZnr%mH?)sE47&XM|0pCz!_^@+UpIJ+Glb(6|S=A<9O!j&gYMj`}#ArSjU|b+|a={ zvTlDSc}@wR6}ynUCl7eL^o9w!5407%)@vnyKazI~(IR;g{=M`+m5mFJvcETH1eCJB zv;W?|>cIt(za#(NF4X;<;CEWbK5Iq(el2YOUB)~3iT%5HcK*qKtN-u*{-n#?x+>OR zvwcsoJhjJrvHW-cQM+RP<=?G;xBtJTD^5xj8)xvM6p`Oyevi*~ftE-f0v9xjt&iXP z{jTRPw-fuj(eHMD(m4wkk>5Rjw@Xdd5!)W?o+I)*T+#h5@O#vj@gl$f$u&-7@pZbo zn*Bf8{_fx7(gm@<^JUhTi1;a49KiCG*iTRF?$>va~tqD-X7yoBwJz7--| zMtuRREAyO;vEuyK^pF$T-*O3&tbTmBWRZ0d`mY-I++3U=gST_U=12ACZT363uz%fl z85U2(Q8%&v^NmM|{7x*>*NpwHWE&hS;z#JuaoI_c{BY-#+3$3rI^Xog#mNtg6UoDO zvvQVy*ZCGAc@z5olT_T_Lu|eM$^K8=S-oCt9crh+@8Eyx_m}^_@g3aXb@;DY2Zw11 z|Izbk)2AAd`%jJMDfWB(-}U*gS)c!^ye?VVByykmUmd4e?}3H5^Z$2$RSO>dTXw(q z`;-3indrYWvHiG{J@3A+*w3WFj(Cyt{^j#Fk>~H9{0h0l_X?5wo#W;RlYeKP!v1#a zPO&)tyN>^!&9N4~!}*;H^`@5O|0vF@QUk?)XCw6YyWDYYmDqK6{rFAn_hi5K7wTFO z#sBiYEVL8K2@)D2=Zwr7Uv_RZ6?_-rLi_9tx5r?KK2^OvqSg^edOE04AN9GlGY zotSCQ>@;=-iM(G3^TKz#!tRHGoursl@;uG%qpC{pMDG7dol2PBK$n|ryrjlv7MDfA zJa)fNY;VT;mv2AL>Rnqtk#UO_0h^c76K{6^&EK+JkH#NSG?&@GJYvu66$g)G`&}c; zm;L*+rsmab{7|n>tltu^BTP@t6|j6dEdI&t9DBcqJgg02&&iTUi7c*A`F`wsRK8Us z>qkDiVNx-tj_qHM?jh{Fsci4c=3ABXT4bNb`>C<`zMrrZsVk}G3nKUOsIu9tPWeN} zh&-o-^%*?5jJ0RR4Q1ooY0I&Dys-WIjn5+WDR*v-*nODf62tP@)MNR-eE!=G{KVEt z=>Jbrn3vz%lR{kB`-?`WP*#V(w`bgph50mmWN~EPZh?9I_V0&%j`CQ%!so>AeuR4B zDi!8WX~axcukj{HOh(iOvGtJ=j1zgT2>Z$1krK(9kY{#yEAkwPZQjoO3)_E}8XD8Z z<}-8d0#+wJylx`v^?Uz*E2Bi}M%b>pY$L1NtNr`1dM)R*iOj>~OpC~OpBnR3*!!T# zSrwMgl9b+T9##83uzWZN-4mI|)v+dQTpzpMBKz7x_baPgZvJf6uh~G2Nnu>E!{>>O z_fLOMGHL80l24(Zs5nm+uTXEAvQTW@W^4as^ET<+UZh3nm1AjB{Hv@k& s@HYd0Gw?S9e>3nm1AjB{Hv@k&@HYd0Gw?S9e>3nm1AjB{|6>OJ8zqz%H2?qr literal 0 HcmV?d00001 diff --git a/test/data/samples_20240815.csv b/test/data/samples_20240815.csv new file mode 100644 index 000000000..77188bea6 --- /dev/null +++ b/test/data/samples_20240815.csv @@ -0,0 +1,1025 @@ +seed, publickey, nonce +80f531580b97c8de305ee20b7c87624b022c65badf37863f75360d9b94c8b748, ec8dc8f960ce120cfc670b71120eb3756fc77039f81f7e9f763820c1d185ee78, 59039e870f2626a33bbc24821b9c2ca6bf11aa89149d3f726fdf8e3bdef41b03 +33599e8c36c33829a77ffde84b0c9a63fb6eb1fd984a7202b27d8e98a198d87f, 10833f675abafcda6ed8962325b5492d570f04f028c2c928a613ad8f8ab4b59e, cf2a3e440731066c9554203f364597e8e169306b16bf5dbed16bead466db9afa +8c53fb63add5ba46d7d35f3e884436c75d99141fbf8cecb2ceb4a49bec39b453, 45e4cc860d917fd83e2fbe418036ff8299b045ea8ff390c4e67f226ce809a703, 57ba057d57a2353e562eb11e3cd06184660e68e5a8295b0077cf65cda8455032 +00bcca3db8ecb2e5837ba713a3311c6296d4bea989759fb80bcc03684e33140e, ab0a50c2ea6eda36a5e4bfc9de3325682b7e95d3514332d437f91892783e0ee6, 60e4227369abce0de26272a110f9313b4422259d493a884f05c37f6dd85afc93 +09c6876f692ee4f6dab5ea33fe0c30ae760d963a65d7f5ef84081847d5d313ae, 269e8f56b6a6fc9467f19df64c7550f331478fced4351a8b88e8580cf6b9308f, 404466a7dbd477b2f07e88abe497e978b5f92f62bc2b73e70735fa1c6fe05b15 +b8057dc06435a52da037aa6b0cc8aa225eaaf35df702fd55a3c5b7e1854ba484, d19dd21972d9a0c94c022d919aa6f4eac56198d9f74d79fdc10f873dcb5200de, 6be282581136b5057f68efba9ce8192761a5964fc45be06953ae8311cab57865 +5905d90d0aa97296d4f7afb08903cd2099dfda1392a32dcb31ae0cc011885e83, dc1c49b4e9f7f37b9924dc6d950100f9757902792607a94123961aa3edf7ab91, 4b2259498dc9a5bc3c7cdb80d90cbe3fd770cbb36d505ed3766d57fd66a51088 +a2a682a0150c46484903c8230a9c1e6031d49c480e5a4d1279afa620f7476525, 4379baa2d19f02fda714720dd5499d1bebc3f77819d700367def523c1ecec35f, 99a9e19d1294be153356c4f9a23ab2dd782b7072f91479247397e160da77ef9a +3d8ea411bbbe6d7059a2b526c624c83d3c343db76f537c4ab2f9cdcc5675b572, 3431ab5a9fa62e8f4345ff4a125589dabca6e6c7054a55fa7d83b702c0555926, a0d29fe37d12c1620ff24a14d73d9286ab3dc4a42bc26998e4a4f014d1ee2aa1 +4e8fe95c3c588c4e2c91200a25b440bd6370666aa7da01463b46380c61687dc1, 16ea6d89ea0eeeaba1bc42b0975f65cb35f0abea32f0ea0f28aba05181a284d6, d3af183839bbc4796bb7f7caddca8692ed054af06bcf9f537d786fd7595be98f +d93555f4d9708d8d5769a9e67538bbb8bc972b13f0c050e1b9ae3e0e7b8b4f48, c72062a0540da03bd3c0896c6e76bd8d3f30f194a453d5a238b38f581ae599d1, d5755ed9b7aa87c0f2275d8904b1cbb62406fe83966d86eebac4eb76a86297e3 +32a31b82aed7bc6bedb3ab19359bdd61581ec14425fd50462f61838cdf638da5, e950efa0811a7dae3757009b39a3abdc32933e6fedb4d8e0209ab626fe5440d8, ef3067854218e09f7e852fc39b50a5eb3cf7b29032a783cd05519ca16e04669d +7e948db1a7684a32f89a42438ca2c87a47091285bdbfea312aae387d76947892, b583c78ad5c47d2325f367b35921d343bf9e6de0e44986134f2c7711314a0cd0, f993e2c24eda4ce2e62ef3741fdc8b3e0fca5a381b3eed4cad930bf9ada7dba6 +2e162de4cdee20213ebfcf2caf4c04a323de6bce26706ff75d9c10a6558f94c3, 09aa8fcbc2821fab3bf7f0bfb865a71c656500a100b0e12fa9d8bd526b360bbb, 81ea2e1944262bb84ff4be4cb8dd7d55a50e7c3dcc959d0294d22fbe9bec181c +faa36a4f7f4a24d6b0b0ed20a84f3f88107d145aced8d0121567ed306a1a5b54, a4b440a48970f9f6b6e206e6f7ca9f2565797b64fdd6fac9054560c7c0d8011f, 2197aab5575d88589eec41860452b624305c28e3e79441792b310c678c02be3f +e760dd45e081d630936cf558aa12df41933bbc2ef5feab212d05e4215410535f, 476886ba5fd8c152ba27a4cd96b3506c21d5960ef5ddbc568b2a99aa10415a6c, 7fb7e252ec4a5fed05add14be6e83e32a91bc1ee249940d6e476d2de8a8de664 +3e09334a94cf09a1276db79eafe31f635c0b7228133751bfbdec8fef6e627f1a, 15f94a5ef8e903bcc5006c1b4eff4cfa4450862cb3ec7106cc9ba314d3c6434f, ac69a5759ca9e7c360c1331773f633df73d707f56350a508ae85119806c725fb +3a1c7bc599df3ffd675c75a36863c6fd5cb64d9bd0c9b4d4be971a5dc3610bbb, 87216ca3baa3bbe6f865eccd26b5e004ec21cc479e54f206d77042956fe5ee05, e3a1dcd5e679fb18aa2c24d9a5d61435b27cab8922f623599218d864a20b2246 +841ec7849705b75df1c7a5f253b047acbcbe7b41c1d3f023870140fa42d1da7e, 4a052601f6268a63a30ef7c8003c7bdf5c3267fe296b7b14bf8b34c18494bcde, 71d4f9d959c5a605bdb28f42558b51fd828424fc0cd2967f9994947e669c0553 +c19ee36ec9622277e71b372ebea2f71a07a6b394160d44c75eb845f27e248b60, 406d3ae9d2ed39d0a2d7a70e3b5d04d01cdc610a8711971a3a016c469d77e761, cefc6eb11e83a15480e301258200d2619eafe2de434233064bd5c3d6e2e00dfa +7b5afbe57d21dcda54a22da1baae612c78d869bbaf1591f6be115e39b9c704d5, aae174b0420fcd0bac0db4e3c1b80ccf44c893bd0fb23b7a633c37d68bc16660, 171b332e29d830eeda828e2de0c7c033377203ee68de12ec30f1c114188c7d2d +8351f698ce4d8d51bafd60b3d4f65859d9e574340138d2310d4e212715e325c8, 58f276daf479d6003cfd58d8fbea1ca398c8f441f8ac1420a18ee135cb9fe0ac, 6f4961ea5932a4ef0b1949d9575c3e3c1a8a6a584f6ac9ca09e80f871b48299b +c849cfa6b1ec4b727101a06f3c822575b4924df54120813cad8bf669c165367e, 08a1d0cbb5de9d5097c06e117ef635c5f8270e3eb7980c9c25fa05d723e9bb2e, 3896216d13bd6ba119f955728370784b3f5502b1bc9886474b1e3bfba1a352d6 +b9cb313d7852624a4add3ac76bae5d6f51f755fe1c2755407f644127c645a863, be7edb2465d546372f384491df1cadf8cfb594b4dd37c3a13a94b2fdf628bb6f, 4381fa78fc094a53d6295bfd4cd038efd41b0d4f12ba481523302d9d0966bc37 +545cbb878e0126261dd69605e10f02e27cc8080d593edc1f5d9a6b7432af85db, 86e169e92c2e42970ec09e017b604fe2bd6728776eb792e5130832502c5e7541, 0a2cb9ada195325b2dcf914bd38c8a8cf1d32763561205377698348b7f01c734 +3fda0ed9c632857d6e0edd390b7f644f40739e5be9a028f21417a9f44601b0ca, cf920d57fe3a5f6b5abc28c068c94033db6d45314b3ec8c05c38439d9f07f5d6, 407927dd89cfef4f9343cc2760763e2f7a70eec9dcd563674b1c91bb07a93d42 +2fe4e34d532bef8c4ba5ecd715fe35d1e195803dd4c6968739391a2c47cd059c, 8b6f647242f84fddc5bb476455895af6506539727d537bcaf16f556862525aba, ba485348975a9afc1ee5c2edf87c25b8b02cea4f14f1d7b6751ef62bc4c27a6b +8e26e98b5da7ab3a69dd52c4e3e1344b584744eb4a03309140ad7311866b1f0a, ac502f8894c790ad38ee8d4e54fa816ba072d54f8376ef87325d0795532e0956, 7284ce4ecca0ef4be435026f9a4d94e1a37311a1235ed2272f4ce24757b73bf8 +1a053b9c78f7a7660c6255463b626a807fb47d4af36c6570b4467044495623dd, 335cf0c6f0959112911c889f606956b911ef608b80199284ce6b2c9a9f76473c, f1b50598e2ca32c6440e6db8aa42f2e585d65e52978fcb11e3660ba4b282bf31 +cf42c718a7f11d6b0e7e63da48029a82bbe57dc4f1279965462831ac24456aca, 3c082e6e8d68120df35eb7512a7f92c6ee4219bb77b50ff91a1402f0104269e2, c24506eb048805058bddcd39942f78901b5a511d1185088dbc543f594d80e52a +114a7aee8996ea110423cd7ffa024515005035b98e160a6ceaf26782cd625409, 70f1f878cfa0ddfcce0bc21f11cbb5ddb767d1456712687043fb6020f0e9c9f2, 8551e022a73368b63b39e77310218e268499c47aaf1af07962e5f562b39c917c +722836fca4ef287c7b70de23240793da04c49854285a3fcb7e9d80752d63a58c, 5958922a179c35522bd603b6674bb6cae9d9ec8d090e7ddbfa98b2e181bf5fb6, 780ff13e9c1775479c944b7f73839b43dd16810ac2604c0893ebf1348220d2bf +b3e29579b22478f6494e0d7e0fe966496c0f2c2e596cdde504ecb2e649a90c3d, 570ca0f5b6b4ab520ee601e6ca375d78609d8cf5516f20f259841eddb9df916e, 149719ea5a449fe6b86274877fe4e1d16ed83da12d90f440c840cd092c176134 +d155cd872b02c54f956c2f01aaa7cb1cb547b9f918f92eab81a560d7db304a9b, 8ab08fc8265aa85e2079486d9a472fc527add5eb1ae11d5082f9517213d42f33, b8fbf5dd9fd892964a6fbd7c98a577ac5ae87679302e80bc4c69ded252c3c809 +b4934b1dc30de253ee67ba9835858b4f9643d8cf8d428e20fdd827a0b3196946, f0a38cc0ed32712affd44fdf485d5e92f095327c6483ff436793e88bd2311ddd, 37422b8f8182bdc3eaf406c8911b36e546a0abbf00a5b408cc778b4944f3b178 +710ded5264331e70caa7d37b3251a5fdc4d5e6cb9bbe9f4622f0a0e8fecd8b3b, cd118a6eda0338bed8a04945a0070363a52adb0d861c39857b29179085e576a8, 37adf1f6236fbb2c3dad5dc351f2c6a3d5eecf09a1a5b64175ae6674f473b63a +a1d14d26171bb8bec64256d8541b2afd6d6fbec6509d8d144923930a022dcdb6, ea28643e287da364b2aacc372445ae4f121818d9b1972ac2050caf958ddc8cb1, 8b15b914822c6d6f6c69c9ead534714cf602bf167c7572e43a176732065bbb8b +c00607e0dbfc8f7081b426febe9303c1d70e2b12e978567a50a2510526dd7ddc, ad1b94dbbea6c49d2d7dc098072aba7f83236b11eff4fceae336bd582f31ccb9, 71bbbf348fe841ca5c0e6d337ac48e47c683dfef5d2a4eae6d6e13e68409a619 +3a74d031ce7fd00c6305a056b4cda29c5dfe2ea4b1f290340033d7957963ee59, 7e78d8492c77820066865da1bacff13131d4be8faff7d420b38b2e4f8bc725d7, 5345ec907518dfb86aca52b67c69c34caa2c661acf5bb88cda4ddac7a458e563 +e7067e81f59054fdd39b4c84c984125f25078c71a6a4f1b480f8680a41499a7f, 46578970dc609e8ec08898e5424adf322925d50d68d0c54b9578836525f354e1, 72143871ffcecdf24958fa56dcc29f2b552947d6577716aaa11ad11c4ffcd4ec +355e22ca9a74741323a2d43aca7d6ce61dcef619e092ed8b2b1dd9c133e397dc, 2cf8f941c787930fc3423b42be4abacc139edf6332a51c1c6f731db490ca4ad7, a13718621c0411eeca4c7d817615f22de7fa28d88f88f04df2a0cbbda2725feb +574d0edc49a71cc118e9330a494c179df083f22f2410e19d741bc0dfe5b4576c, 035111f0e11bf01ff64d57f1ae86ece12cd5aa8fc91b1746e7a9e40270a7b97c, 49ccad663b19f6fb2142d86e1fc9611e5ba3f02c86a7f4578149cd09530d3073 +d3f11bb4d6e8c5bd4c66aa3071cdd40463021e29fafc2342dff2c9d7a164079c, 1d410ad2f413e2238c8f6f9449938b239047b31b817119a6e76c02da3e6a99d8, 8d0718ecd0785c979079747de5f333e3a05c7841aee391d19959d40f95a8a6a3 +322fed3ec3298d6296f937dc30fc1696ac73809464a3ef60694bf34881a2c1e6, fef059179e8f2768bb5b1a279676c56917f26363036c40204461ad649a18fa54, 2557b0ede6113697e3477bebe14f6f37933305331942d9bb494d37cb322d4832 +c5f5f796ea7ebf8c60b951397cc345d3e5264228a903172164d606abea738468, 8f7e868a4b446ba230bab9c2b2ed4f8a055549d37b2256e10c1e8f0e24953146, 43281b5765f86b7b733394539b168be45b62f3198fd5e0be4b2c3b38aa3d3288 +6c13dbda91de56e65fe82e903be3192c4c4cffcd9a3a9b4d5c1418b24b8a2e64, 70ea1b92b31365a2bef42b7d2e0a3bcd9db39243919e9b6e41622fa2ef81e2b0, 7c265d33289e0495377621842cae112fc0be98f8c5906f7c8350d4e6301e2749 +7e6be75fce6aecec7d29d71c09dd1930047fc8dcb24726e2d341556ed300207a, 6c33cb6585242091cc9756d54d8234a8f69ddd0ed36d11564477e749c0777e70, ee962dbad0cf1144cbe6940bf5e06acdea8d81513275be544ab55c6dab5f73ca +6d9f82ec5eb789ae39effaeaa617b80e54968d5cc31a2244ea01bb96a9f35f1e, 0828f004370a092c22440a1dcf92d565f2b58d977c4d2bb0e8219c567db21b61, a06f4e96044a448fc768d8e7993938c5dded73f682a4537367b8cb0b347de4b9 +4f8b780f6fa325800ec29fa80b4544f64d1e78ec8ed6fe277859a9af9d0f971c, 44ba51403fced34ebfde037e578250a4f7e762e71cb12fa091eef99db65a15b1, 4352396b1c31b975c9fb9c4ccfb581d6a69a1fa79f4f4fad4a1650d66389fca4 +d99fceb52f1c4f9797f39560f8feaaab5ec097ffd0e3bb40e981949405344709, b58ee6eb07fe56bf5a31729113c41cd7dbc78c3271e3dc8f1e801c5a2e15d90d, 1de3611bd60e01402b67dc191e9650634c8fdec99c89a9e9f7fc0488cb29595a +c5fbb4330ffbb0d9d11e70968fc21ae53340424f648607d9c86dce186fb303b7, 7a4e817113da2ab9d5679a7a332baae68121a33affdc1221cbd3dbdf568ccf04, d3ea6a747e593e40bb6673f5123f42ee42b908b0a23c08ece26b94a82a4cd4d3 +80ebc8f072cb591e2c6e2e5de285def811c27dcae6ee76ac1a41d545ba8808d8, 3b65499e4f2c4301c47bca2f4710b42bca5bf3f76388b5a45e123441b5cb5b8f, 2d254a80ffbddddcf742e202ae2058fe92212bf8a9cc630bf18954f7a0a7b54c +8159bd0ae02c9cb041383605016cdd1841ba22156c0a46df7f4597e5e91401bb, afcd8eb53c1b30ee93ea50aff06135a1bb9e5b38dc79c73730f6c0319620b8ea, adb2711503dfcbc332028ad2ab72e287016a72dc6ed643466d297ba71edbc183 +47c0e32ba68b1d7100c52505738fbc3d6c7c66f496902aae2427f583c5e04062, cad6c50d1c38a192c5d9e3f5a0858165514e4f8c214e45e792fc560c92650131, fc9282b3aacd1ad0d7a6de5ed459006308b7a1c2911e74eca48893a13b51b589 +4b01ec0452998564fc1cd7f48ae09e9a24c87012c0724bb96ab5129afda7c929, 593e2b53604d9ad6193ecde26fbf81463df5df09a9ee7cd06b75414c97d97061, 12fbe71c09f60dafd9e29a1c78a09aabe7ecff7e6405de2e63dc0a32e9b707f6 +b6e5257179bbbac8bc4a5d067423861ebca1e69314be5b2b8136c84bd37b5ac6, e609de6e9a0597ebd48f150f53f895ff9a2c032086a2934e21ba9d15d66bf9ea, 6dbddf5bed01319f77c879fbfcf58bffbbc9354c6b2b50f1ad8e49cbd6bc91f8 +e2f11e807ee1df48706f9c746a19bf45d499377423ac9ea6d6047613c94bdd23, 9f0f970782dc87ad218730a4155c4e32720b0b757cfc0d2b6316a68dfd9e9bf6, 60c711c10a8929107bbb0a86f409a2d01d71f45b262666e5d3f517e89725ceeb +62eed40ef81368fb9706a813ea60c3e8abc751d96c255d1149c1f76207a2ec2e, 9316ffd3669bf42b75122254430c45b728c9b2e918da5d3e087b7fd4932d92a8, dd2de48a7550c62fc59027f156b1555597b29da60d1d707adceb31b22f526788 +0b4f401cd4adf4195466cf4a03b223fa4c6c5fd5055200a179b4ba0cb4723b9f, 43ab54d8c5a9879c769b0a049cb741595be1e462128257a302f907fe91c74461, 47339008af7a990342c939514b880844df93cdaea521330b2a21fd72a1d9abde +af10bb84310146adfafd888c1778cad1a9dbc96153fbefcf1fe6cd2cf5e461bc, e777348d4ad816787cb576c76ea931a56bca30ba553e58385661e2ad67acde7d, 36981badc6ac02e4385b820d808c2b860244ceb2bac1121771c8d123f023a9a6 +6c41e8f56cdd00a0495e93444dcf93fc69535114a44d9e4b026cbd8ecb08e52a, fed9ec22b5f417feaefd830fd6c334a5bd35a2f19ac8a26f9928c9e9c780e0a6, c674d66e3f1080f018d4fa0344c404cb2b9168b1951c62ec2edf8301fb4f71c4 +25a9c035146da8b3954fe3e4a065d437eda47aae3a515dd36a9914798500dad5, ee3e73beea28e372ed13c83dd4358a516d9e8b104b532d2660a23c934cd00829, fa937a718b34edd63d09cf7a927917cb8259d2fbf32ee922cc94a58933782ab3 +2602c7426a80b72b27b95c106dc8075b7e95a24793a7bcd4d728e662e034d489, a1957c226f67e458d043df8b9e18ae3c3a0482c6765c5ce7d3080f44c4f14b45, 6c114b8b6e25ffb34732d47f78c6488b5fd44205404fc8309bb1eebf0dde562d +276f29618bf141ded8b1ac4a1620a00f86ac0034f6c01b6c123813cc3143582c, 611350bec964aacd5a99fb5cbfe5c6aed904e5674ab17b12ec6222317d7287a4, a621021ccade9c4acccf473c390f69ade59f62fce9f3c0eff4180b1f2df5233e +35839e0216c1afb83377642c425f05e5eca30c4ddc26a1826240d5f53a3de3c9, b938cde395ce4db47158d687f78812129c68d1ec15d364d6ccb7e72fd084ec68, 0f1e559dd0245a6f8260fbfe275116471c4ada875435e5faed57e403ac8d8e16 +1aed9c13420ff5feeff438a5047704bb7e742a54fa60ba15614025d6ccf8efc6, 25e6a1fd14e4e4f9fd14e2de0510300e0fe2afae516ea5998ce859b48ea81324, ba9880062f566abe8febc61a257dfc74aecf228629e1084b46583db43cc6ef72 +801e645c7562fafa48d92d756fac59174d8f155fe9791f633a7df78449db9bf8, b5360fa37cd80b48608a0b58e25a389ff0855979497acae0a85ad0ecaab18336, 630beb8afc4bb3eb741332352c05cf01b0ed31a89c1ad7c1eb28f326695356cf +d3db1d83f3906421bb4fe950694a9144526a00d55c44a0af2dcbe668fdfdb57f, 13ef0d12fd31eec4cbeaa929a8b71fbd18da33b4e11c7d9cf28900f8fdfca1ef, 6a53e08906f9c1f169ec357e95fd4f239bb031e6276caa4347c306b86d84c779 +57488552401afc78c0ac1b048f740f4c122bf634f621b181a1ad6805669e4bf2, 8d228f797a67e68d6ad1654a85aa8fdc56ec1576c91e825f9e93e7a68cae4496, 2f106a87032409e652af9d2c16fdbf6292f66c61a61b598fea159d151df4cee2 +5410c73aa43ecdcc630e34722999ad5b474fe9f5a5c1de96180e9abc9e76c4e4, 46bec0ae397b683bac2e27cc5a191b9ba2f114e88c3d93134006110e4844121d, fffe9eba823edf55a7430e49ae8c68f5dd5eaf8a4f27b4bc067c9572be9e8e8d +a690a103714c9bfe69e0c40a7d42202bbf8bb5552970763a1dc2b3e0c7f604b9, f723fddf7ae23bdaf6a6111e6876568d3c749ec655f5671f4293b6de2d6b16ea, bd61e8a61e3122f9b1c84e1f2c24f1df3ca3ddb33c165f45910117876c4bf7c7 +b12df8dce59a09578d89f2e0f7add5dc03cabd17028bd4b18d806e00496d04fd, b5f6e5a816c30f307fe3d2fc819a5f37cbd19225a2e9192aef571d7380b14775, a99c4b95db06d69bc84307bfed0ab3b56b01eafd4ac98f6c2814699109820d11 +938ba4a622f87a67359ae215340be309c5b34d2209b53debb6f78b30acc2a7ba, b441f65fb5b58e615526e5ce5ba0411913b146e73dbb79776d7bfef7b1f55e09, 2e4e9184507090764f8c89e43efd8fb32dd52654699fd80b39c7b489a96825f1 +65897a29abe9b3ef9c0b67e92b639558b6ff56638313261bd32ef0ae32be5402, 757df81604644c15f4db9be2e280c94624ecc14ced514a95d2f32ff1cb1f5bc1, 157df3d056868f295f8872a35662eb7d6b4b4c8ce00c52e14fe23fff10e4a275 +53f665f9a5ee3eedd195affbe303454ae65ef7a088708a20c632d32fcd08d4da, ae2f9f855288cbe742c73443d446db85c7e162b0152ed2a520b179c006650efe, 72eb3685aa6610bf2af341a54662f312d3a9681a477103fafa22aec76cbdec47 +ff44b8242b818de51f991217da3c4c19f4b1c015dd861c15d05ae5b74565cb4c, fc28f8aec401dd7cf760ffbe6ed09e34b08febc886ea1307bd9c2174abad6332, 18f0e35b1a4fa56322647a5e0cb157638d6e4c833537315d46e4df7c26b92829 +c3606f11004f6feafdb739cbcc6647c8ab628a578b1be5767e541ef70363c324, 95b90bfe96f3a948ef2b4ee05f4df02181c2718670a3da96ad24385a63610a49, 088cd3f4165477aada13e45497597f165e0fefd2fa742bf1bd490ce00e893347 +e822051013fde3ac42d9817744483d04af70534d13c9b5bc0d71db36f3b4da02, 5f22bb3a984a7d550f4b308ba0e3f9e5d5d3d13ffac250564ad67087568965b8, 3271eb98004fd6f5d54af1bfbdde4e7601709772fd2c7ce41cc3ab2d08433459 +81988c5980a83413f0786fdea093e2d874ccd9e3c65b34efcb3e5e7b531b0997, 4382d71941e38bf76c0b204273486d8b92eaf095dbf8d29503741d894c4c4838, 8b2d2fd2e0a883bea1f6a352d4fedbf6cee63b3e4b28cb9c6dafefe17dbb0c10 +3a3a9c56c4fff9214f0798748edf4de1dda6d5393c256ff039e60b4bad3652c7, 813409c86434813dbd79f2d2957bc82c2f1775e3d3c78550f9662096ce3ad79f, a3e52f8c0fb0021c1de9e922d9239b451f7a1996cd6824e450218e0606ed3201 +5f3a0fd4573c1f0dccafb7d550520323ffb790a067ab48b2991848f1adaa17e8, 0bc24c53de33a3fe74591d885ddaec309daed16f7d8fa9ed819c175f88a06444, 53335086e38a90e2000e2a6349f09a6e6c64fe86dc2ed1e864c8cf50fc9e3e91 +85c121006d1eb5871286a9feb5368874335a2664fe24ffc300b55654e89e4627, a1dcaf54161a2c56b85c25aece8a56b866bfacf2906be1d2a55e4deb224fc7f4, 19dba2f65508fc4c8c9999b91d14a18e50c47fd867ddda0d8940025b0e2c20e8 +01e545a9e7df6abb6a3fcefec9f31d357fa82958de1c3306bba0a97b355b89d6, 1c8d250584e08f61976893821906cdfe23948ccc24f03319643f82025141b363, 08af1b7eed7cfef6e5180367677295cb393684def6305698cf21c8f60ad56a60 +89150c673568155693c1d6fba1becb71f02877dfd793a76bac295411c2c0a11b, acd4bbe38033d64a78893b6761bea9d77c7da033d6554486bc0974c46e90d0c7, e5d3dcf3dba349458e10d940483d35f0eae581313f89fdd065d3f7d00e99b7a8 +0033a1d4bf31867d1f2f22d23c873a4d7f465b2d06abda98109c3d824ffa90e4, c7c49f3ee2896184c14b90f78d1c105e56716c1b4f095ebf9aef1dbdb1a65999, a32e614e268adfa107c464e2e45854c11fda0cb0d49ae79862c9588d2fa44465 +22d86b31ee754c8371c57546f2a8e5af25fb269c89d5ed264603858366f959cf, 5c694b3ba0c84d46dc86f7468ef92e38d48092692284cf7b2181774217bf81f0, 537484ce83164fd942b755d84ce006d68a6c745bba2cf5c860beb8857cd908d9 +d15648691f0082bf7f2fa9b46fbd4bae8003f25069f895830d3e31002d9e0d0f, 57957fab5adc48843ca1ab337e92821397fc37dcecaeb783a536ca547418c07d, 075c4694fd39b7b3255c15ced1ef93d7c141813c644ec1c77f818071e1e5ecdd +384c70b7623433fb8b0532171446d6289c4fc76fa3300949a29f7daa2ade69bd, b1b49dc825e102e4bf5c4841e9d88271f1fa5f4c2a7b90155364057c7cf0687e, 775fcf9c57e16a3e903b19f37658d0d72e661e188d1f20437004205f74fcf670 +6f04496fdc89079ef20e544e73c79c0fa1f3ab06e9dc7dca59ae9b56bdab7f68, 78aa278b1755873d73345f1a4a6c94d60d4e0be7d1b473bcbd409d79b0e119e0, 61c2669638fd996f73989b30b1300c353f572648eeb2cd56f0fcd0aad4eefd51 +9f22f8f64464121d39d1ef106c5b467f7780bf81a84c53862f3e1c85da060d13, 3c5ca16916247dc638aa97f375eeeceeaa0566f2f2a29a67b2dbbaf5b4ef2eb0, 972db6218ada7f3599bcc3e3e0ffdab33a52d4b2894c77674c7dfbb712ef9762 +79c6f3482bab0f986b34c32c5c1165b4a037bf879e2a24ad3356e312894567cf, ed4b7d0fd0df05221065a642dd52d229ee7520c01771cd935e3ae99a4c2018f7, d006a2963e46d78315530801ab24b94477cf3947016150e30e3168eaad7eb856 +f0fdb60f27c5daf6ecbc1fcc29519119607fb768fb1893a684afcac17cdffd0b, 01ccb0ceacac5071ab06099bfac45fab8f2408d74d3d57a81185a4eda6ce8008, ffefde43352da4e6d1b85882afb50aa285e42b92939483923ec402bc4d485e6b +a688141f97ddecfe1fec93ce6dba05a118285c1d621619550d014b4de5044180, a3578a9fa204d9a08624a9b5e00af88a3204fbb63f3cfc64ef1097555463cf4d, 700d49974426955080d30cc5fe9d858b321fb85d742a90e1b51d1e8c5f22495a +141f67d566551797bcbcc4db14eadeb93e055072215da28ba12aa7e01e1b816e, 2457ce686634f37e947ac4731b46eb5756dbe2ad3c2a95ef2b709aae8c6ca2c8, 6db2bba3b5edfd81fa5caeb3e37b140ea8443daef20c66c9ca106bca3a362f9f +3b2731a00c9f8f5e45c1a55598a50da57ceffdd1de4b2fe84439fb819546859b, 6696ed771246df57a81ad93891e084cf5886fda3aed9d8fa97e87f5f022eddcf, cdd89cb1f07eedd2a1f0fce951648eaf054fdd9eb4ada1928b0ddcb0e3c0012e +7ec1a4aded428ba5553cea5abb68c51402f543a743d96b3817b274d604dae417, b7036b9464e1d68d61bfddae9493f419cb309f257da9a07728a639f1150a1eb8, 15a4a2f71cb584bbc95b785a2e4efa1bd104fe731d840886063799e40a1fa9c2 +812a1f2225ec3e060b7170a545fe926c072810fec8b3d37ccd36c76ba97a9620, 99cd89e8bdd19846da4d0e2e17c0adcadd6893b311605e09f3ffb92bd4ae5373, 5213562a965b1c3672aabd8ba642168ea3b31379413544579a9c77fd5a10b996 +04b91ffa150353087c33e51db967dcd8c9dad1ef6e39a03c3853b6843d9e7c1b, 164b7551ca68ec0e9d1a0d18a62264c2a77df66e5add21c75ecccd46dbf6716e, 87bf5e773ac3627185f3b6bf9f3bec2d2204a35f5fb3c57f4158b027c02f668a +c1f1e7eaeaa4ce46ce56e28bba6b4c500543a482eacfc6029a6d75018a493875, b913b642ca4e8989712398be58b90f9b4b450feaf11ba42ae8fde88b6bf6d81a, 2f86525660bc8a4981e7ab7e9d70c1e9e97a59d6ab09e62a48fa715d50713814 +2e0266a105bd9c4a4a21de81d2a780b90221f900ed0a6793b029cceecde5a96d, 1983882728801d9e34bf7a3b5a0b8c26641417ae20715b2f370a521e669c0783, bf7cff995468bc5ddc734673ba8222d06fee2e4b20e0c7d4f970644b8d67b097 +a1a22d00dbcfe8963dfe2d74c7a0521357cf339454f77540cfa80b39b4a12bc5, 39b74cad3782af7c74b31fb8409ca643122946d38bed96b123c050ffd6bc11a5, c97025f70f009cd3a3bbbfb639a19a5256a6021f02f98812253c42a0279cf45b +3310d531f1d354d1865ef43e3774f0559c13034ce56d8ca4a1bc3eb82bb64507, 1897e27f1340034d65ffeb609848d4cc48ca7f9642f9c08199594307359faa3c, 679535057ef092abfb6b1d1c58695fb63c521bfd4991197d2662ec199cf2c5ac +dd236414ef46b2dd599030dae78e5d720ac802870b567b07872cb4234e6879c8, 4263e277deefef6bc4f0bb01e9c07fe90662ed6a88a7826f28f6901a41ad6b8a, 229567a4e2e07ace7c1f6eec066a0115c787cb58f6cf1c62bc4db15730da67a1 +0bc36fc26a2d1a2a72d4c6e34769ce8fae9ee058ddb53fb3ab45648798845a70, 06169426550a2397cfc5e0c365c52689dd8ee04073063c1d7dcf93fde665874c, ab251b643a13478b461f47a6a94c81401c1585f9c1537a1083ccdca35ed2065d +9850ec88dda554e4c8f45993b7625b16323cb52864baf445d67abc70feae8e05, 9b890275142b66230d9ea50bc18c4eec1ca93fa31a9f3ab82a09df13cfbd555d, 6925edd3d7fc1b3091017a91ccf1d6d14e6475a08aea5d6d04c401918861ca04 +e0fdcc3c53ee1b130cc275890178e09125d3a7e246b8f1a51d023c8734e0da2d, 12db061a896635e8d155067a0c24e7389796b7c9d09cb4dd460ea81fa575cae4, cfb3356252fe2c0594363984f9b267e4ff0d1082bd4d5abfdc48c2768d681e37 +342c81eeff7e84f439844934a336633d0a13daffe2d742b312dfb7e68a663e3f, a8a16c5b7cf83fa370655820b53f7659d4eb6c66da080a61162e09b43993c614, c38bebb793d39f555e97c8c78fcb711cebc7b504b96aa2ed56128011a4d8a0ba +5d87d2f843cc093f60e2184749b317f84d6e7a8d2ccc082a61fabac5d0b6f73c, e9da5a5876d04c0de62bf868b10c1a50a3c933f4bafb9fe668905907966ee1e3, 5cfaeb3938967055f1b07c8f66f8f3d021f485fcef12d68a73c9d7b06d595e59 +7ae501e7bf2fecc1a6ffcba56cd05d4bbb0aa39fce5d2258798216f35be54cc3, a13678813238e0e387ccb37c27bb3c67a62cd3201c6d7e57e6b4bd462933ba57, 865e6b2c1e83c2f452b5940833709b71db12dd86695654fd65ce3a2a98bc96fb +6beb58cf2245d3b8da6419b6e555d713b94081d14e66ca62fd3829186dc7b54b, 95b21bd4ce58a6c26a4f904b274b55d46db0c7f90fbb5b5dfd4994c0b013b628, a26559a0544892d437c99e3dbf33d2acf3b97859b14e63f74b8af30b63b2ff71 +b6a12dd060d170dbe65d637027758886c98a730cc94424a6dd31d264f11da9af, ca04d85ac18ae93bf03152cca95032db62e1649898fdffbe33cfdc2852c1db5c, 17186b1b5a6ce1fedf29cdee5e1b9e660171a26c77749a97432a929d426ca670 +2df71e51b1d34608a4b525c5dd713099a0a0cc1aa43e78a2ce558c30133cacbb, ffd5a657684d5889da544e92d7a928adc876a8ac17bff1bd42ad073c3afeb715, a9065a635aa2b658fc75652d5a6fac2d256b96c477a9c612a169f68f7157e93a +667bcd645267073cbded4684c97df027a4e2ef974dfff4f8279a537a83fd382a, 0a095409a7837ce88da00a699ac3dd927dc2cc9e0f8cc36bb2cdbada0c22f3e7, 61611f9f3d26706dee1e9ffa0e96abb9ced62956c6ce58e0b72b8905cdf4b338 +2d97dbfed1b0566a3b8c978541e708103ccb9c05aaf0b9d5f4cb5843aafd55a6, c4f074b1ccfd72e495ee966d064779d8aef8204aa74913ab0e84f115c50f9e99, 8430bbc65ede711053c7c9d3cc346009369a01718054de2e8f1e043fa5bedfc5 +8483d8f30c64f2053bc0e27c05ebf517e2cd1bf7eeb4a72e96f738ef5b01ed62, 5e72904f7f68886a0853eb3721d6d90144d8888f923c77d5b7270eb611802759, 72676db61957f202835830cfb539fa5d48fcbd550f0aab92e66346a8d939f16c +8c8d24477e95b5a9932c7e2f190f4c6005e74a4854b294900ec824ac8b3fe51a, b45ad080c55d2e25d16620316b502710c27fa566d32d5a3ea2489d7b0a266f75, 396f1c0c248b0893e63574da3c3517947706c4a5d31df5cd1ed8590a31422a90 +6ddc43dbed0791192c3404d606bf6b98de107819ae082cfde725d2e7a1ab8210, de2db4a849c5ac0517d2c9a2db40537cd2f0186232083adfc489f8beec05f578, 5fd4dca905dd9ad5457f3bfda3a529b26c211f13f21206dc1fc1775a4aad8bd1 +45244bfff6eec268a03200ca48ef53017b7b695079741746948d6065e84fab62, db87b3847c87781eed1029f568b64e109ab4b0d661f28705d3ed23fdb4995d36, 15530ade117c0b6a583ffd2fa14a7f26cac1bafcc25ebaf35a71377a954ec97e +c1aca78b540244a1a71f90dd70e7f033ad1a9466280eafd32c3a17e8139b6510, 46e272077efa1b9f0d6775efbead432e51bd41ebb9c5d044f4fe55d22a4fdab5, 5cc7a5bd17ea082a24e9149b2cdf0be024e43dba972118d749dc71b59d58f7a4 +9a884f9abcc1cb1cff302a903afc92aa96885e0244b1e4798d136d1eeefb2b93, 426a12dd21a90f5fa3a857ca19abe8134c466292d7d944e7b6e6e90f0098f80c, 4743092421babe322f8ba52963f68389bbc5de4400462456c8000831c61df6ba +31298813340f565684b7623be3878f101aeed3c7191d190c71444138c5754d98, cc9cabf7a23a5409e2430cd30db171c980d6317e8e917b32edc998986b3317fa, a62f0280201dd584a9989edece8a333f9fc7d51a63eade9499ccd46f37242319 +54026f489756c050adb92434594b2db31467d68758b4ed1eff83857685750601, 385f0481a26bd3e23831c9b89bcd28a8e30eb94f90f8e6233cdb1db7c804206f, 91d001cdf4fa78079872690acf557560742f643d0ef20a9feabed4a2f6d31199 +5c4e7ae2da23ed358754d7906de527f04275ea4afd99e9ee580235ad2a338e6b, 66b39cb67b34996799cf0baf98e4874a409d4dabe475fb6dcf22c4e3ad43758c, 4da8e7ef6e99ad6e1882fb9f11f6a4a5edbf0f06981a8c752176d974c345eaa9 +a946fa860d6ef960758bf8d7a84325b9e37bcee7c3dc3c97aed8daae53f8ba7d, 26b4d2430bd657147b3c392697596ce20814210a3f17cb00785ecc91b2403116, 6614036329b9bb0a3f4dea190ef2cf90ac34bdfd9ded412ba29b91c91bbdd90e +d4368ea8f2b407c5d028f5f9a3ad5f5b38aa30e2fd4aa479f8c61c0b806c19e9, dad2691b2e70fb6b941256f4f4cd4267e8e9e1a569c7ed2af6d2289ed4d25054, b771c6a4b1210f655833dc176a6b9a8751beb75b7bad146bba70446282cc9161 +d4c18bd7700b5b29a6a7645c5cdcd8f7db14acdf3f1808730f8c700e2c65748d, 4c59eb2a9f01d7d54a5092bb5c76e27ca9cf79d44e6ebb92aab9a0f2de82ed0a, c38dc673cce24513a785123cb9ed15aec2680c7b4f02aa3f169b655ca09b8236 +0d121b82880e332a8a1ece9bc5a4569cc9ca10586b2383a86a6226d29a2a668e, 1f85cebd59218787707ba00591a3190b41a6949cb51837811daa0b7945dc4b5b, c4e5cd8e2aa7544e0eb53ebfbfbcba65d26016c6270fb2e29ad064c6b2529579 +ef531ffb5addaaf7abbfadb92591aa96fb4d9ed4e25a19e8f12bed26c27b4296, 6e67909a4a87ac486087adea2408c5848f44c9ab10553fec632f49aabd3f61db, bd31ca0366f1a3e2d6248ed5ffb99b41cc2e8304ac0afdf256e76e7788479913 +dd215bb4873dbb510acb973aa3787e100e42995616280cedfeb59fee7e4376f7, efd81a121f42a4040fc0053322a3daad2dbd41a9090c4432943715c352f712fe, d6da98b623a73383eb63024ea6ae755d93321f72e369efa85a7ef43675244564 +57442082bfb5092b223ce7a23160102f620d58e14be7097a3aeb6982e2850a7c, b4878e6b082c03c89d7404d4ab25779aeb25a6e2de946a7cc659430915214f37, 3891a9abbbd6093d9e6c7506691b532754e9beb963c9438e94ac4539c70c0240 +f1c764628048f6a64024aea8c54d402b4b18903bb7fb4d01bb9c5e122dcaf1e3, fcb07789c557a58c0dd5811060bed7fb38b8fe740f9ada8c3022843978822bf1, b060644e456f17bc028e12dfef777cfa399f8b7c8da506b75b18277b4881f735 +55e639d1c147399c7574ba12b19cb5819300fad39efef2e87074b216fdfa23d2, 04b1f2962156ed7e0b2a5463bd8684ab4103e82e09bae6ecc00419e2d7f2928c, 765cfb0d4561032596e817ca389c46cf70ef15f4094351ad4c4451ec8e4be072 +33cf79a8b708249dad152456f0a06a92d382521aa4b62eedf4d85219135da5c0, fdda516c77d28e4078469e61d78caebe6672a88cd7f471a98d8c41f6892dd38f, 9a41baca343fa96c9621b53ee230d9f732a13acdca1cf7e531a3ff6835a4c44a +fc5f04be235703a5c9a39e78bfb8d7b4309767b856d0d039f11739773cec0fb2, 3ac49d5f4580a8e37825ea6ef888945d919fd004ec38097979be8cfe1094900b, becea8cc7ac76ae2a756a601494fb609b89ecc98ffd492bc8818ec1f240ca1f7 +2d956625c3796f0d7458b526bb593d8afc3996e260d58655c2e70d2252cb901d, 3b5314f1868492e323e821b6b1d6ef229dd3dac52b041aa63eea66e04afd62cc, 314a375136f5740c3eebe1a53bf1d57a070eb5fd3507d17f74198efe91a0183a +0c125054d202e1edc36c26d1b5c4865f0df90c5f638d4d45f80ba331d36368d2, 7df808b4ab19309996b70985f0c35fbec7ce8e4c0036e27a361a1d02b4dcb2c0, b663e0ae8634d8d54a92373989db5b92411e409206030d968cca46d97827e244 +a85eda3053917954451b7f8f98253e12925302bc51164b8ee06f7b905ce381ea, 0c1dbb68218741a3f52c44979918d2c2f0c4c92a652df3965afaa4f518aa8153, b231ce4293fe7a3cdabbc3fd43b11151d9db8d45826f741e982ce68ca2ae5103 +ac9d625686bee8ef88c1ee22c7e39c444309fd07d85dd251634fcd04dd8e80dd, 55ef45548f2eee4650d07cd91c7d390aa62ff612f821a2939e43696ee4aa375b, d5b18cf35253572b55f65b6ca82b2ed451fd9109610edd93848cba607adb1c48 +8f6e049dbbf330450108bffcddb6bb8268187617c9a6a6a2abc8b67121449792, 75d3e6432703337e0e6d07d643972537b5b26a83dbd17e4cac4f8fbea360d1ea, 4bb8435992cf3073d8608d494378bff3388b88a602257927761a2bb3bcdb3542 +418469b56d9f6ee431953195c2cd80ad3730f3e2710a3e765e2596ec0148da6c, df260fa1aa7973e051426a4418b155ac465dca126b614e1a8506fc804fd6e4eb, c1ee21771bab3c1b043c6c3276a6c7c030bd8da1ead75bffe6bae404a0a2811a +33b38fdf29242ce513f78c3c85f3cbcefc044a59d265d944b5d96d41c004f516, 287fd00efa8e748ed74db7bdba6f2b2989fe6f1569a0ac991e81023c87ddd9dc, 1a40bb5c8a308ca4db4219fe7df6382f9723c7ab6015658463db43a2ed1a304b +a3649e30f02acc2abc96a32565c57b21b9f4a97585b5c1f77a9d38e63be958ba, 9f994b91331a64b97d0213980def07b6c88e64ec25c46e75c643ce4b12778454, 2446288131a6164727ef584b5ad8f7842986aa24d6c8a597068d10029e72cbd7 +ca2006dd43ffea48b44ab225c2f8da28a7287a67f97f71184c910d90797c7a43, 4e52cc178937f21b5752d8b6f71e3b1d9054775dd1e71ecafa4ffc7c131f9a2e, 4a32fef538ad13ce6ee207797680b8094ec065dcacfced16d9784603f293273e +131b32e8e1257e29a84bb034ced3a77eb8d8d29cd903701a44f7c9eeaf2d6a98, 7fc5c006b1b015605b6934f46c8845b5fbe698d9725b065009cbdac6bf83e323, f37fd1afcd207a14dad719a03919c5a59e499cc8cce586055423553687c3deef +f713d7ecf0b7626715d360fa02eebcff2f0a6346052cae09c4828a91a523290a, 93e384aba88634b37150eb7a812765dfeccf3fef1bfb29815fa74fadcab3ab4d, c449ef6c82fc767b09bb01c4120cd286dc5b38b15e40973f20deb524d3dde08a +9c0911229e3571736ed7551127dca4aedb72ccd5df1fc49f2f165075174639d8, 9b34a7b316e06696b043488bd9167692b3009d7b4a97f7542f3513b2a096a1ee, ba2dbfc11282ce3136d50d3bcae5d34bf86071dc39ff9d45400d7bbb0be5e44b +1df2ec653bd09ef52bc92f631610b6a57cf1e2725e605eb6c411c43bcdaa8b02, bfa0543ec0cdaca500aa2facfcb08d6037f455fdfd5ef402ebbb7c70e6be1f48, 8b8f933f669c17790871070b913bc052fa3e281fe89f53f08e3040c0921d75d6 +72143f4a0861e3b78b67a2a63bdb66e83c021d4f2bdf44052c2378cfcde32901, 59b3e2d4da370003df98a5698a9ce2d0e08ac41c2ecee532d04800ae74e2a476, d27dc95fee1c562cefae10d98cc7f5845dbf8606d1fd9894197f9b486c3d1c36 +360315484bf9554360dff00977b09dc7fd50a31b530af59cb6fc093462479b4e, f3b1c5b0c24a1fc6546b0dbdc307c047f57aaa511b47300bcc0d9b093cddb0e7, 29740a28ae1c8b5f52a185209bd35e658e4cb91265a61220e0fe5e2028674722 +3ff10dfe5e2faf127d08fde1630a94402e4f4aa5a565e90659d6be36296eeed9, 053e65f0cb912541f93fdb001f1f689b2ab11cb0141d2b158428ccd9047660e0, ae21d0c538ceae8eda2ba2c27761faac927e81c8054f80b78561c667c6486d91 +fece9432b4e3a3f337161cd10480d388b5cf71e4d421a76ac1580a3636d5cb2e, e35ce2be55c572f393d5b0bd2c89378787b6be47084c15abafc78b27d858265d, f697992bd98a65824c5b18339b4dfcb2ad1906804f4c7a4f44e64f4497ad93d2 +d700401e768d861e0f6eb548e0e90afe5e834ccabc64a3f540ee851d67efc088, fda9dc282f25676dfaa68155bd07f5aba8d979b1640f5f8c02b6eaa14fd2ee5c, 35edbc6c6a23282069702176cfe56c794c28781a26fab34709c074d681f5140a +0aed23ace6af9df090ac60dcc057f2d31c97c1dad3bb7b04c0abc606f3934e2d, e7ddb2178cd136d118cbeb5bd85990c341f8e87d366022879cbbdca6a5505926, 073231a9eb996bf94dfce558e1fffdf8b1fdff07af3a1db1d410032d22f1f1ca +560aa8dc922a41dd8e3cc9b9648d1b9223a24768e2327d3219a6730e7d67c1ca, cfce7e7cfa669a8a441acd6dfb3d21019c922e26ac4f2619dbb61d054b41f683, 77f9f8e57c1ce1aebeac0a2fa5f4ec94b3f6d6928a319df0b1a8bc98c8c6b0cc +08f04d41f4998a9b8bb5566b89f0679f350f26bc760a2b6e27a092df9ec65a3e, 1464fdcbe03d722f5feb158e599f032a703d08d9c3970a4a8f559149d8faafd7, 2148e2e2c5a1c71792426e144563c93168c9037fc7115e39f49a54ed71f86fb0 +aaf627bcf7e876fce82f683b11243042e371e173d5da731281423a1505d68fee, d8e3bf9da4524e99e7b353ad25c8b1bc9cba56e6fa237d47b6bfccff947f36b2, 785bf4eb340914e11defb77db92dbfd0e84cf32eda7e286957a2084dd7c08780 +ea48eb49e538dc8083c97d64b285f3a3efec023a075c4f1db65e52bba565827a, c6dd3b4a508b1563236af2815ca3b0ec89b6066ab11b2b1dea0af99607c11aed, 5aa72ad1f8b3247b0219536a1e6ee455a04357f57fdc34bd2acf5cb738af221e +3925165b6a1943bad2a9c08eacaaea28e6e236fb229380eb85d8af417823b0c8, 8669eccac8c32fbb433ceabbf51074c6b99e3e281eb3e94c3bf737c4713c16e4, 8fa888d92f1580b56313ff398ad2d8032223abf9581e34f5ca73749d77a15e8a +d5e160383e8972d51542f3b7d1169f41d7da9ecf0df2c27fb6953110955161ce, f116c4dd5e390d7dc7a23a83a9cfde74d59000f6ef3073d8f5865fc02b500330, 473a56538b7812ee3d2e843fc005e54561b90e539baa4e7aaa1f994f06c0c8fa +82cd7877b12df26732c2affbaf1538e87136271ef74483402168db929e23ba3e, 4ae256afda78500182391ae29cf6f38b61b53425ca917896f32d06a66e1a67a1, 70e9596787e0ecf7d6abfa128b3a2e2e78346f9de3eb05a865c1f8bbe540a914 +81f452acd6b76b80aa12d163ce246fbe2ebcd9fe163d7df98a662dae368b18e0, 77089437c230cfd062c226638dfb94825f9dd68e08ee50c88c681b3090886453, 012edd4404bfb58a6f3c310aabcef7a7bb9d126c468ae16b554bc11515f04632 +db094c5b759215b51afac9fa307cd605e9af9f25879b0ba52f1858171ef4d1ef, 5011d70454acd4d687e697f827fb6ea7ac4f2db96e81fd0805f03d4b9f2eab37, 97e97cc0c70f1a263c80f93b887e24d5b3dd0d723b9de5a4cf3e105300ca5782 +bee8a36603d7f7149181ff4bc2dc4bbde4fbc4607cada61f8efbd3585ea74689, 94579e75bbc8e507f82949490abcc17d39eca32eb38ddd872f200c1196168b91, 50e34fdcc61d485f63991a0a8b92419283e8446188657a7d2cfe4de0b8537c8c +70f7d28e45fcfcd7e263b7cd6b045cb2cbfd9a8d8c079d09794e335b2fb540e4, 6e84d6ed18cef14466e35b062a7bbeec681e609e2aa1035dbdaed8340ca5940a, 4b611f9051608181f82ae414aaa7cb60a2bf7c58cd38b4759c8f94b7ef2ef716 +e1d5a1c77624deff9b45197d10c3563acefe12e5e319b288adff8a4abeab819c, 5acb0371f55f386e7743e5f27b3fa74fcfe13d783e50c5373023e83e53f12bc2, b7b1b6123706c73819c50ae68a3cfa7c50ca2187c768e9afad009ee2a76bcb67 +58e156ddd1f5e0a3a8bfdf95eabd23ae927746a2a1a51cdbe1c255a21ae408a8, 4d0e616bcf07b310bb82b737ed27a7c06e553470e21f2be6352b3c12a3bd25fc, 183e9cc1e03ee28777473897296856d5bd4cc87f345ee99a60564c9a158b5fd3 +83a805bb7206809158ab14d2474d7eeffa032984acec25b26b88b1236732f209, 5fa4545238bd3a4a4c415b208327686595a1ce01a240b7557f8282e22a184fd7, 8e5e47d338cd003e671289b70ef6b7e858b23997a89def9de120a705046970cc +872efdee7d9f8d5052ed33a1e2e6ca0b680da11f065023481147031f2c081bbf, 757c2332241740a88d65ad04dc9383265ad031d79fedb0a387a506847464b3c0, 8589e647820345b8beb185381acd86454225c55088a74919564f39c85fb5ce6e +c020dbf2977e28ab173e60a60d09ff178e97fb049fbe18411e09bbf6cc871d52, 4940e04d02db2244b5cfae40cc6d9e058b5ab9170538dd1dcddc7de8d2b9641b, 20ac5f9cdccdfe143b2fe25427716d73de663b8dee0f99cae363ddea06a8178a +3fd262906df6eadcdf8c1cd8393b316151bda1bdb833fde47c676f9d12d158dc, 95b9b714669885f5dd90a097f7d6781e2ad8635bcf1b09e04d16435ae2c8ccc1, 98adbc1b44d6059cf2be55a89e7678ce54f265cd0a91fdbe6d7dcdea12afdcb0 +b1397957757c860a6927b1be5dd1dbb23c217a8cee70e73294846cd05dd749de, 802d41a1c3c430ccb3fd7cabf2498c798e9fb7a41980fe86de0daae3edab5e9b, 1ae98f324acb13564070fc836f0ad34a2ab4f672b86cc51bc0c675cb8ea63daf +8ba70a2a2a64d59b00ed1cda103d1d2659bf8265cbf71e670a6a7e34461c1404, b3a48c31b42d0e6dfcc0e93f5e3540adf1ca1f306fab231887f6218251239bec, 356cf270681fe79b643e6a0cc2766b35125dffdce73955263ea16962f006aa2a +83751e83060d13f3d2b02000d54e9a030c32bda71bb7466c4a0230d4c282a89d, ff4ebd14245eebcab658b0ac70d7def1c644337d511e5a9e053a17654be1d1c7, e6dce624cbcdebf71b9dcf8019a9d1210e647fe9c80d3a8077fc841bf05e8feb +df34f657103b664ed723deaa860aafa60bd11161a0a7e5cdb895139fe1b0cdac, 71c5b7635cbd0a91f41279429cecf96918948ba616799c8da4e28f09af94e48f, ef10153f3fe3da49e663e89e0b9c977a1f3e196804fdaa728ab49135af827422 +c31ea0ec4789d0ed2c9fe235daa12ff7217ecb23cf2098481e54ad7d6ce078db, cbdf18612bc01604cff9be5573916f6ffd3940ff3ea657cb1ccbb6624194453f, 7d073ce50412ebb89ee6c4543e87c0c41e49b1f6a961306283d5ea3a4d7430b9 +73905efeb6d2dde38375cecf5f6edef97b7a75db4ee209d4b72a8b56f2165674, 9f7f932c4681b66acb72271efd1812e1b38b7af146186ae3f0b3a05346620f77, 2ae882a099a95ff05c127bd8f174868c3e38870505214d24717e9481ae300589 +a260e57ebe0be57d36cfbf288e29e876b3c77a4b768702be0e615937f9ee8654, 1e341cedb9daf5c549381f9e31e0d31e2bade0156c1a1885f1281b80b2093db7, 835a36fbc7bb1b2978e289bab0ffb9ca9a60271d44e7ae4243e441b61d4fb0c1 +8c9d04ab46e794f57c8f88a28a5c73c614030bf391e77bd4528f3d4b7134b143, af76f926576bafec1333c2dee3c134e21378b99fbb4ab147dd51dda99d75dfa3, 92d66a9d34a21e4d6925fdbab7f5296ab26f38b43638ea012b332a4486777014 +1a6d9008cf1661dc2470a649549a37403c6bff3087376199b7da1597473b5dab, d844dcb2a406cea8df3665799afe89630fe4461a92ab05034ec3e6a47de0aae5, 87926a2599d415909b6b64d9f5cf149c219cdec0d1e0bf07d8ccc9381cc7f8ed +bd92613fa4175a309f2231ee01756f99e0706886b581a249b890e48a2ae2d5b2, 5ab099c98294e74b7d54d27a56c0afdc79828a1c4f43a994d2448b585a90b35c, 71b4f39d125a499c65077ebff3180c7fdc876ca3be0eb97fd667ef29a98c8a91 +6946b0c2b8d82f53bb64d1dcda7cbf8b52e74e6ac9cfb023d2d98d74061d2fb7, b1cf0f62218bd1b9de84c71da916ca7bef9c0a0ea6960bbf71ff19c2136a1eba, 5f80b8c28b752743d8ee5f03b05c8c2386c98abc48ba7120da79f39ee865589a +0e6a854d7d7aa4e3e915fa9dc7b730a1997d27af57604eead3962850b7ea8b75, 4cdcc21f62f08e07fd757f14c907253e0bb367ed1f9b5e509ac8c378563781ec, f201dab7618fd8dfebd8bd0f7b081c1ac5b795b14a5dfd702836262c8b29abbf +0cdcaf42fa6665f0d5e89b2d458701437344f9c73abb1a26a4050f5efa6a5488, 9e448707e22e616bd2bb4ab8494ae24d51e75d682f2c932eba3a8cf4893c22b3, 778e5ad0580e39c3d3fb76de4ceaac8b9bc06c75d860a7538e8a20b3866ba58d +4252fb05f613ae00409cb372649522a59de53d3b7628ad90a7a8880cf10552f6, c445a40bce2814ecda8d5abbabf5f4528fbafb94b9ee78c1f4df7ea50a2f401e, 7bf9d3ca6bdcc1484814523a72e74e5a7ae13cf92eda4f2090562d72866716c0 +6ddb97d5dddfd77103f463fb375732324aa4f595d1f197ddf7e725a85e4bd96c, f1639bc721b8edfaa15153a59491455128b348c92b94917b4cb44070b7cdb6b4, 50f9f9026dcd44c6f2c981d9d51910f01dace5fc114d2999284c7c6aeeb9fa5b +3df11b4790dd6dc133e44880ee290822d68ac5dc2b9e085c5be4dc7edf8fdf7e, d8fe5a6db1d18e091d1a16ef2ff3b5ac99a9d2784904463345ebc6ffb2478473, 353c6171165bcbd49fba22c8c87f4d4f96aa77d5d204a2249e102fa27d28b087 +ab15ffb29cda5d5c1f347e4d703ad78747e530805f47defb7cbbab2715fc5cf8, eff5847ae9dd70702f10099022a39c91bbdabbd17ab4b28bd269feb9c79b14a4, d58cd39525f10a1a979c6730800dffa97c91ddd906115ee1b4021e0ccb13a7a8 +998229cf99c975d4001e0f118f8a60204ceba9a230321256d44a1dc5febc19f7, 563e251233ddfc708d4136376472cc4de4490e80e4bace95d36561242e53eccb, b29b289b3814fafd0a9b47f1907878135b15e698fea6cc3240f3f1300cb31161 +b65cf758c7ed7ae9dfd8b80a80d8c27968cf43f241345fb593c0259165ac53d5, 984368fa27bff95c0c937de6dceca5de7be26708239913b72a6786a6f5ff8308, 60eee2a07a170b60119d967823c75f25fce27c24952247abf06a0c49ab0b9240 +65a71c43ffb27df61e6b5dace9ec7850abc9436413ac7587c31441a5d796ac16, 7c43693cdede88d2db73a90edebae21904c545fe3c187e64a0ddf18631522536, 54e6393f3bf84261b43a5af4d0a6437cb5a1d412c08bff6fc079f2b9004f3065 +faa784f7f0c9a74951993d0087d72e377bdaddec9bd44a05554d1095e56b060c, d20dcbc660b1594a39081fda43c3995a2334d80349223d6f00b2dda738d94458, a10d61ae9b419e4ae4a7c2127982c70e9df18906fc68e2458077b6b528324b7a +5e5fa685496e4f314a35e93e20a342b00ab039cd9fb0dd44934bb4637112f0e5, f09bb0cec3ef6e3efc010858e07ee4afe604d51ed89fe95df26d24b2c0dc8415, eacd872becc14ec486cd96f4b181e6827b62b0d156e5edfe8d6b0859ac71ec2c +ae847ef11455cffede0b6211483af1f3ece97696d23fc7665d79e27dd51da4cf, b7a4f58988cfaea7786d43881b02cf337f4ff596d540a19ab2718ed3cdec4e5b, 35eb57deb1e2e7580d20df75f36d7ed4757dd84392dfa2df382671d19238ad3d +7000c118fca331eeba81e0a9edc22c1043c43f33b06a9462bf59a20a51f53236, 9a77c258843bb460b147928b448782042a815fb7f0cb9e8462e94756e37cf591, 4eb1e745abbcade3748f9acdcf2581405088c580d88b5bdd78fb651b7262a481 +68dca3a3c6075b45e9c57b0545734ab162f6fc7bac52114fe6362ff5c56fd646, 509dfe7077e31f6feec0924c13eb602a67e1a17a3901f0d7bcf2fbc1980e308a, ffc5c08dfc92addc3d33da399c8438d21e808e2df608a039a2d55c9ef61b8374 +200f55f7bcb041aae8310932d1a33a7dfc6b4723ad3ba8a013e67b93442b73a2, 57d439920e035550e55fb28d85152d5bfbbecb47ff2c3212c13956a651c2c331, 8579c52d9ef9a80ed17fc205d059942090f30abfa86fa781a9c3396217ba9830 +7cfce1c4ea644eca36196e673c3b1a1775835069312748746ce84fee9db4fe16, f8bf94c70ee3d2645115318b4c21f7d9a8b08ce8792b4830b6421625d5cff9bb, 7e5fe1be4755da669d6840725fbb9429b1e0a2aa550e095690e896ac0c43dc37 +2d5372d933547adb170e9f536be1adbe9fdd054403f55afc3b7faa60362478f1, ade03c4aa13316b446463bde69928e7158603d7788be132d73f15351fbd88588, 1af14779373da23e3e01f3d10c3824236db650c732a70f987d8c53f0fd4cbeac +9101be1260bc258fb9a021d2bd8d81254fb496ef245862b6c91ac0a84d8e0f05, aaa144e41c663c736008750f688ffec6826606201471ac5d964cb90a20ffea24, 786aa890ed2f4992ddbd854d97b4f191211750dfa075ddbe35fd607841308e34 +2132abb898a91fe531e6fc962fc8203c298794b75b64819ba13abfa2dfbd0711, 0667d6dee8490c3b09ceb943637c8d09fefa648b3c28669c8e351d9640590e2f, 15cefab481f444ff8c1cc878e326b15128cd0ccd3c028da5587d87c82931af0c +c7b483f8caf3ebc9764cffdb5e196df30f5068739505b0d9405d2d5c8ae3117d, 949d629f1902936d43238440d82863db758ec5329d1f258011edb1bde7d91c9a, 39869dbaad7b13269186cfbb97802b0c476d091bcf7d2b03aaf9dd7723dce7bf +a7c3ac5d8debe3bbd788c8f6b34def5e03089aa425b43a694228b28ecb88743f, a7f9ff191c5bf7a97a464fadb2cd278e9f09ec4865b1e920ec22c09ef1eb7877, 86cb050cd5ff87b3a03f1ca9af000ef7e47e024b86d21a20968e82ad5e551e36 +4071d517c6d31513ec056ec2b7ddf7d3b65fef043c0060df1515fd421d75253d, 76445583d7c7b15e4425c14e1fb948886bfc0ea797ef74f7c406b2b127c36cb8, 7a9f296b7a466714dcf841dff13e1af9e5bd82f4ce33b80744b578c4b8e3656b +9a12c2920c65fb50bc88d84204640d54aaf10ffe30947aada59ae6d028f55b06, ee9f5dcfcd82041ae67dd71f96f11dd76b34cefca2c7e2c48638cdb8a5aed359, dc0a1cf8de38b20c22d40f1b8832226f2e945188eb9993e47b2c1c7d0dbc1270 +069201ab8f985987b08a7e6b4aadbfdd4880b4dd83067e3a1ee185451b407b4e, 6475fd163eddd8aabddb207176debb02903312b23304699b4f9a37002a53eb54, 649a97398149fb2c14ec197e49a1e6daa9d8529390ae6936aca6b0ff100c3000 +091d0fe6e3154efb4faad9d440c965b78384e2bd8a6c8233281cb982028dbe6a, 155b5a83a57ec3fb8d139c08bf75b87e7fa3ae8d7921e20b3a105cec918553cc, 022f843780b2a359270012973d1e4e6b84d36ee4700fd570b3fdb578533ddfa0 +9898b2579d8984b0434de682bcae3b8511b12da6c511852f383d76f96ed44cd8, 08c63b6dde99e2ffd5b6679b2c15f0a3166acf640de256644cdc37d344cea0ee, 5f20055a4cea3cbe2f10756e0c007d4289535060a7d69dff524f88bfd7773fc5 +e4e17f4caaf225f05222456daf01ce66ac049232b5210ec1e8314610d8e9b457, cfa46e62f02336d97910ee09e3cbc729536436d3f069a4ff7d2ec9cb949e252c, 54303b7cd5b0e2d71232782dd5b32f2f58ab69da37614567288aec97552b69e8 +8657fe5eec1be1d00769e6c6c2adac46954eb12cb479c5e857fcc196e29f6dde, ff717451eb017add3cd1315fab34308d82e03335ef121b22e28f3c6114df885e, e2e0fa739628ef01b894dcc483b6897d3e6f4b38555063ced32e53f266d4060b +f843767cbc4d8b1bcb360d8e06ad460d2666f9efb9507a6cd35f12f327d0d2c6, f2ac6e0690c08cf3fd83874cc4c120dacfeef1d80d87c05bcccd65f83d9c1e20, 07a10c67b1370f35b5f215a97ff39270000a3fef8fcd4f787e3570b56e3327b2 +4bbd1a42c2414cb6d62b0315861feb0dfaaf14d322b73dd6c5480e79c3cc7a5b, 7ebdd84fac41977f3f942aa3f4b6838c183f39370ee7373a0d0c7102ac4b245c, dc80dfd2034cfaec86c1134a91cf02b219c93c03fdcb726c70243051935d4d0e +59d06b3cb62da14dfb94a366d299c6147b9c2ee0e265d7263fa11f0ba0a22431, e0dcedd474304ed9e3fc65bade532463032c4ab0bfd3e6f2bffbff96de0326e5, 2193732d7ffa6919a86b47e55a2f03528ade05fbcc6db46dd9608201a07976f4 +6eaabde432ddcf28b7f2e7c002048be68ac28fc8934fa0f4cb92526a73280f48, aef159fe054d5bcc47a167661c2eabd11c57be11f939e8f68b300eb96916e6fe, 9ab6f79b9b8f862139e506c0c63b74e9b81df1988d47f86e95f889425dadf81f +64466cb72af9e9fb28eddbf5cc76c16b3c37688b3fb9f509178c0f1b9f1804ce, 145f78a867904448d6288ed0177201f765af7132368689ba05139356e9d5b6dd, f1e35da99a6377ae62ef1a910b3b23aa496bf22eb296ecfeb8aa0d577cf70af4 +c30cab93c5e2298f643ba99a27cf6b118236d862d14b5bfdd31ceb140c28d22e, 83c3249e07b056ca097a3ed729ed4aaf24cdcf0c7401d7cd8c92cdca4dd9833a, 5b328cf9b16b3cb9f279eb3080e52c9a936715429c9814b14bd364706150f741 +9deed1fb3165916ba539a05863690de97a8be624df86857149ee7ed23d5d1f89, f50fed49fdc11170e9c001c1893d67188505666ad599d32b15aefb22a1ea18cf, ce6915ecdcd2c88d176086b2d639d5948c23732fb8ca2d87514ea1d7d264f9e4 +5ffa28965a6cae50ec920959b6e9cd4314715bdf9beaef187b8c3245e3c5817f, 90e22c5b91059c43f614f95c880f91fdabfdbb9b4506190afe785fa61430b481, 98337bd5e21026f7525ef478b2774d36785a1ba28818d7401abfe80a828d8b72 +3a1a4a8d4f9af68ce0eb210b4b6cd483674ce85c903d69109e485087ec337999, 643866818dd04aa086cc3164492ba1c148539806dd8efb64f9cd4f671c188b2d, e2cf7d090a864c9f9d4df6d08ff86b8d4d60544a4819458b2bb4100e3aa2306d +0aa66abc63cdc0387c8d79d11ae850a5b098b918ac3350797d349415cbf48f97, 603e535baae24e76ffac90e3fe47b974ac12d352f501000f44492e2abe479f84, ef157293c8c70164530a50a8dfa5def22a5f619995240dad1068856f2c236f21 +dda3b9d3a615dfbb27ac647934da4b8185fdbb837b8d7af71ce2d8777167d6ca, 079f062d54f25dd59fc5a6b1bf323288b6e0286ec3fd328fd1ac566e3a0ef65c, f24570ca025b8161be99dd88139938f3db16d3df5bc5bc6273aa32b9429a977c +6e01bdcb7c46f37013dfd031a28dfdc0425f3a062528a20724ad128360b6e5bd, 5ebd64527deaa0cd685aaf6ede57ae718abead95e929a99cd11168c254dfe75a, 3215df9c5809677ce119c47b8a4d7b39299e276aee5ed34fd200c7195a5e918a +0a3751c99a9a4d3a6db1702da200c3c56f8344ef78ec86e4a5a95d7f0afe4eb0, aaa8d0994ae0759776e9e9ac9c8698b1c0639b5a4a1376db404fb82271d65f5d, d0372e0af249bc2fe6b5c37f7c428d8b6ffc2a39b9e3ea41fef09149f6c99c81 +a251ef1369e8ef1861ddef3ec7c8de96f416aaf2a237d9ac7c620166c7c61407, 155c546ddaeb6d8f7721b0f66c45c2ba6e3f2a48b67ee09eb5df0b484c721256, 187f05c5729dba52e1327cdc62e567b775a8aca8c0913e985f683b677c642cc7 +6be4f29e26b69d6c9de74cf3d882cff1257871850ced2869c73e036687901cf7, f2e637ed4b785907a9105a8ff3d31d07b624f02a3f54e91ae0988548be8976bd, 98b7a5eb7314e7e6ade82202603007f73c83c400ec1c0f6064ed542170f00b8e +0cf9d3cc0f6c096a8800cec75873d3b3172ebb85fbf22c967aa4fb6aa4f45624, 675abd7eaca43cac4ba4ca00a22a9893d84fe12e132562150f1a42ca20c72045, 6f45d475321bf62fbf75d654e2c6b734b28550824af791ddd48b9ec570349338 +efbd252a2ea3ba67b0999d394a482b92392cbfa09a90adfee1b20d0b294fde05, 2a0f5a3a4bc06f930000623cd3759bb1e454a7e5821cba72f393bc2260596926, 3e026412ef123575941f97ef8c15103c7819c0680e85231797c0a24063f49c9d +41ccb492cf9fe5d41b030d85afc0f608d1e2549e5f32b425f7c1ea1eb61c5c8f, fe1f05f16c43feca92b9016e8cdde1d34c67cca00c720703dc4c14ebc49c2c2a, 6025d5210ad6007d572a61ff1166d76d7462cefea91255d55c241f7c44fd52f3 +cbb83584f97886992303773e348df743f80782ca551652c2ecdb4c7a5f732997, 617436701d4f31dec317dc10effa92cac94902e8633dc713228a789454e7dbf5, 7c200b3a4e29845e56ca4cba09c7889a3ac683d5ad9d0021d9d6a374b5fe767c +151a200a475648b11e09406dc60a96296b45f77e8d9894bd5fe032a58d2963b2, ba7bbc3c5d2eea07abe147c030882259d154ff648ca1fedeb94dd4ad31367241, 331b1db32329c0904f2e52ae38ffa7774a886464bf284c9d76e89d7a422affb0 +2ae6df36890febf2004c766e02e87f185d15cda75c12a3bdb4ceb4a5242eccf9, e53031b53034f16685d00b8f0be258d09b4bf16920b5273cf3d59765ece2f2f7, 8a616cfe82bc82576b8d77bb218a9093dc482fcf99be7cb04d3004c26d45e387 +d17ab0e550421586eaa1b549a86d696e6b6da2d9e13dc65b3a8b0a2fc6900e1c, 074811fac06b4066ad18603077d178afd5dbb567556d475bbfdf1b14c4b8e4fa, fa0c91026bfe017bb69aacf72a15a0417a395d2889813a096078cf3d21de6f7f +8620a2fa85662e22237f07c4a4a85a4bf334e4e5f61a602b26a9028eef7fe25e, 3b24f4f1f2ef1b884adaac879403bd5ebc919e89eb4f9814cdd4200b9a894e30, 4270a49b75d013c657faba4503dac62aab05887600e44eef8afee3fd4f306c07 +349cc07adfe04d112cfd9a0415a74a814ba66d6e684d95899ff7f03fadbb3e1c, 06bac3e3c06c8f27e7a4961004dc0eb00f355b73b83c93c7657072125c6b1cb4, 6f37b3ccac017fa5a1e93afa5b7fe37c97840505fe63df962c8f447a373deec0 +508e957417d19bc0b2efdb9e9e8cf5525a5dd609805b19cf573d37105bd5fa10, 693e9e6ab95b431718d512a9b93ece61a051fff79ada69e0d1037d11e15eca34, a07810812337c5256cf0084aeff8dcda3e9ba07c703406296ece71c869473db2 +a20ed7af866f5b0323122d2c162efe448c3061674faed167d5adbcf5807167b2, 79e7b77b1d8c7e67de08e4f774f5a4f7cdd7723cf511b355faf975fda3c48bc4, d80bdb05d850c50725ac8169f457485d838fccdea2cdcad4ee5f316cfe189768 +665ad182fef840c6ac55acf97d0b36879e9ec4354f693fd21765e9bde69aa3fa, cb5c3f1014a616b3910d94a2bc1c660fbb51b74325ceb2adc827e9b11687ca41, b3707a1795e8be2d4ecf3890d3e2ea6bae045ac18edc5f99cb981a1de30ea0ab +b106171a9ba01c4e52c4879e0440abe36b87bc3b2c7098d067d5149e95fcdc52, d5624f868bdf564ca7511cf63e8084ea5bf2da5f528886c732ac3a721dccbd5a, cbe5e99bd4f3c645d5964d389d49bb38333a46179be6653fad3a905dfc0a399b +a79b6f4444b3b067775fa3ce7fc91160b59904a0b46d98b1a990bab034f7ba44, c878c2cd691bc3d32a3dbd437399c35c9ce19c5358394710ce752c1361d30ed4, 393cdba823ee085919f07cb05aa01a60aa3d284101efb30e863cc290b0b70801 +fca86003809701cfd997dbcbd15b9a6d3b4483c2bfa2ae2e76648aa858e94b64, f3d6ba5c609d77edb6ec4b016580b2d037495b7a7ea206f166bb88d22a9527d3, a529dbc0590e7a7d928cbde59456255bf1505adb8cef40449a2b1c8d2a2fd11a +147d0fd0b8934f8b085bf9b803cfba170146713fc3836a19aa54e6d1291e663f, 0a1c5aceef0603d066c6bc8371c9a11c2ad84db5d58b38e36dddef944fbe7243, e40c850ec82b47e5eea1cba355fa82b65d47d391c82e9fa6302082ae53567029 +19736fb6cef2a1a39487061d89718377ef5fffc483ad3d1cad6687949409ee4b, 4f9e1122949a5cf692a2f4bb80974425ec07359e7eb19ee802725af6ec48ac11, c3bb4b1012b16c1a58b83a6dcf4112002ac3f102b7b4a189fe9168ecee002679 +6c7228cb7f4b6217c4b041d3ebc198c6d75e78bce69cf5650d736be98f60839e, 7d2405a9d1d331a452b77b8ec2d36e3de103bb4fb0f8126baa40d1b574b6cdec, 64eac758e8da2de7000fc28866e5b18d10d1506f45259f0bd5e061be83143129 +a4e89ed3a3496ab2553989f0144de66306a39e12344984d63b6cedf8a746b83c, 407664b7d43997d7a0b139f9ff02836a73ccf52329e951ba75a6d46aba57a5d0, 68b29d6a02cc9c061f4eb07a76ce2093951a90663375d0239880ed075d771c0e +3f5f40ffaf6136af7a4d99c53f7fe4b7d6e2f342960b65d7fb781dfaf4609905, f7bf70246bc8f0894c42aa65a122eb3b69566ce06d5e5a4cc27af3eb82e4890e, 0fbb971ad3277251112b301c13ba7bc00220c18d07e1673cc1049b09b0ec12ba +20e329fb2a007b13b68991a691834fe03b9566945c1a60ca453a0a123d0628f0, 1f7a22c4b45f690d8e11485644a49b6c82a67e32e92af0c6af163fd3e14a2aa9, 4fbbd89ab252700656256d3214f07e04255de23c85fb57e3643a2b88f9a975c6 +1305625a8ab12df2eb2468d80630bb0db4f0abca34dc452c2a3875e8a5584c7b, b807f5f2d40efb69e8803d57c8d3c60e85968edd3740dac873cac066f1231944, 9ab65083e8062fef9e912bb94aadcba6dde2f73c160251e41e452217e7363a3a +1f152d2d777805f728f876f85690e4b1d02cc764c9b808a6527e246365497610, 9cb23e7643cb8f6e28e5e285893cc9f1a68ef75117cd6d247cf7dbc7950dd265, 56172d1571b693399f8a79a638e6bb8106f04e7833715b5ad0b6b6c4cb9d059e +d02d2e09bad1f8ab4219a0a6971fb17be619681f0ace36526eb5a5c09f90d534, 1769d4bcf522ba8531f13f5a16d7eda24393066a2d9a34f63bbb01a0feb7cad2, f105401a4cea6464bf6b0fbca57c29317e998f12b90ddaedff827a0d8d79954f +4dfebf1d8d1cc9f33baf4535d5eb623529804688449d043e22c7328a88645894, 862e9d4df4835ce524de4449f2c5b587f1bc653334cfd876c2af942c07680a4a, 75257587d873ab4b08fd8d5b9dd52b509a4d34edd73d1742ae3c4c7613ed4032 +c0c76cd91861f18fba205b543d1a300a372064730679dd9ebb4576a14b9d75de, fbe8e5d57a40c5392099b278828e1f594919c8c6d9fd401f01c93572347a9680, 06134b9f03953a8e3749ef58c1294d93e2892a3561867b35f844ccecca53cf8a +a0b7ec515fba5adf96a6bc666b864b30b3549d6ec8e759ef831717257fbbe05d, 1d8de86c071f2b37b57027b082f11bb4bab5efe3f79ca558d9a98b3d1686ba26, 37b8a5004528429aae57cb0a4e68de8398d00b9c60110415faa29bba49722cb8 +7546800689066609cddacf607d855ee3fa48fcf3e555ca841a583b6a3e7536a3, f46ce8d660d35b7b9282b359c2eefc521cf818b120d2c2d70163dfbe2b9e9cdb, 933e12bff3473e3b5a76c54371f88c82851380318a4c3f7527a15e2eb0241cad +a4de543258a6485e2412678c1f54eca3b33c9b596f283d1768e61501320cc0b5, 58201e3ee015ab507a0bf6530d94913db838fd8695aac8d0b45b90c8ced052ea, 9634f56d59825ecdfe665bb91f1066edec13aad194ff8bc5c97707f7e5eec8e6 +2832d5d056c3251cc58585e1ec46fee83b7066422a7e48a00471462e21551d6a, 1b0f74bcdc25c92e0d10753d33f124f8c2cb0c52e09936702ef9536a8bd6a4fe, 3af3f48534e741fe77128a6c1eaef02fac0f2d97aecae4cee890f38e9f83b042 +f7f68849ede71cf678063f66f6e1ae38f1854f3084f4f4cc2846ae2e69450cf4, 201cf906865c23154017b987461a4600ce30b48f640d465ca676868e4f8db586, eb75fcafc15af498c1a7e6110fc1e60246f0737742fd37d8e2a0ffa954ac911c +48570d90ce59afe7590fb6ce1a28d8dffcd9432bb6ec3cd13d96a05c102d3268, 08309d4085c5a9e3efa5b8a238213b9bc173de61deec4922b9aaf9e3b5e8b52f, 750335c4c77e10cce1edb328aad6d427156f9ce891d459cf03ebc983549e60f0 +56c9c53dd5365d08624e42718e5834347b6ae6ad61f267a2da17f2909e8389b5, 5f6092f086457e6f51895a120c472cb159e25ee6be5b3daf04a68ed88e82688f, 1ce95bc347c64191ced6b4e59ae73f19069edd3d512bdcb42891e3a9fca7cb52 +cf5c5e18485c927dcdb928fab02504b425b066f442d39e9a31d12e2aa0adb39a, ab46f54ffc2ee8f82e6cd465bd48d7a3a58b660afec9a0be44a7c891ec613d4f, 07a7c88301a55efcdbc0e4122007d0a3ad466b235832fa62c28254448b978d25 +f95cd1f41b27d17bee8ddf1503ab29b44111e04e2ea6f5e28fc80133b23effed, e5da5d1c08a796a0d94ab838dcc1dcd8bb58a32e33c6c9a248fa68c496437c98, f1cd2fc8f434cef688212488f1142b1672dd680f859770cbf2f18b9cf232aa12 +8e5242c6d5869682e16a733dc5d46d9603b2e933c4819429c3a8a313fc63835b, 418798d1e1d1aced31830c818f088907bb918fb6a6faeb31bdcc16d0919446ee, ab5425bbf430ab987e7b4a245a2e79a020327cfadfbc3adcc3279cb9f7cf16c6 +7324ab063d6c3db9b79ca8f3cede21cf779a7525caaac3cfd122de33e2a53ec1, ceca8b0bce0579317dc3e61b308f78b51137b22e9ebce2b24fbfb288425b141a, a555fac95b96e94dc1b589f9a40368a960e95ba8780fd71b6af98131b92e9139 +69fb18860f208e77402b79981f1c9ecc2339f73214beb06a3e7e1d95b58d2c55, 11520f2f262818c0931ce835bff1bc1db40b08ec07ea536a16cf79f12a9490c0, 2515a753064788efa6edf3019961b6257055917912f11cac7bbacf3cb74effa3 +b4fb8ce3b698ef092144e266168a4873298bf49e43f9f1c896ab720c730a2f91, f4c9f3f764a5fe8ef0a7dfedd6763feed4af6c8265c33dd02062cb59f8e527e0, 6123820e051507abba4155d1ff8c6a531289961e58e74828a0025865fb8b3e64 +a81a3de5f01243f42463e807c7640f01dbfdb7249ee853b1120299f76e796ff8, 13ccf9b8d14989143d6db39f78db7f2241502b55db845364122c2cdef1c02c24, abd7e2f0ec93eb83a3ff315cdf73ef975c976357df6c17532cec90b23ddb955a +4fa5bf35337d77fb387f5ba80230ba3388a35fc9217764961a2e3ef1475e7f17, 0a90abf612976ff3e2d57ba66024d8bd9870a6d1c1cb3a32d7f8a0874ed952cd, fc730a27f018d85aaad887038fafeef9300437f147d1465cb74fe2b729cb4165 +611111f9230f1bff602760c2d8b97ba244817efc00b803fc3e44a0d5f1431eca, 0fc383507c6e9fb6399872cdd7bd47fe7aa15b784f5d8ab64d27988792d02fe7, c94f2ef299ede9440fbf526a80359d06b6bae337273ce10b30bc5729934f984f +af06b165653d9e73b8c21e15f7f17c784c111086b0c712375b891f651503bc20, 33bd3fd02d2147d810c517497335f2898750357853be3cb23577694e6b0e0127, db4dad3e40d74553602a36cb2d0a6288b901149ad982eedf4e7b9b75d6c19d48 +58d3eba71ee7f0313aad4286cbc68967d028e607be9f1e649fc6462ba94b1367, 9b4068458a4c70b018fe35af2ee1224297f4e7cfa1ba67d02c2531dae564f523, e57f7ffbcb5ed8638f25b7bf6d2002498718d4b37146695216104a5fa9398fce +89081fd95382c0dab6fd97f8b55c4751ec6b8b6933509b046ed511ae4f55e241, 20d605e541f551908322b7abb4fe9e93e730ed4aa5074b51562fa1a64d700868, 2e8c50b88914937dd99ece2bee0d9cb28dabb0dc287a88f9793dfd6ce0d0e16f +a49cd8fb9f54b998946229dd4522e2c4882ed9c9dbc8a6c34265b92128a62cd6, c2f20d42fbdb15348cd28693ff84c4dd724ce88e53791b0cf34a08f4570c4290, fc0eb2a5b418bf22d6fe9383bda5c50d00cf5683f15312852c14c39686ae99e4 +a843a18c135bbd5e357494024e74fe3f78b8acea215adf1ebfbc1394ebc315bf, fb830c864f7c0f44277231e8f90b9fad6b18a9ed93e6f22ccb47b994c871fcfb, 39273c2e51527db3373e7a9f1c12dcb481fe96b0f7e3aba0a25ac3e9bfa9bf6e +788513cda60fb4d4d79978770fa2a0e668fc2bdde5b31438caf7904005d9c4f9, e039f2dd844f4fa15bcb58434c638b7581b78417d8f194fa892159de37629671, 3012515bc6e96cbe12b39ee682ae820c5f0ef28000e0a6115eaf999398be9946 +f6b5caafc27c1480e815f83efae6d59036e43b5ed3461825b8ed1a97c9035c90, 1c018c9dc29540525fd6839fa612fdc653267788a1d348bc5fcaed67b4786337, f7e454a08bbba49131f2a0b92a186fa206ce34a89e7611e30ac71e985ecefc54 +b6c664bbfd57bbc11dc06b9f3386d0fba9d9365ed3b355566a84df527bb476fe, 9e00a499d535c74ec6f9b5518c10d89666eea7278fd136fbd8698ece3fa121ae, 5af73b067b082937d8da429bc79c8deff9516bc8ecbdddbbd86cc093c71fd483 +7a2f60dfce65bb8b5e1da528dab2f1d9afddf1a4eed7c42444c46c6d375333b1, b56379bd6ba7d12adcd5475cbd187c2ccb3d04a9812612f10bcc55b0255ef48e, 9d7f6aada8dd42d4a77c3cb72a4c3a154f52d0bf521984f95c7d7dc7ea5effd4 +020a3e31fbcbdb141d331f58b22de82caeeec8e45e10d24d9cee6abdf3cb4c97, d9f5d5d3688c461939f8543bda3f1d7f08d8633c837edda6fad460f786409fa4, 44bc0645c7f018fdd9637db6d6081ce0f05345c1224a77778b1269895cedb81c +deb8723fdce92cc7560ff7b86a9e8cfb9c4ad5e230bf6851867eb6dd20907aef, 470c325f0d8462c6ecb62027ab95d8167cc14f82c7a3c37a4bc955e53671f4e6, c31cc2dc1ae2faf13fcf42f243948e0296a7bcfd3028c67b768f09ec713d4c48 +effdf94de930978c17bafd1aa739af3a6a7f2a41bdb4d8bd1d7d1da617318803, f07d705f17ea414d4175f677b5418a2f8fa932d59056d8a6f3a1f1e0ab137fe9, 24dec37c04fae5490dfc019f27018d84bcd469e869c05e1d3200829acf96fd88 +390316e9f19808a5c455f0f12bc60da93761fbae37e7a888929a09692439d233, d58f6ee143efb1e63a5b5a57539ed4c72e6a3af509d28c01bfd731c2b4f74e4f, ae112962be47b89064e049fb0a1ad7938124b9aa8904934b45531cc218da6428 +85fc8c83b8ac38baee564bdcfb38bb6ec3e0374310eb1433bd4173af0cecf445, 94e122de3b8e3305a50723e9f99820c4b65040fcff75116e1a874fa4476c673d, 06b5b168e9ac774393119103235b1713942fba1267597c7f5d7988f5a726f1c8 +7360664603aed1ad9c65602983eb7b67f1590cc82ef7f25ff66274d222365c10, 2850a81dc5e02a6065944e06e309bdab0fb3fc9c36684eb2b188b91b77519f12, 991c3eadbe12f3b62417f233b88bc9c01ebd72ab813ddc46bd8d8fb4b14f20fe +92b694a02c91655d09b2009da20d815a117ffc5e961f60ff0979a7d546159bd3, 0bd57ad96104235083fc28cace48a1172e24bbaae22e1703ae850b156767459d, ff6a6857937ec0d28cf136de502205feecc5adc5292d97faaacf43267b57a498 +ff76be3da28265d978343c1dae12e5280590dc8bc1104d5ea426dbed3e9c9b79, 8c8273008011b2b8db1f3ed0fc4f6170796488063fae7c9a3799db11b76bb792, fa891cfd0ae9b18f70cd88524f60f9f21f67e99ba7fa82eb7b773f1d60af684a +3ca75a3b38fc38f828284018b766cfc550893804a7f02768e57f75b9108c6318, e4d8f5e4968860c267b46333130a6e715f06c7076b0645bcd8632143be6c966e, 9d4e67d85d2b4a9151e0c39ab09b7bab4b30def80ae842d76f4ddeea7a4e6f4c +aa3792702d9ea1b9bd856d7e219f8bd60e7f592a9f18272a1755045311cecac7, b298a23a308b66bc6c47081e54d16edc0df2a62b5414c8dbddc4f3bbe89996de, 2fa29c00fc00eee115ae40792c57f12cb0d67ba1817c4bc62e2de41ac09ae641 +66edc4b15c50309e1ff00e4149c7b866bd87bfb37955ef72154d6e0b11a98123, 125d810863a4fa29d4ee15dbb0de9cde10d1ddcc92bd428a160e62e348f2f0d2, 95ae367420d47ad63c0c8203a1f823610c4053e4aa5d98e0bf6bf3363b92b157 +96c44d7adb091c8964eee8b7a9d027f5aabeb870d65175f603a23b3a99bcdfad, bc0d26ee4446e2c476e3ae722d458b5bd7bd80f0c6269b0bebf357f41b9e2ee1, 12d0fa7baa38fc1fccc1fa803cd34a30d2915650f3964302d922f3328142c1ae +51c31c928fb63d8cc0356e037ae08058e5c78013caff5e4cb7e4b6bef669e1e6, 7b0190fdb37e5fbbaf448aee5cd57fdcafd4f948bfabc7aa77bbb6b4500e8e2b, 8cc11e1166a8807e462e435b6592d2ef711318d791ff04a02e874041ad889222 +4db1dc26a268bcb20965a09aa5c4e8785480cffebfdedf66769b278e4e22a692, e5d570c6c4544657bbc76e3ce6908628a110d00a20bccbc78cde0eb1b07ad4ce, 2ca991e45e050246410bbff55e0d5a114d299a4277ef76538e2a56a67a421e80 +3eb7c5d1e8c19e8160439c39c7fe85ae2790b98c29751894c06dda33d4a99736, 64117d02c50e1ef08652aeeb3401766c0e4e98214effdff6eceea3ccc4ad4f4e, 3c52582e25419fc856f3d610782f65d40812c3b0e032d18cfac388a2f93c32fb +c1f2e34040c58af0a2afe512eefd2344ef8d5340472c07c2248f9a93d9df5eed, 36fb5649129d2c95856382e6e79c32fb962035a2693b84a9204c972af0e6d471, 63e9067d8be99e43041abbc6892a34c71e40410801e9d8184173647e44210a24 +abe91214cdf98c6c091716c5234b6335b0b705291ca1dbe815de4362e86ac7b2, 2872d3ba5db72cc40e6d5301ebd18f6163987028f2a6adec51c1eebb36632813, 3e551c41429b64929ab5a4be7b4efa71f19f16e0f876300ff9aa24aa0efe15f5 +8573f710b185747626aa59f01f595849c816f16a7938f6d355149e20b6ed9bb3, 38ad06dd657ad5c3cc02f23178a6f9e945c9f9feff4c243693191170d95aea4b, e7eacbb5c2ac9f4a478f7ba94c440694c834bbc2711f7bbd3138ece7a9476c05 +6859690dbcc8faed26c4277f319649fca59139bc79deaaf08a1eedbabaecc09e, b1e94848bd60cbe662179b56b12a70ded1fcb700de35832cf060396366cac545, d3747f9c833081bddd127bda78a4566595e49a931d104f53e096b3df564d0270 +6b94598d0aa9f5bdbe420b2e70c924b88214c7bcb0eba8129d23732d486d5033, fa3ec579bc83c6e0ec586b0aa832e2658f58f214d5a79f39c51e73db2ba10dbe, 937750fa1403876c8fc285b7a69d16e4278d7688c8d6e2662892882d4b56644f +44b93a544a5faa697309fbad41a6e504336fc938f6b41e2ed9d8eb0e65617d3f, 05953dafaaa79abb5173ace90a589393fe5c31115f17f1149964d4d1ebbdaeeb, 4a15b0dc684dde8363dc3445cdc1802044dd3adc3476f544ba584ee548a69ea0 +5ed0fe551f0e63a22c57e24b87d1608d58b51b6b883faba8d57c0d6eb3bf2bcc, 67832e184ef95d184a49f72a026fc5eef5cd4b0d9f67c7ace51e3ab4caf342db, ac552bca1361ba9a6b2ec1d613b54b3fd6245ea3bdf53752d3def4c7d05bbf11 +fc6c408947870901ffdc04994342a51be8baad4717c24e7130f957126b4d3194, eec931e012e37518236012e7001f9b503ed3aa82c0af99c540de1cc3fa127102, 92ada1eb41a87ed9566006f715d10e35dc61c928863334872c04bec573d3260b +af83730fed2141036542390d74124765d7dcf8c4320f1e439f96c1622540c7c3, ab658dd4ebcf765a913793083de0960156e0e2ab876ba7cbc716d619f232473d, 4f2f54d7da8f3d160151d9d57d2806aee4a87855741a9d3dd56c0cd5c3feb843 +9e92ee5cda38cc8bcc8ec45b8ccbbd9348785a7d0b7de96552aab78e4387d2a9, 390b33b7313349c342d9446e9b33e8333011c5fcf80fb4b4db3122003b90c0fd, c706c6c47ab7d407bcb52219d5be9d0a60bdb7b74cbe63fb78275d385dad9989 +b2983032db9a48d43448b85a1009109377c6a62e69012e0985ea79af7b03357d, 03999281a51d106e484ff3ebc0a54b5e40eddd0347c4ce98ac49e26627775a74, d2985f1e64cef76aad360d600e8a808087c774bcd26ea15a89ba80ec03e6b00e +7f2c1c290c3d1d9278fdbc9455048eb45cbc3c68dae466db096e60ecce1f817b, 039cbdf935942e700ee5acb97b8c2c3d7cdffea610d0ccde886e10229270d884, 6acb9b35c1c5849529c548930fca6b1cdb28ba83ee847381607309c61ff93714 +d6e43186c0a2f76e08f6b1f4b0f6e32b5fd07f26beda8ec4cd43e3c1d3cc754c, a934e9b151cf89247414b14e0c33cf1753b701427a0c2f88398ac623e6f71602, 93183156167ef2993da1b3f8f020cd33650965d30d05648593d841b8ae1f5c0b +9bace85b82dc043a9238b47ae091456a7435ec62f9ab03e8127d74a0ce127849, 17c12597f778b881def7ac31c350a4a97657fc495f0caf3198f84b6cbf2ce565, 130b2d55976439f64ddac690bf93a465072bad00f11b6f110a56ff39dab93ee4 +5aadcfb02c1776a0442e5fb4ba4e71f0b1679aaef89426364b4421a7485cca8e, 6b428e5dc62a462e736c608a668cfe6160148456176075716502abc5bc5c47d4, 0541a9a2d1260e8f5eb5781cfc96e330f3c496980faf4d8a4b933d8a6e1fdf17 +54a04fce072d2de00a2c096c2ac3f9240a958b8a20b9f42c72e0a054494233df, 9d529150abbfad05a43c31a7f4cba6100b4fc7f996e6eb5cae20684e05b557af, 6f06cb5b3b2bb2eff37f4cca922b9002ab305ae3c4da9df734590accf9b8fd37 +ca3cf1f77cbb6aecf5c8960996cc069c3161a8c66f3085e1df233a6cdc341a7c, febceefa10a344254785d7ec3450a23bf48cf9d99c6fcefeb5a336c859444535, 88ca45323379921f9a21960b6c669e8747ab483bd8902100ea18526f138de5c4 +02692909c9207dea17c4f2c75772f05e88a7a5834922caf2ea2817c9e96e6c96, b6ee9e5a13d3ed89fa496dcade61765a4833656aa5a37b8b954ac821870a3cdd, 388d76d08f2f705655c9ca601be6e3b4818e23f4ae3fcc2c672f71853e709644 +d19de2015d79061bf45c3d6ce36315064fa8f0b0f12c457cc2036ead743ca5ee, 77cb5a56cd07fec4b9a2a7ef620974abbebe6f5d171285670a52a94f14cbe1a1, a60ec3d2e3b8449535c235f96708863c66e90a12e2ecfe2b4eb8e2439501091a +745fc3714d4049c8160096f88e5664c685e5636a9ce4fc8d2dde99dafcb05b86, c64462d58638704e6bfea5c47887b1a89b596ff03de81676fa138bef590cfe45, 2af32888a2b52de4fa1a2569ad293544e8a92c23b5304d81c799aa641185757e +19811bad7098c30704db46dff71f3d2600a41d9512e8007d367502fcef4a575a, 9a86356c29de8bf2ca7b3807176fafe1342754dad9c12fb4f608f9af566c8d83, a8bf8823b0fa34beaa74ad830962f4a2cd63b1c1cda52db88d2f471548b93ffb +4c3e87b4f0e70a828295308df6e33f2da59f87e12040ed9cf3609bf8cc377fea, 776e66a38d6c096f633502ea0208883b9ca5edfbb0a36f7278d53a5c5758cb85, eee223451d0fb73eb538c899f3e6fb45dfd7b242cd1212815cf4086dcf8b3f82 +98d1589d25b81cf76bfef6fa10d05ee0abba04dde6adda866981691d711bc4cd, 17cdecf22ddca6cc6da3f07812dff0864a46c9a78ca4de3cae649673e394866f, 950e5ccbe6ecd040bd7caa264540e2d2c6b034e95c66ca81016edf16fe50bed5 +a0aa0b1e81f5e34c4ba997caa725a588538c0cf7a5b91a119a3b8512a0a9e672, 1a429e26ed67bdecc373dab6c1987c0a131283f0285080a401e1e1a49d4fdc22, 9b8e55a914092654217343df0e2223adf39b376609e74a9b43ed760c21f88cfc +97fb9d5c6106e38ae1337818ab84e67f413a22bb8dd7d27ed5b35284400cd4af, 5a89fb5692832964aa04b3d69234191c6a539d33e292e01a16c16482f49c8619, 2c9222d6a68621812e11e150a3cf67eafe9b6013b48c1d5d368381b0574bf95e +cecd182286b3b4ef1d134a3f3a5984397937d43c077d2868855f022d661366d8, a8e33b796496a702ba70788dc56f80affa3023763191c05d31911a37af6b6b14, 0b240c7b3a2e84d112864d3bc36841eaf82dfda633a625f9ee5320e4c37c9dcb +b181d274cdb8d6b2e941929f84c8d1ebefdfb8473b2941855367bba11f498ad9, 62c9c29a2c54a3c45cb7d685005fa4cb4fbd428f6c1194644533b47f0fd8ec8c, d2b348c04fbf426fbd9b45c4387a5cbcb3fc55006e3f30524f17f5c881d0e734 +4d7d3efbe571b5e5a28d43b4062108f8dd7c7173b2820c447d5faeda40ec6aa3, c79f6a527481aea82674625809e2767859b85469d3fe061ebae1f3a37f214867, a4b33fc3de7c7da0145fd004118daebcc4a532b01026ec2df5dde32b790a6a8f +89eadb841a962cc2804bfbb3c5596d91bf23268946278bfddb0d8a131691b5c2, d659fb2b0a5c909fa5b8a5d330880781f8cdfe501c6fb1f61d909f84ef94d759, b6744bc23d838611d0d62bee78445e8f42c6f07ea488c446c3809dc0dc65bc57 +f2654acab4d56dd356c627a417f32eae94710fdf1743f5c4094e64280613340d, 49b460cce8b5332bd591e0fd802138c906739507564011c5cc46ce75114f4863, 563c6fbda56e4b632a20e67e29bc6cc1a8b0abc185de1d42ef894a83a11d03c2 +d60e3903e877eacede78214cab8a4f438ad325a431f5546d974d17fa59d37325, c8bed219ac91cf3b5cd37d64aa3e97a0ccad0088f62179375473d1bdb344ef35, b4ab1740d21ad0d0cdefa7242c0e6d24e2f937a20ea30dbb614a0f165423f12a +91ee7a0a76142aa4a90093dd8438421425b423f3edfc6c9edf9baf88cf812736, 718ea9fd12b79855aea360d687749d10e6bbd83f0808f45e641cea9485c9c59f, c52d911a47bb2a1221b9b99db010fc2f32902acf64093649b6eefc718a83033e +4e43c451a1da9c7a9ff3e647e1ff3d482dfb2f8c0896c72b1cc5e3c6aae6d768, 76c6fb46bd0908122886866e3100ad0d66beadbe17d09b4a106bd43f72cb4448, 79b8279c949c450e963cdafbca8d17065593ba08b01f29f7f0660a717f712bc6 +e7587ed1a28c3b21130cebce8b18a68449c3c60dc199ec409803d26748b392c5, 871bea98c3ccd580b5adc276fdc0965826ef498453989cb8bb24fcc7a6ddb7c4, 8b28a9ad40a13ce70191a129eb4a91caa6ae569fe42dba2b50f03f1bd5e3230b +66a6fe30b321e974e0187c8f581855efe7226e01720df11977415a61d486feff, ad62901f7644a7d589f709bba23596965920632d7944ef7dc4af844976d5ee82, 8897daaa3fb648625b18aad6ca8d4354c1c759a65998a1711232b004ae08afc0 +6cc65cc5e89922379b531a4a75a87335cf8ff6d7192cde374778434b8de24fa5, f54c1211f48d85f7612d825eb7c441bc1ba234cd1a1f0208a2d39ff6b19a303d, 9a09a532c49c62fa9ab15ee03307824c3cfaf6c9ee8f8ee48d34ca4612793fff +0f3aae2b990b876a38ea684fa6ba1d9b70115875c0f13f88e25e63ecfe625481, fb1d76732fade77381001cdec91a7c61194713afd317ba0e870eed3e55d00d90, 119d558a2e9fa5be2c7e4a72873fe4b29492a796b9373dccdf904e96afbc6a77 +c02224448aaa7d172804e33b03b2a3b02e308d2e5092fd5782a0ec7a00a50686, b9517b83535695e2206e4f5db978fcb6221b49091ebb1fdfd2ec8cfcffb4493d, 75c18deb5d37fab1160de90ccec02cab3012a5e849ca81b2fbad7016305c905f +904e1a981e43fe8dc6d9d44399b98922ab9a171a7183cf9aaaa8731d0ebd78e4, 6840e17f4c709a2093f2b6a2bc439d7b778c0556c0fad0be4766934604c50f99, d020979be777c2b461ec097effc87fbe8985b5b62e4bc9f5d6af62feb2e7da52 +d8ee43c866887c55eb8097ebf8b1d21e32a67bd26f6cdde52b4d707b5e6ec9b1, c1e4f0125eb89db1b7bd733b359ab6fb7be47ed9adb3c4528666e0958c89874e, f5ce32c074e33d30eb3ebca68d7b8be3f335f3597460f7980c25c87d3ef5620d +9b136b40b2d8c82e652add35f8ddd6f235eb6c21c86d98cc8575a1afee821661, 97782c2da4b4d54936668e7fc1c07d425219256a2c504a7bdb0d742f1506a8e9, 8cffabcba8b0e5e85d1d148ee9d20540672e84cc22270c9193c9d6d0a1c9f6e8 +09097139e45f4bf12f8d51ef4ccdfe35709c57b9dcb293460441c65dc4d5664f, 824c00b3d3977e9abc4117b755053a42348cf403f9e45fa53c383f0c666dbf61, cf1934545531f11bf2ab60bf96567df83a2e35609e20af3861abe6de733b6046 +eb45f24e78505df5fa40ee4fd53ced00a2089b3c447014d052d5c3730d0cab2f, 3029e6025c04d2fea31ac9a959bc5ba91826490d5254d61b720f0d16068578a2, d6bd17b8a7303cd3dd68f814571332ee82a7848627d2606434a159692a2f5bff +8ffebd740fabdd74b0f2995e2ba6f3555dd93d0143baab454e57a923e5072c4c, 85ebab13178cd1bdac0dc50e2875e27b5bf81d0e49bf1a2164fd936ce4491ec9, 20ca606a6f8302d667a01781a14dc69a67dd1e1104f3101c54ba4427e64634a2 +7e9f1954328e942a8e97da5db18c9973fe5aa4a6ba2d5d398d34cd10461de8f8, 3393db3ff5d2b08794d55b63017bd165266baa62da584c418145ac60a9692a7e, 6537c6b49d21cbe2c967a6bf18907b509a1c0125b4a686abb53673789381656d +b94fa02e3c6530273a8f59f25213dd60ce28f3f62f586de1796b99411c59e785, f45f6c6bb06178ea7e60a77a405e92d2524acb4ef51562057dd851fa70e88d0c, cba0cb6c27370c4408458bb54cd0c5dd8d7bd800f1fad926e3d4b13a865f7200 +31fccb251a7b4e3be1a5105d3c8d10d28accff42bece10cb8eed77f1342073c1, b42a564170326ce44045284843ba9093d885b9e2c8de774eea543050a018da81, 73db78c2227d51031673e4eb648204fb104a8380dccbba1a162d6806dace9201 +2264bbf7772edc5f8ae4a6b6afb96f70c3a651e42cf04c85dcca0de761005f4e, d84c9867d2cabb75feb8e4f48045281bec0d1ccb5c277f97d49d77a20339cb56, 8099cc346a4a1fbd59194cc0cb64b6041ea7e462cedbfe3e23e8eb893ed3161a +ceda63d7a796cf2088086095a1584881e98dc3e6eab8b29ae9ad2b73740d4008, 82da86b1e4a17cf1373b85510560a89ddfb7752b5c7d074f1405601a868a57f7, be295496181533f9cdafdc6a052fc37b7eb0fdeaa92b20efef749a43d0e9319d +058f37dcb55d1fef2a2c80285fbfa7d5b3a0c26e4cbea092275304a3e23b4a8f, 61e05614ce75d0f043759d552f39d8d628926c9721ba9a67908efdd444af97fc, 3346b12499be05109d819632734792c02dcc622e25fc34fcba1694765117dce0 +31a8d330844cbd5232678db507c8c307b52a5a3cd8fe99727d1bf5ab8ff593c4, c268b2e75ea2b48fd3404702ae7bd695dc0394f04e3ac959070b7f1ab5b36616, 4dda5fe28a0ca361f8b57bbe042f6494d076810005ff574414e5773b11871e77 +43c27087df6f54a419d8708612902a7524575d6aa04427613449576c74b6d4c4, 7f0b4d51cb88b39ea277efad0ea1fd131633a0c3a7ee3bd19daf316e9cd6e4dc, 9297f658efa46f178c44b2467fdd8af261bc465b53d2b4a3316ffe5c13402388 +d5322f6e70a8307827d09f44ccd2a518351c968f5e2e91866cfee3646ad99b0a, a3e874dad0db055379339ab1f1c99e79aba6412713378c92daab5202984eaec4, 9710434e6ce188337015eeceb71c8de11709c0e9a5e03a16651d704b8f1f5f46 +cfccb0369ad5c38a6245666c789c20f01dcd27acbed6d7f4497eabb5c4a58711, 4d7a0eff04cdae99d5d40dfa883db5b466d115a62a9cfe55708e7f152bb080a1, 278a587e83fa55f222d3d0a79295fffe01d44a95b38784a3273daac33265340a +612143b81a9eb94bfc5d2b957873e2068f63494254d1383aa53ebfb7cc1a7b19, bb08e225e2dd4dda24d825af88a137b92f318a2388e27453b152c05c63b47343, cdaa899e021198880e3504a44a8a9f890a8878d3bcddf7ab51cda12c4977ee99 +d5b8ba4bed1f33b7f6972dbd4cd4fee4f08ac51483ece715a9a48a5489213e9b, f9f3b06271697ebd61cc49c7bc156a80addb9fa7396066f165c0eafd23396b26, 9d82f95e4bdf527573f5829b511a8bffccce02d3a376b26a708b05e38266bb00 +5b6b322dbf49d62af13c01e17688bea3db573a37084b9c9d01ff88282bdd3ffe, d402a37a3bc76e9bda73a9f52d1f87cd230927a71e050c5547763e8af093ffe7, c06a005b669559343aba233e632bcca19c575dcefd163556896414a84576630c +f752c7fd5a03ac805af9fb31b34323806621bd73ea0fa6845268c90d7361c677, 40830d095f0cbad61ecf8547f48c4a716fe4502afb6f2ae81a757e40713c95ce, ff00935ced29a609fa7a42d48aa902b582f2ab0ec09c443c3750236731096c7f +fed6482ff749ab302d3c327ccb5770d04896c51fc690361d97c745703a7a542c, bff03901f4c92938aa6fd0926aba318f33102b89c3a7b56c0c856640119c706c, 075cc18e4c818ae6b4ca70a49643c381a61d5bb06062fbfeea7aa19cb90e1c52 +603f14ce233ac8216b87f61198aaa2ef184e2cd603b9018e338f12b9464838c3, df72ee92ed57bcca5d578ace54f2ffbd6a8b4ab3b3e73f9ce2208bb83a060d7e, be6f836d82fdf6bfb06c048ac4d655d091c700dd2c9dba4e27ebe9282d5d8f35 +249467fe22a33afbccc531ae379e1503b47ce92b2e8d6109f1ed7c81f1713e0b, c682a6a7f46f2153243dc263900db374d8370a3087547eaa09cf551da05ad5f1, a39da45a187aa264b7fc2c42804467923154294df8aa4396cf90029fb3a0eee6 +95e54a50b13bf09fa0fdeec9528216cb1427532cf047a9b71314ec1ffc508daf, 8afbf1ec6a224e501caeeb33def3b74d25cffab29aa28db324c20eea452ebc30, cdd13c1d83277945e07369c1a705e3e2b3bbc17fa5ee21748e79ba8ced006904 +6008ec9d1e3ae1cb6ef51976bc90b697b72013801863ab2a5baa940d3f96f0f4, 2415d537ee5b154c6bf6e92d0ab6595f6d95f8c70de40093497a71ac4ee0d493, b165640e4bf5a78840912a5e2602b590a3354b4cd973adbf81b997e91423e265 +17a161e669326737a55ddf40e623fa1c23fa9d2e015ccdb8ff1ead398fb25b4f, 69a3b9757d57e4fbbde90cef0e2bacecb35b6e037d7882c0a8a4f05fe783e051, 223c61f9f5a6e6f2930f04662ed52392692a69ee4b1ed787e799219ac838b397 +4f6cf1acd616b993e3861e2b1256d46a68432628718d04a6c9b480f738127d39, 9a63d472e2bc8f0ab20433ed359e4c1bf54dcd143d2613117685e959bcae0166, 43b8d4cf6cad67fb21efb1a829762bb8b1f4490313b020fb20e1cf9e602733d8 +a2dc382409b5ab60c359bd2a3f3b4aa64aaf63bb2215be73584a323e9ef4d019, eaf5bdbae8d7b8fa838bc6b7f8dde33b109e1cc5bc390f7284020af2016c21a1, c46c7514a655468aae7f423db0ff9d5070948101ba823194bdadbf7bfaa4c9f6 +6d3048a4cb1bee61c59cfbb2150ae9e31b5383080f34b2f3b4b5b61341df895a, afe2f43a2132977a9af02660beefc0c309a0f21e6c0961b91d25f8ffa9fdadac, beec92796c197ec46d7d9b3bf1f971800cd947c80d76d0be3c11cb48387e812b +8c20618bda061e9b54e2e4d844eb56af7537e83be01ff3e444d2855ab66308ce, b3d2335f0513e55fdfe3ae7200a69c12db9d9ab114c11bb298cdbc566cf262f0, b3374e60b1f11ed1aeb0b4740a531b6ed0bb1e37564d28ed523455d2825c09d2 +aad27f9d4c23118b0fb42de4b28b703e271ec6ad0bf043b7000874a8bf65700e, 48f5167467a999536da8ad289f95095daf91b057a53ece8d8e6a41a75bffaa15, 335db0143e7ddaf57223436641af6965193971373bbbcc403a3f7ada2b570113 +1799d17002eb69b537c0a894420e6942feeca7adb5ae0a56d6a5cc416e1d341a, 331e0331d348519b0014c3fcbbcef17953c9e2f06402c3d58cd10e3ecc18dffd, cd566686f72bd69576d577d24e243153ed844322e269141ac639633424bfc358 +e20d14b0c4004b884070ca20743d0984b02e24798d6265f9bffc091260f64738, b6510f4c57cb2b678bee9b70afdf9f03843bd59c32ad1ab1a80f7d8bc00a33ce, 966bea8315231695f11a787345421100ec7a8c8bf84651e6c6dd750fb94f2d8f +86a7f5426ab0fb2cafe482a4582ed448682609405527c0a71f40de6031e987d2, 4164c512b62d2fbfa08a63636e4cbb73f84941167829d9ec6c5c586a6002a0f8, c982582a6e2d77990ff22b2c9b460b6e1a66e2b7e1a8c21fba519755629ddbc1 +22767701d4863cf0fdafaaf78afab72c831a343ec7c636e2eac60cd88f57f8ec, cf2af935b972b0304c58707752dcb2fde55b9124549fd297649596e3c0b69be4, c28f1428fc8a1ffe9dea6ca9eb91447b91f928a1bde711c8e7f6b922c78ca7c5 +f5c9d2e5bd9587cc4be70a2683a5687718ca348fe20bcc4c55e96caf48f9dcf9, 7dfe6042c3f9ad9aea16f9cdda05a654fe3140a4a5eceaa81aae9fee6f42b951, 1871f11d451889c14d51e7f50a19a4c133141d60e126a7081b21d1a953005be3 +8ad3cd007ab2fc86d461b3f1c944f3dc414f3566fd1e39e5a7d481bd40ce1f82, c84cae4e234d399479f0b8a8b7f68ba3d2a18542b9be722a3856535f133cac80, ad54773788217d5779cf4a43548e11b359b2655b76ec51296807b6e358c97189 +2dc3f4e11460d9cc059fc528ba16f5c912c7dba4ee0920f1dd62d7810bc4cf8b, 371b322efa2f3f2e41827f8ced2ede62dee30940cf59ec9aaa089619785f4896, d61f6fc6af169debce3c1737baa22a2c24e2d0b85b3e0108092786de8b40ad2d +4eb8e114820a0eeeb1463ddb160f28671180417e537690cc54b25cbff2469884, 9bb83ce53d5b7439d074c6a6a59e70be74cc2e2abde708e1f823604be79f87b3, f0806997dcf99e81d7b0ff7468c9bea42913a6226837f13217e8347309fd70ab +25831dd8ea723cc0de8ff13793c7aff994b55aedf70e3bedea92c68696b88104, 550f8f0444f6384dff070d8543f643911b3d499443cc4874b4c353665f8196f9, 8f2648aa0b7b9725f6dfa2680050e51cdbbcd4310c1e54599325bd529de80457 +ae1b77168acd5792bac9ae8dac20a530224a9ced758ed77e25e31f507c39dac0, b5561c1d2ede6105a75bc1df5f5e46edcdf5b52af5063498b6cf395b412775c1, 506493f3a0b2665bad6b2536afde39c4653b753cb9ac327fba696ed5a278fc47 +cac226c1ac4249ba001c2c2059e3fa9760e786bd17af6aa26f9558af8fb43db1, 36a7a810a754805a4a34f2ef7879d25907e5ced9a9b581db466f9d309505a070, 62ed9397cbdf33ca7a73d5bcff8b4a8500115a3d914e2a4c9a15033060d4053a +d2f495a37e41595697edeea98decbe760a7228c26aa7cd4a7333eb695f04401f, f510c11a29560f16c210de88d0cda9e91ffed267cd070db76e21a59f6d99be84, 58b5591ab3c8b592af69e7a0467bdcbbc49e8cd17e44ffb31ef46f4f26018949 +f9b8449534dd8170fbf55e5326cb0c35429ae7eb49fe906c1fe1a63f840ec4e8, b7a0e2238215e017a4bfa36bf0e5f645e48253d2d243455c8a3c23cdfbcbbc58, 5681e23138eee67bf2daa8da093d9aaf7c86ba1677a83989e9a084488e1cab3f +6be36aa617a09f3d0f9b48fedbc83c1405e25a4ee23e5c2bb9f50fe78abe7462, 14add37465d8766be8292effed9720498501fa7c046c89f89a5778def66e2d0b, 01d511618422d4c360b9a39171fa26a0be319f0f0fe2f172e3db46d4a76833d1 +070e7a11e4c9dfdef4634bdcc5d5e21802c36a94fb3fa5944a5773ef933af336, 4922f59bb9a7e4aed28742fea242040eb7a3a1150e15eda0a1bb9dc2da2a18a3, 07a69c912d4b5e706df9f4144ac93c9531508957fa13b4d74499b46b093e1bc3 +1a23402c59d348eeceab3925a42a67a3360c36a904ccbca3d07bbe57038ad20f, 288afbdbfad8ca6d000584084034d973511e22ebe5cf5640dcbeb4fb93def5ef, d47371643f5a1dbeb84c1479aaaac26014af402678330188ba518de87252fcd5 +a6c22219274b18940c6f246876c11796ab5881089cb2389044d170c9b4db5ee6, 81a4613d8ef278a0a14b9f4e5af013e194ce463879167d8ccb91f530b3edea6d, 3fbde245ba96b25a24682d8e911fc4cf7e735ac2148dc1bec90ba59c5bd0a880 +01510551379faaf967e15daed5eee59f32f14a4882e1cdfaad1c5176185909af, d1e65e5a326f051b574f18c243d87049416b662f5379dcfc198678d85e859887, b4421641b225f0037190cef2f7f6bb521b5cde8480d00a0375db9de402944cdb +83331ee5a9d94eed4953996028bce779a2a1df5683e6c65c8a91918891f952e4, b9ac9dbc3a086fcb58dc14d01659709b354ba85f114ee7acf53f3f733c5a7587, d41bea4599b2e71e37900cef8a6d69a21bc40df1b7039825fb10c9eef8bfe387 +21ae89ba63499eb5413051241c023280807001afc799d9dc6fe56789d8f21324, 50da7ab02243c410659a351fb304f8ccc90249e648d8d7e495ca4e613007729a, 66e7d4399218e701af0dae04f88211c0b30143ed2add6548ce76e2d5555785f4 +b86bad6dad1a31f660e2d0458ff7203da69bfe85afebd2197f5769a77728263e, aaa66d5b663e8ccc509c2dddf03be1f8ad85144b88f270d6c5250d31dc39d1ff, 067fc0e2d7188bee01459c13fbbfbe8881058d66fe4942086f07a17dd18c39ca +ec67dd6168d63c6c34fcc0cceeac8d31038f1b46b0f953773afcff281a72db8f, 3a7bc0292fb683646bb7800a8e7cac89f6704691b0646901df9fe1f10e3907c3, 55d1f51ba65333e23d75c9299652ae606a4225a0781e65fd64948f25d99f6c38 +97b8b9a1f7c8ba6be10d8cc3280d9111bf6c55bf09a5c32bdcaf926fc72ca016, c645a298eb170088da7601c90f7d1d8843b14ad6a697ba0a1414c92eeff18b6c, 235f648eee5c00b40a0d97abb75087dc4ef94274f52fcf9d4928b11badae1e7f +5e422707099b03963eff74cd9f4c96c6690b0e5df2dcb631933dd15a4db1217b, 6041899fb1e2048a9115c6b2333a3ccc176ff4dfdefc3f3d30ddbda547275502, eaafdc4f50c181254196cb7c7ca9265019285e99817b48edc3dc86c7634178b9 +c05cefd7f48ac78803221f790f17f38a401960033ca9ecd6f90d7b568f61d236, 9df66accf9486ef15d5c5a4ed20e2f786742ed429878fd17c7b34d831e938cea, 6f080c05f8304847f5d24789f2d1de22e6da0dc8cc3f75c62f50638af574270e +47966afce233da40c6f0389a064be3aa084a4446582e6e31b1d6510d2cf5575b, 34bc6dd6e02f42e23e568620df49759641c0da270ca493be022112fa7e178790, 071fe44543334c6b3dc651a256cc0d8c733d9ba990913c4cea170e7e45ba0993 +65167976723e1794b0a71d5fd44f4c81957e59772f6a48c69342ecc4c30ed8a2, c88b179402b5c402c60ae2cec80c994052a1e93d4d3424d39da05d96f9d5a32a, 034fe3b28a9cd41bc42d048587b400798e1235b3839a2875f3a50c327adb35f7 +08ef4507229fc0a765cebe4ebd7834dab51dbec0dc01a0447a117744561999b3, cebc3b273453c4d0505754b364ae20dab9119ef45ac7b0bdd442ca167883e97a, 5f1026649f7111debc076c4aeccc1c80c2361f769c0c7571667168e293bd1e7c +1519c8a194bc36acd78d93b45a8642b24405f3601c0735b138eebc6f77f1bfff, dc4156e9d38078aaba1e1cdf69018d76450f15881fa860873686fbe51186b300, 72e0dd2b6b21b0c19b7db17b2ab1a62a23b49474651a78b7b072a0e64fe89375 +667ce1ec401d2f63611aea5fa20c7505763366ec692b425629b5aa1924abd725, ea3bcbc67f9260bb38757326e846b5715f9832eb29d2447faab854e15cb42f6e, 5920bd03c80a16a5f4bc9578747950352cb1fd4b5d7c90002cbd4a6c667f2d90 +a198b3f483f580117b8f5e756f63437cc1f1893ea523d38ca10cbc29f3febc2f, 4f0eca2ea748c94d56fa99991e158ef7d0fa201762d3e66f27b34c40b53e18aa, 551a9c1a56a530920a35b3d4e045ca5fb8d319f363eab410287c7781c1755808 +8c0cc6bb9ff332845f1ad854d8ce984ecdf3d18bd8d1d8e5e89f6f18af61aa80, 62f92a1aeec61f45acbb48f397e7fbbf8f67b1c3fb51794369375af471995c9d, 7ec5b11027d042c7953512e3e74e3611563858f466d52463865197168c1b66d8 +d20b721c97a1e5661ff2dab50f3021e73a27c76602448f5abc82b32ad6bf242d, 232063d17132a9b42cb2c6abd23a8d0c508fb5b9d7a518e8ff29960ffc2ad320, 7b18fce578779f36b71f866cdce2c70801888c6335d0d525a69867375cb6eaab +1d9b2b2349ac4723897ea54baa6f03bb52c1c185642bd0bc5c08958eda37c416, 40e50853dee913b7fd3bbf8b6b41ca4a803ed2fbc397a758e50c42a2e2f7ac18, d0f9cc11f7bd27361b2887f1e3411caacb4badd4bde88930cd78117c3623337b +d9956a8b24e646400f9886fcc5e73a4e4b09230c9d93b451e98adbedc13b8d8c, f757b2376d1a43a30aa4413cb3134782873a7df4656fd2deaa7b8affaefab79c, 5b736f489c5331b81b0ce5acaf0f188f0dde186958a11a97a57b381e3854c34a +265cdf7c953d85aaf65ab42f3d02bf5a455108851c6f040d0a49d2dda424f54f, 903e9483f84808fe2fede8f956af7f58245a7e0c5f04c0afba1eafa717f6c646, 2fdf21c547f1cfea97395a938d966b07e9e2e1e9bd7fc701433b095af4e09b7d +bb652d54f2b2b4dc2a832a125ed692a8614be973bd61c91ac5bff04cb5d2adaf, 0aa241b1de7f800ea73620f4b7beea20f0ec3568e7cf1c45dda720d4ff0da6a8, 68d04065863bf519df9709732bc1b7010b0a568dc47f5e8b371ee4a681832ad9 +2b92119cf8e35d165c89157ba365e3de1c3ce2413cd523bccfc62fc2bad8b5d5, b048c5b80184fd0f47d5a6616e11bcaf2a10c29287289ca377fb0b130f534316, adc4c711dba12f5198222896302e9a0086b00d3d6b8324cefb3da57bdf65c1b2 +3b5daa7f94445754deb91f6b20ff78ffd45e779ac26b723fa1ae3d0f69e6d5ce, 7fe349e4f7866d7dac6e67a431f3d36b5c71e39ec01da66adde657792659b6c6, dbc348a4e5a87e7683a1a0ea45a5f0dcc583c66cceb16b0075747be02434bef7 +69c91b51e34d889f8dd12347772fc4ec4c2958c9a6e99980bc9fed7d14dccc2c, 6807cc036d7a3d7307a1f5af2e2b46b90fb3a355f64c51f5b07e559d414a5db3, fb00f80fd33c06ff4b465f1f51f4dd34b0323e12f68e100f4e245a2e7e7ae16e +1ef349d1a77790a0ef89683c43b8e1ae349a3b16702798cfe465ce3562dc735b, 57b94df75deaedefdadad0038ca65dabf5d821ccc7ac3c008877e62f16e48755, 032c5cbc00c7045291f9478e01f4ea808ef65732f9ae898a8357d123881c5c77 +d7c7d05f98559a96837ec0586593d9cf6331600d375b3b2183278d0b5a481bbe, 63a5073ada2c71423665ba174f09685e956ee49cc2076667c1d09e0cda932f52, df74471882a5896affad3954aec119b321977eea87bde9981d2398b091103c2e +671d7863e5c38fdcb0949df4be78f63b9a3e5c033a4cc438f31c868b01fb4969, 06a4bf45fd91204cb63adef90ca63e1b34ea09d4330ff636594171299e4c2cf0, 944766f1b9c7db67f4dca1b70eb77bbe655134b4f5a6d783061323ffce0179a1 +d48b819124a6b6b63ed10eeb83230837e03af9f933860495b87d2e12da85a058, 76bf22593dc5f04bf829f3298944f88abc1cc4eec7800273ec36f0dbf927f4ed, 01bd711c9e07496c13c52847cc16881b323051c40a6fd4a1512471401b56c6a3 +c85560c1647756f46f51d9d84f2e435f62b969f78514084ff68444e5aaacf708, 9bc4aaea63e993911ecf80a3571cb045c43d6f87fb36c71064000a4df1d40cc5, 9087bae7d500f72f3fe3ef6bcb86cc72695ab1f1d4f49d83be2f4d38a9ca4b1b +f1997d4dfb24e6a9590a2f8a4e7d2f32bebb33194a15d8de0a18fad104b66073, b57b5dbe4ea3df9c25408accb70189046d701e05190b3f682c8de71e46b596c0, abbd0a285d7083b14cd9c096372812d73ae68e0b31468deb0139ce1423739cff +3b442740da33573c78096bd7910d1b4798085ed9af804472028638192ca558a9, 83ac8a0845fce2c8e1063e4d3116a545ba41246d045dcd9fab4c3ce3c4a757dd, bfbdf733d27f0d0909ff0970947a407519ef22819813e8c0be4705db105fc6bb +66c25f8dc223447ac9ff9ab177674b943c5b1047addfc4b1170380f36ad62161, 68cd1a1d070e331c7c2b2c2d3c8a05e98ffe80f82e66a3308ba582e32aad2e85, e52bf82629746aed5469a4b4800d8172b3839cad1e2dd070220b748779f15e50 +6437cd6386f22defbdf98181441c312007b16301c914a2084e32d240fabf1056, e1a4ce711e89a4af7ebb494098f8a262e0c97974bee31d13f1302d061a4d018c, 9372804bf22743984c8bee5ea58fdbfb2a829ebe5c458b96ab02eb8d297abc7e +3e8c474f99c9bcbdc8dc869772fc4644c64c3bbe6e152966d82fd1a67d45a432, 41cfd6289c52e4b6c7e8115502d69fcaee12c9eabcdefa607f02de4017871d76, cbfdb22bffee2068ffa2d8582399d00985e19039b7bee50b9409dab32feacacc +2190d82a7613b6e08bf5931b2c7e8bbd75df264ff3adc5a6310513e635ac88ac, a6e99d41b00344a468aa73fdff6b06923ca288d56ef05d8c649975ab019aa2cd, c4e3c57101957e0fc445545e1ab5f931ec2fa96cfd6cb107eff230f2af6eba9a +a985de9c06d73a7d4c0394dca3ba6816cbb9e6ee8e623c9160ef2be3a7423b46, 8e8edde2b6915209619beed37a1b68b5b6a4ab8c21c1b4846666db63ee66a750, b8c9de97cd1176e34e38468b4e68ed92573b6ee886dc427fa544b6ee79336045 +8d237e125f86941f76285d170dadb07e99f94f4d8a98b249ef58a7b3e8d7c32b, 014412134645b1af15457e08fef9a0c55b8464ea0f6c291cbbbc86613d7f8e3f, 9beb3186f23e4882d9a83589e44671add3b827699ec3c199f4daa17159dd4a66 +2c19f29b5cacabe28421c225d9b3bb579d41572d3f59d308cc3815803e540033, e46cfb0e0fa60f8d3ed03b6e50135d2fafb3725bf7ca4f801c3497017c7a1cf6, a71a475611d24d09cf08165c7dc31a87b7d382b40ee0e1cf13793fb740fddad2 +38135a8e98165436bd461aafb3d437109dca6434ca21769ee02974a36508c169, d0798ce03fa9dbe41a130f746e87bd554dd0267dc7408ca995768b14cb86cc52, fd1ac22a8a8b984fb78f4eb2dc4c93e4a6994dc7b4ee82e5a7994faa947a8942 +40e04951b7f274e0d0399fa13223228a55a7f93edd1e83bedbc23f9a8f7ca4bd, 1a1c4baeabfa07fa59ac5b6c4a6ba02ff3ab045123ab3828f803f322b458ad64, fd89278cfd7a612e5b833e683862b39e74addc6913101b7dec7fecb2434c73d9 +16ee42b64c00acc08eda8ace95c0a3b176a27c7267da4bd2d4b46ae8d9253576, 04b544136e1d651a22e7036fbea9cf04d74ca5b5319fd1dfcec20001e605da2b, 2beb8c0a4b7d8f3ab9ba071542da69ce5bac46058993be28620c628b4242c4ea +edfc59d3b550d57989ed565414251d1a1b05414125941c97d0d03a79c6f4430f, 27e742a9a544bfe0178001e6f7b3edf820b7dbda4bad2f73e5bfb525ad1b4752, 04e238047d2308e1a73a7308fe10b6a642b1a20d8ee5cd6adc805dc2730b2b93 +ffae0dd033c6aa4be74ef3c4e6b04a71acfc14bb540d421a16bbbf059449db74, 04457f05b2fc3ba713e8bd1f76ca526267cab6d2ec9522d0047a3090310568d9, c6b95f8581f767f611f970c03d9fa85a0c45b4d63933679491dae06af8823918 +7ba3f93b8ca7e9e1b6ab6d984db7c0a05dad1619e4fb91850c4cce8bb950900f, a1d1811c98f06f3f0e75d8aabfc1eb7abdcc2a71ba6a32ee749b6a316b346e3c, e10ef3f5fec9cc9561c86b1e93a58f604429b14c1ee523250b98c49dec558e56 +6ed99eb8402c44f3d5a03f4ce6e3543fba71cfe42f8f652fd68b4515e9f629a6, ea19acf000f2413b850bb7d5c8e6005b91c4dcf3700bc7df032928fd9416661a, 4bae703e5dbf4c2bcddc1d6c5547c71436516b04f976e58a2bf30510ba96749b +4e132ddd4373f9e0eae8ec4ed5bac6fc2f43b93a2c6c0aea4ac66174cdb021fb, ca5c5bde6c95fc79aed8da6883bec403ce0cbfcf08363d1a59101ec4641b4b6f, 6f8cd98c30f94c5b455ed2a3ad975bc13649b755030186ee6dd9c64c99ef0bf2 +d5fcefc5c435f4e1e70227eb34bb5af1953e71cabc3f646b192979dc03a41085, db055a0af87a9c703ba827b3f8c64736c112f4ea108f6c35205848c579a4adb2, e1423d2385914c0a4ddecb70b97bdd9b843bc9effbaf64481cac738d3ed4b7df +612009446f4c20312fe0df3637e3072cdd90a065fb4aeac4a3d86c6f0f4fb67a, 8c67ebb73154e252a6b77a9a471fa8a9352c2903e34053cd083a3e4c9fb4c5b1, 9098b300e33613643b837a558bfca72550e0d1133822dcf23508bb59e7224c54 +d91fb416ea1f1197bb728b32692fd645e5dc8bec41d360e9761d914d99490ea0, 32291a6e92e9126a6782e2c6e2d0b8223b4bdb0fdd3598dea08bbe29b9e586a5, 8fe51dfa1f8120f7da7ba7b406431f4a2a1639851fef36946d1a6cf7e0daa2f2 +ddd8791ea954db6680be03b6a089f572fb8b22065ac0870e18299b9552cf5ca3, c412cef8fadd10b4f0cd832219a22c79a743aba82e26e90e06a9b511264985e2, 387a605553e78493eb71afd9450bb1547303c6b1e38452b83d0900a7c8c238bb +18d915aaa40de409ec6a74ffc968bb9a41b97b90961e7cb53cea87ae0a1b5d24, 7c98d539771e9a04b5b4434b0dffebb2dc7e8e23a07e29ef96ab0f7402812f55, ee3a087741191640edb4f69ad329ecb77eded29c571eca82fe34837e2821174b +3e42df1404b84fedb7dd5029b87bc1c7a5c3427d0fd3e90a35f15891e6bb451f, 30e99ff1734f01d021f21e8c5106e4ef3f92a56d02bdb33a997ea5b4a82f224e, f0006fa01d1892f9d7fc5f785d540beee1def097f24ec306d28fd15a63f5b358 +fffb261df2fde26b9d2cfcaef870b9f9799add62e4cfaa790799ee867a624c05, d7712465a51ab82ee3fe67022efd142e1bbfb819bf4c3ded2e756a752b52d25e, 67c49ff22e5a9cb3839a7d7760280bb8c3219c1409b3978232b2cb04920c243f +e5db5055f53f58721535749a7eb3fcb5b6e2c48feeae0b8ed2d93e09e2e22b1b, 3774183b8854462f1e911a6d8da910bf2ffb91d7f8af7c11db66c95b58dd9cd3, 0570c9425fee18e5609b6a39b589f7998c4770e4a453a4364a5369fb58f90d2a +b206953348b8785c450534310c5cfc351c4ecdc02ec408b425e248efaffa475b, cbe474ae2cad669b24d10225972248b4ffe0be3a39186df10bb9e9ec25cad21f, c88a4f10b22973528ce7f6e24c9f04936f43d6c5d84fbe8b1ef7086421ce8745 +aea0fa925794d1eb2a21f7b84a556f76837d6046db11a9a855325521c70173d3, 5c33ba82050d2e51684616df8c5bb82d4908819fd3aab874bbdceb3071af0cc3, fff2c20907d32486f39b08fda0cde331cb2dc6793214cc48b5f09a27960e0c44 +e7320a78f3a447e0b409669136fcd937eb6478d953729c951d2cc7919506be6b, 0253a18d19f02ac5d2bff024f7dd814e3c38ae629bda0a98a5ed91713ccf05de, d657ae399033a5273c785bdb664ab970787b4206e55d31fb7ac3796624a6114d +7b726637913482546d96aa8d4556c523020c5b4d9e9e45d0197459a07a24d11a, 688f378b72cb6073687248f4e86701373ea156b861a15feda7c6503c625193e9, 9c01baaf226d072cbcbe8c61dce11129a7845015c09e403ef1cb4200cfbad970 +00c9659344c2e04979805a7d7dc4b4be7f7273a2f86a07052328bf3da8ccfc77, f527084c3fd7c25d8d59a2989ec7b707dae9941d73fe08ca06c436770bfea677, e3edfd20273a4c5fff5ea7e2613a1146b2415c8f57e1268b0a91d2b7e87e658d +a286f98a00ee27b018bda51a8f15930d36c6f01f8508de26e84e0ee99c736fef, 74c74307b22b9202be81a19e9ee8035b77e8353b650c91fa3bc7952c575f7fed, 8b3ea1be92c9400ecb0b7dbc72cea9a89ad338570b8110e3f874a13dca020516 +2d95aeda6886174094a284e5947a354c219db0974e4d10e03c04d9a13fb58080, ba2ce98ebab47879f4ab38bc0969be400e347a546f547124bb1c96908edb587d, 90b89d99b9c1b38258a5822cbce1f1bf199c5289f0a745b804ca02567e600ce7 +8b28956d345aae6794463f52550b5f0ec19dcea544534acfb78f507f3b1cac9e, 10516002620df4f9bcb0cb2ef7ff765a6c2a31cb5a0d93714bfa54df62a47421, 1e7062e9ca9d24d23cd44fdd8084a9588daed6458b31031e2e9fd135167f823b +91a2634c96dc25ffc0c20849ec0279c6aa4f8548f663444d5ec03b669801b51e, 2033f553159fb40034982fcb57b502767ba892f204fbf4e63205315f9e0a45b0, f2d1acd248dff0dc24f866644f6018eec6527c43829fa9bfea46b23e60a47865 +8fb38e48e61fc9b25da9cf85aa7e5059d97b1f260d2a4bbb3c104b20048ef8d9, 3f31635048b0548c87d5de5f1b3559e3a5c78baf833fea04deedb1ee22e17170, 59ecfa0f6260f8c56f56a126f37c39fd63ef4e5e163d65711536221cccbadfbc +a3fba6fdd1c9e2f8c5d2b688884a042edd375a83176a1f088f55349197eba526, cfb79b5bc201d36b2b1d9db43b6573be4bddef16da25416af66703cf41779a28, c1b27fa7b78e9e7e0f7ce4a0436b1d0e582138ac545a286bb6b53b1fb046b419 +08a4b12dde32a0fa7b723e68babda5b72e52bd4f97c2a18d7a49254484b0f589, da5b1bed232e9b3b03743ccf2d1af84ff92c5301e10ed90efcc41c9552ff3b32, dddd736b6fb4b5d53b4b05d3536ff43736f0e93240ef1bf6a90635a8a3f9a83e +8b63e70d4b3f2d44929134ab520da543571ac993e61c3f63107555af7b2b6f7d, 7a09c7295134255198c1aa0380444f08079248eb9592ddf165f3d75f7d4a1126, 5ac21c27b4ceebe8da1eb63a30636251423f62c5b5a46f17348ece9c4582d1e3 +f33701ab7e14a617f519ec8e08aa59c9e62e3e1fa36d869786ee8394beca0438, 3e1c7893e55763c300c565cdb972a3aaa3de19e0fb2a5c413493d58f0341a9c3, c3af42a0bcb94e141add35c6a92ef46aa8635f04120f1b30e0e26e964d4d197a +031ac5ef9d2514076686cf8f5ab6b4c1f0fb3d8ab56a6ce8fa7c9bb51c07e397, 400fcf5de07d6ba01004413ec81e10bec19e61966dc72221d367e14b350f2960, 1e2488587be279dfdab5b98717565c6f8848661360ee6bcc41f8dd82c2f1bf89 +0233ae87eb9cd9ee0d4a0acc8a9de4c90046cb8aaf69d53d40e852dec95a1b2e, 46da515069d2fa2c3e246e28fe7babaa0b9f41ce883055fea18bfae830ee37bd, a336b9d5787d5fae904cb804b295c2b4a331cd7df64235d59dbd612a21eb5adb +74fc94d08dffd453b33b010f685347cd40d49ba055e6508721c5298dca22118b, 194ee590a21f7d5cf13f2e49944d10bc3b1da098b85cab1720afcbb52dfb137f, d6563ea2f94f7f7dfd6b5dbb258e18c1935efc8ba89403317f51ccc60effcafe +d95fa74737926cf8615fe4866afc82603beb91681d36994da75f25e518a0f8e7, 50815b9992ae5f18c90e049c86b5669ade316950c2c8f29c2e5b591f0af34171, 45d3e704861f7d3d701bfd6a5b9c1d6c4f1cf2d7fd0d56f7b6652a04675010f5 +f754b45b3e40c1f69f20c916d1ec18db5c214b3d4aa8d0a214e87573d240d790, 167c5e41814fdca93c06c442fc816b86c9962f9d6bb82e7970aa301d486b3797, def836380127d3989d414f6b25aef0fe570baceccaf644aefb44e5760c120381 +33c69f3f444bc7e6cba597068b0e47aca608dd0dfc83afbeba54df250e979488, c56f6d0f92aca6e1b05aca5c80f51fb5a89f0eecc7f239297eb2bfed13ace515, 09e0f8f19d307848de26f9397774fdb3b8d8f0fbf54735f4838e2a031f09dfc5 +c4c7252ba51cb819e3fe6ff60db6386389bad50a7b7372bc47b13f9c6dff8c25, 4d0d06381cccf27f4f64ace2c9213c01dc2841759ec632be68a543353e910a5b, 1ec9b0d4be3a7048323855b4ecce1a4b38737faffbdb8f6bfc839e9b6de18e94 +4e2df28699e7a75681dbc8aee002996ce6b23d600c8ed95895b5443345000343, 7a63cbee05611cf86e0daef7fa8140dff8dc930f3ed021602a9a9dfaa94895e1, edfca78db50f73ec0c3c94e678e9407416fb940ce2fa349c1ce6d2bab652e8fd +365a8cf690154f641fec749ea161f072e418df8373d547d13455abc5d42edc55, 7b19592d64a49687042a2bac0aac5f529c1edddd1a8be2af73a47dc61478c9b3, 6c817990d837cb18d105dddf31fba923f5e71d6302e6cb7fda6b120fbc643ca8 +2e085510c9534106cd71515797f8e54177bcb6bb74cd0623076372300f4d53d2, 0bab06f6367729d80a6e1c788b8c85302388486b00af55b7c055a616b77997d6, 0094443b044a0037cb7ff68a29f8a6b3597550383b705d003fb511de585caced +fc1888612220e847af32302b27bb24a8ca2995329dd3aeeee069bfb27bdcbad8, 8f7098230b7c6384887dde532e1c86c91c0251f5de24c8f646ee45486bd1ad4b, 072bf5952577ccd64ab47b4521d9a8f25e034042f61ce2439c0e2bc5fb097940 +9586350d9c716fd4cc7c0aac521b6c7e9c1cfbb1876bc6d0674bbbd3db32b4d3, d420d9cd010e1e4bf108d7cfe2162b7b4966b3546f100df3798b078ede59c14f, 8066e889751490cc108ef9b30067fb76639f1a7d4651952d15f2e6217675380a +2d492c4043080ba92fb9260027562cb63bbb30da2b68fdcf40b7cd362fe50298, e4a129a536832eb203df1d5270d0660cab78444fbc4ab1246d305b6b0887301a, 5adcdac69d8391d9f83e88c08965588775dd84593c3f438906ea7518b61097ab +caf617923211e211a42b6457c91e33bdf8bf01c6136de35cc60d2132af47f238, 8e890ac754bdb3f31ad67081517c898c55a264fe50bb2ec1e12be20330437115, 42369f8e87421fa50a2fa2bba84e6bdd95c33e31a82f3d6a197baca290d8d0dc +ebd4b6ec53b0319f8b4a6eeac2cd3bc69ec2a9d49f82b2a8714fc5883b2c1846, b81d9d3f8ec1c23a688c9eefe0bc1ffcff2ed9a3386620e3f15d45167c649b8d, 145daee3fb0d6512367a74aeaee1e3ec155d6e4b545bc95f2eb06a428702bab7 +394e6d7dd8fc91efbe4989186c82561d26ef9b84d585aaaeec34bfbadb678b45, 492bdc4b8159ee576bcdb6d6aafc790a29b767579f9f6b078b235574dc316ede, db6134e5ecc765c7c8d28ae4a275b562183d7395c694257db718019d3b5a20a3 +fbc2a815abe0d3355870d460e2a27565bfbedb5c6037bc2b20fbd77d2accecef, ca6f6e9c6a77a1291ed524036486acf0a692fbced8cccac50ed9640bf8b091e8, bbebafb312ccc5166cfd2610b17cf77de6a60f15507afe4a23189e46fc04a950 +426eda11cd697b540e07e51a7091d5fc43a37a495c1d79cb9a859184472ba210, 3e60aebf040a74627827ed5e73f38a165b5d23195c5db020b4b8c11b78a31b81, 538aa5488839ee0a84947a92b353e0bae367980a61fa8556a7d391e9cc4dab1b +6fa4c7f84fed8b4cc1f329b53ffa9e334ec9bc904b7baa8f98b18bc940c39b54, 5851419774b1b646dc7bddb747af46e7065cb861aa983c0619326b79eb3889d7, f276c4c2ad411f52338986b8fb552bb7aa015a92053be69bea41859e28e53bb9 +c4ed29415a85afa311cc83d8bcb1ee5ae546c43375ad87163feaea22efdb3e99, 7ac48e14f3761a7d4af8397390f4981d9a17e689f675dab087589747d7bdfd5e, 63a8970c1db4b5eca0b90bc514aef207eee55f4edd2545a72b03a0f444f03bac +a1a2200e6c911d082a01edfdf93fa6454124b65d7ed1d11caf2764f09917122d, 58bad1f03326675b8c2a9988cc8b1fd01a9bc82e750f164faf4c61aa71f6b5af, 4e6fc5eedce6a7f5e73d20b416ca7f90e463775f5c5d3b4c2124874203590da9 +2e34a3b67f5e3e9759a40bfc3fa6a7f25a356b9437d94327e3f392f5600d0829, 0bc71cc04d418ea25fa0fe038e02c124ab9123a7bffe9cc182e0be29859a8a03, def995f773281ad36f9b3949f2c16f196e4d1d062408ee8d8bd5cc1d9ce4588e +0735c897bf79cbd78f922938e7814e8d102f3051e4600addf528b7730bb0d03f, e28da2625cf1c60be14c81eb258058a8b64c5fd7b19c8e4e533fabbfe21fd0c2, d1aa80b8ab1b9283c9b377925468f3044928df788b798ef9e52a85accf403b35 +619d6a9225ff1c06d8a4ae9501a4c719b315c312a753d42c9b84bcc612ec6ef6, 1b1ec878207e9c2b6535def957f940c66b34c5b01766f9ee88ade9c6702371d7, 4cbc70775126793bc4b445e5f3a76042be177e532ffc5dee69f6e42e9587d540 +334938005d6a2ba88ca49cdb767a70c596429479b01935f293b5467b3c7c3812, 12debba46841b8d84510b32921b94f8b9d3cc9380078b1df32d92e0b9239e8fa, d8e9743f12962443d2b5e0dfbca6b6298b21b265b6130c59f22f400226802fd5 +282b0f2f37adc23dd63cd37938f733246debcca0641f04469767c6bf0783de99, ba868bcb4cc13853cf67f55bd285ef51d3ff9e27fc679c00c13c411a62786b58, 6423e3b0c6432c46c2880ca9fc349e155d14119e8472a1c746de8a0bda44f02a +4ef5d58ddb8467b2223f340b700735676c44586ae21172de8ce62f2854c1ae2f, 2d354728f94b8dbe2fe4474c01643c32ea16fe3bd81a62fe8e81c4488c35a2a1, ea189caec57aab58f304a1a4bb473ff050ee8d37645e6d5e1ba4de9797447122 +61181e9d743e120931aaea370c74593c11826e1021df69bc2a985b4f51f4aedc, 6ef33875bdc4961ec0ba9ad35f392389f3e7e8e0af9cabd7bbb13b7bce503303, e39ea27dab83cea1487f805f3c6d2032521da772968f4a21c983718cafc2327c +e46978dc9010dedff0958148f248342238bded7c671e4220595d1bfa7db40722, 23d9a35aa98ab12beab8b1ffd4ac43fb9858e27e5cc7869ab8b5be136a715adf, c720d6ed1bd6f98c8c87b05aeeff7021b3f9bfb531e0b64f8380a5271a2fd5d8 +8f2434a73c24ddda7ee5d721edb4811bfff781d837f0909200cac02cb0d2ad84, b9afaedefbeb7bdd494e23ff9079277c8942c864d9bed73fea6e0d6fc1724a3a, e0e7a989816ea07ebd38776e5cb32e34e912f81f8668e9a47d5e569cf182f87f +4eca7a6981024e68b7b30ad6c4e370a38975407a38eae1ebc5b674c7c5f085d7, 3753dd2f21e6e0b1967f5aa515ca272f26f005067f8057dec34b72868df79b11, d9a197d2aaa037b2355717d5a971d44ea50259e6194dd0135e4caa95c73c244c +c7a43367e10eb1489ecbcb370ca91f50e8ee388713886cec0ca990ceb1a69dc8, 1f5535267274d828ffcad61fd177e36cef4e35e03ad0e2a5cd48a9cf32b6e48d, d1b1bc00523ca871f860489bff5151e8800fdd46fbafcb0a72e2c18bba001ea6 +f07544436356d2714033361761462cf5f6a07701b987e5829c3f848c9c270f8f, 660743be83554fe25e825ed150ab71206b912175b841328f74b0ff1dfe7d52b1, 062db1aac0c015093a1e71a865ba4bb1e3d3383f1fbe32961cb39e609e39b021 +300425c842629b528f788c86bacef74951f08277500ed12a5c9b028f075ac87b, b9ca7ea7be2ed8f2f4077b1ace6870d11d012d3328ae370d6dc86c2ccfdd8dfa, bd066cdbeed5fe770cb14af422d6bc7ed6354692d51a23581bce5930d0895192 +5296cb953b06dd5394a5394a1ec32ae04e820ffba7b9f112cca22089dff6ec4e, d9b93138516a4bd4535af2fb3557e9f30d0e0b3bf72157c7a5a9f1f23ef5ba33, 31ceadc59ceffdeb4ca02e4f50d65032e9ce0411eeab9151c854ab87c3adf562 +8014700aa3976c4ecfb592807a1aa538f50f275f597a5c6380f0a45826b0dfd7, 90f1bba00230dda4e05b1238ded5fac83dc8f27f46ea2d576829645e6775be91, 2e2e0178cd1e98d8ef76fa6b569523ebde0247396b8c7d1d707fe3281874bebb +92095bb4e83ce23d1c987de87f37744b562a57f080b0886db445cab7b3a243d3, 7818d7c8ba25f5d982657e3b3ec2b6a95011d6fc9bea96199d5f60edd0e230d3, c40d9fe26a6f80aaf4fb405f9bd69a52c6838831f1e81fece20c73edc04969fa +eb79a1a035e42015526b1c10670f88acf1adb4d96d6a34d7319a196639810a31, f457d88eee0121a8ad6c5084ab625b7ec1a9f2db7aff8a1893a29c7d32d55c14, 959e92622a733c5ba24b32da65eeea2e28728b7d24cc66f9dbb2bcc00b9cb106 +e84aea96d2f72ed7b8c1ecf8d95e1f4a5d4fb04874809e544ecf011cb7f333f7, d8d73ce6d2aaf6cd263f45da60172bf4360651b245d4ad46e105d6bc1effc756, 14db2c8cb9fc3dbac49ef7c77ad85f2516e3049c8fd3de6ed7a96f9406fd59d9 +0ba5e93577da4bb0898e3f922484385828a1688232c4eead3b5be632654e76aa, 9ee5e7fcea86045fe38154f531098f4953ad26aa4fb0e74e3617ceb7f42754de, 64069e17ef1671d9a5d50f61ca2737b113d97ec32c2c763a26784bf8d90e6ce5 +30673a3dba01a0c1ccd57dfadae3bbbdb5c5496348759b30c11c379741d5ef06, 650a84518dabc60266ad5a14cefa7db34f0e6a694c71e38b1ce55b980fd8e1cc, dc9720b617ae9529ac18e00f0262949d5186f218637924949c96afcf73e5ec35 +f4c4affbb72ffe03852d08064cc4f3e2e9e826af25e930973dc7019d11550ba1, 63b250af7abb6062c2385a025ec57eb929091204eb419adfce7030d33bee3859, d9e4ee29a00bf4dad7250f7739dee68d2bc2ee3d8302ff0c94f0fba976c4e08c +9ca9ff652ab43f98f5050af83b54ed94faf9ece57a82486c5dd1cea0f97a0e90, 965eccddbcc7a1f732c5f9018ae7b8cf281b8e72b14f71a964a90118c1e65026, 5f140e5f14dcbe12ff6283a4f7d0408ab8e87342f29c8848b488579060690039 +5be1693cf213ac3f0229a5de0ede913193ebf283e368f932eb52ba377c82ab18, cb55e9d16acb1195f63d40d496f9dc90642043667ae33958b2f6ea10510f3c75, ffb296ab8c16631e569afeeddb48f9ccbd5d2377ec3b95d8e0211d3df09ca452 +505d86f6a370e6631dc1bb86da9345205181f76cdf3d47b5905dfc503c4449de, 84f6f8094cd4e6d9fdbff34ba2eebe4c47b20d9a728412d4867e3c6b0027ebfa, c537f91cf2176785e6a687324e3c73dbfca5db70696432a0aebe07428ba475d6 +820cebe4b605852fccad49e89ccc86a352570ca1a324dd9a78631d237652fe7d, cb4583b7e20ba096706ce67a245f5ac29a83b706bc50715bc42c72f629da02ff, e2943d4d3c4672aa25750179e21c62ceb6a9314834d77753f65b6ffdd46c86b5 +1fee6c610b44df77cf8557deb5b7944248077411b23f38b40049fb7e8a60f438, e3ae538520c127315e45fb0a9d252f5fd10beae05df12fca6f7a779986e4710c, f2a6b6d10e82fe2eb9ad71164cf4003be2e34510c45a977349ee3600c387ea00 +d3949770c54f874687fe99aeabb170a4c60f15ab7297b0b21a0580767765cd09, 125cf82db1ad008c7bf212ae95d01cff58d6bf856a4c3e6e873919ebda8a290b, b5754e94d10c7636ab82f58e9af98b1c4206f3bf1baf27aee758103f4b04bc8e +2e72e73a96550d8c6ff0d2dd7e0a1ace0519922e15accfb7828cde87bc7e6fd0, fdd4ea21f55350e245590dab339942ce95ea2278e7988ab27a4c66e531f0e5b3, e328156a094fb1bce191e2e04100c161789195a5cfa536c9df68d5532ab07b21 +f6779fee18551108f61f108022353c51017b0b3898deb802482f21f9e717e6d7, 1f68db435c25f06aa2ca44ce670b249a9eec5ce43b69988bcbc28247a558a6ff, 23d1ef85687bb78eb9491b28a7fb1a5709f85fcdd61295058a4ad5ccb45a87d2 +5d19a19603f2f7b676ddc0572233b4659a2831facfedd1857f9d89144f26bc83, 44a7dd57190a7f74184a126a9de6c8c698497b6ebb2f70d75f99ba22aab55764, 2cbff5d0967655b69e49d6117616511a833c531c56017d58e816a6c919fd34ed +a6164d2605d3c5a76c17a81faf9e716c9c718ad6ec7f224d74d1a9842790158d, 79f51c516fa7e73e52b1c5baaf84c201a3f548ad99e0c3c6780459e4f8787871, c379e470fb7badef23e576709314201a025eaab49e97cf8f19d9a9caaec5f874 +0480bffd2334432f65da56d975189947a1c889028307b32fba08b2da06361fa1, 60f6b69bcd44e5ff31e3cf000fd9b80bcba9d255db4c39718412e62fcaf9f79a, 6511de28d7c070e4d547d27f0020298695101cce37b1c3119af1f23ca6f9ffb2 +57fbfe1dd44f954520cd38a301e77426b963536f39ea25ef9ffb0f924b9bb38d, 3a46a05690b880a09e1a5e8ce809abae3a66faba9fec589308469acc2aa81b7d, 9ff4d970b784f47145659ac1dc0be1a22ec5269355aa74e9a3e0f0f83737ee47 +a3371a9a82c3fa055458b73e789f40991207abbbd8d710aa96ad39c035fd4f39, 2d12223e30b9807e897ff984c9118031ced0e6632b98392eee1203c7925b1f14, 193ff8cdb817dc377ceb2f05ba9824013a403b00bbcd9d1b85fed8eeda452cba +3179ae84d6f8b35d796823e07570fd8b249246772fa6de00d79da7ca805ed2a2, 182f49bf739db7099f7271dede0814cd3a31e687313294ff028e73546cd5f126, e72eea743dfd933012b266da9ababdded34b8133998b0eb18e6177ef1750f6c9 +0f033754c8db71856995feb6266fd7b87260ed8b62f90479a9aaff496416e7a4, 3e1fb24dcfdc0c0771bc064312cb423a17ce0973ffa36445bac23974baf01ad8, aab125ef16c876eebb2e4dc1227fefd841baf14937294a29f4d3e9487acfdd0c +5d22ed58ac04aac43dd29a9b1b6bcf01abcdce46c0478b4c8219b1152fa952c0, a26d7e934d663fc01b21a7f2a87f9d6c4e37e5622a0518eaf3f74d20d9396dbf, c334d41a01ac9b3c89058e824b3d9a7f73ba622b6ab6da12be50afe8e5aad161 +d42190e6a46905ead29989e2846b39711770078bff4267442a5478272385ffd7, a4a1273f5d1daeaab49748a20a32b20a4df406952c36c5feabee53b2803559b4, f61584f3994c0ecfd0cd6692802dab68285f145e5562db2033202f1c24ab81a0 +6244042b8241aca12b0713b6b4f6130363f3339a292241723b05661da58e38bf, b6b8e9c79ed467e88523ece1a033ab3e9ab588014488154a0eec7af8d82778e3, 1905edcd02554e04aa7d982d933f138b257115ff2d11f9e53eef8ecdb8969839 +a8c9b906d8181f93990be961c3794980da9f43f2be2091e6a802e5b362579ef3, 7aae2bd149ede04c6d51d7b170850d62b7f0b7af8347497d0a33473bbeab2d18, e30711b0855f76550177348546f3f320e5760037bbf47e87d682dc9e907f47de +96ed0fcd5efee98b48c8c693e6e90f060454486b3827b7172ee59aeded9e97a4, 896152b00d3bd047a0a3a035b96c9874e26633243e945b0bfa42d1a88b4f400a, 087c7113ecfa43ab960fa760f0c8fcc699bac16438e8857853c6669260bde102 +25914744539f4633d221009f44f790bad23d021d72405a8ca981d82cdae429e2, b2ce79118037d0e96cb0265ea70556632fe23e6cf45d01c572b02216c6b2cc49, fa3f12aeca52f0862799ae46b30875ab44e155987f00e59b27ad6a17b2e56a20 +c43caf10826ff2cbd37ef25343041e36654d97071f7408f5952aa31a691265d1, b9540d1ecd88515a8815414f757e1d8cdd144242fa336daaa48b97044d629982, cc63a6b375092c9325bfb5ef7595b73861a5a714a5621f8e2cb76ac5d6a3aaf3 +28acc4acfa7861447dc0172ee7865058e4b0650e807ccd5d90619021221e145a, aade4c93c4cf893f05d0001af8e838d1460b9b2d731d097aafe8a03cae16bbca, 915950e205d758bbc7db92506f403a60f1cb01f3c4eec356540f5358a02389d5 +9a0f49950cba7712df8a4bdd8af656f057595d1cea80724501dbcdf2178b8fa4, 3eb4293787d40f7eea2bfaeafb79b7d73afe8c525a24d24faa0cfdebb815d0d0, aca77831c277c838b91b707ad4f1fa7e944608254529d69a798667d9d57e3ea4 +acd7984fa5578709b8defcd8624e0437fc1e4556ed6e635cd39dca8e070981aa, 6869ccc989fabb82421ea3f2a9f3fc3d84483d423545d348640cee1be4ae286a, 218ea5570f3d1e72000a8ce5d5b1572c576510717a86c2dfd7de6b5ba9f7a906 +17556c0da80581de3d4aaf527088400993bcab8c433b969fdb50ae00429f2cf5, 3cebb5421cc94cd9188cf2946fc8dab13e0397d6c94d3376c2c672422d9b064a, cf47b7cf47c06204ef6036e1ed28a26ae4303cbddc236bddbde8547743bf91a0 +9ad27082884403fa12889c89287c267cef0dc3553c7106d937931dbeafa23314, 3538160306662046f4e8e3761ffd4fde7b08e4859785d2e7462ce8bce14428f0, 3eb2996200c4fbbf206a09f28fc1b030e637e15979d72be6c154ac2685b43b10 +0050831b490db7d800f6974d8ee02ed8c01f5d495bd63b7503b2ea66adc4713f, 16da58aebe62b23b54e9dd3c7fecf6d88cfb44d61bf0e4a6128dd3c837b48218, 8d833c0a0da01bcc0c83131fe43695b0fc22e19446e20adac7a0270b6ce791f3 +459a6689f07312c67bf19df5f6e8e17ba7fdea8766f47ee651ed7e8bbe942891, 7902dc6c43e72d60d83ac6e5da6cd46d06f43a52cf77fc62545d3274d66f89e9, 1812476f801fdd1844c8a695827edd4473eee13b39bf5b1bd23b598d6e3cd8c2 +6fae52ad40bb44f65c26098628b0c4e07e380fb915d85ed144d4c37d224cf141, eee63ae8310ca61e6dd1bc62af1bbe6229c62de719a381e02738dbaf3cc04824, fdeab6e0f003e72a0106412f3129c3af09e22a6611c5c39424838e01fb82f309 +37bb97cbb35e3c62b36256213e6167afd484070605affe61a8c2ca4e7401a791, 79ab02eb919cc2b23c9ea35d4f2ed50f9773b3ae02e819ee449a5e97ca1bc21c, d165917daeec366fa2d104db9d3e59d3ace9eb6288b23a9dcec55221b4f4d8eb +543e09d6ae60717230f38c190779bff9b42eff4a183e4b6cd36c5a58396489f9, 2b6a52c794e84b57f07d8015c8a937116caf78d5817337d3a01e6088183694e9, de7b34b6fa7581b2cf90e48be298e477f709ce6ba995318a3892016deddbaaad +db4ac20bb0287e4942286836cfd5dfa5b623a1f8e2ed2e5eec4012898823e63f, 1173ec2d291f894d35f917a8a09c61a61440acc562ad9e35fbdb7744dd52c093, 4858e75294b2721078443ac0f41948962d48625dde7ee7687dec1b4d5a269229 +5ffeea10d9b35db9fcd56a588008bc092d3ef4bcccfdc69a2019605bc06d2115, c46005eba6d2764bfef3cd9d06ba6d2c6609008598cc0d09d2cc8568e55a3d05, 106e9bb7b9b3564073bd68a54d0a71e23f7c647c4c7d832df3eefa02b542fa08 +fa65ad48a9378d16306123be50084f02221088612e8459abc5643f8729e9641a, c54e7b26ac578a37840d43b075811f21ac752222a3fa97887de9e04c5b166594, a008d22b131e9ac1e2f68f65704ddef0ab70177eaa219ccf964c64efe1c4e99d +df5422b3bb4b3273a5ee44fa48423e27a04fd2ef3cdc9fd66c11c89175040efb, 35029d7e88839176ff40e0c25d1a9de4e8792944b4cab1475aa669f19f26f658, 467f2aa7cd3d46c88cf0f1a7aa81e6c7dec5ae43bdc840104854936e5a6a2059 +23d568dd7db2dc313122ddd5e05b025e28993b61a70aae704ee4ca16a62b71d3, 2ea09e6143aa43842b46b4721951475489199ed0b261f7aa677760a3a39cc95c, 1458ecdd053115c2a08902800d1071172d778a8d77cfb0b62674548cee862df1 +362f98ecad56c72c71eeb27d6ed5100e89901c7430b5c1fcffe81d536de56df8, 18637d9c4d3a77ff610cbf1b646c5b249f167d2f2db08129384c163b236af18d, f925e7881f601b4294edb9a94eed85ddc463553bd850a07a20c8768fbfbeacc6 +0d18259fbbd74b59f0300a9499e6cb920b88dd3d628a683ee0a7fe6bac475437, 08fc6feec5ac0dc2e3d8b5ba6154de176f53e818984485f1b1f4e96c426e6cfa, d29af198c241ae391fb5fe7b83bf168031010c030202ca76996ae17298487aef +bbedd62627b4dc692e6139ff02457ba44a7fb7b315143de2308be310b3e14929, ffa7b456057eae291af3ac33bad6f9079bdb73c69b534d811b952adc9feee727, 3e05c7bb1142e632659fdeb3133b9f8c48a02de7f4d01ca3087d75714aec929f +fdb058f869ba0e617a4f8ae00df5fc195e4441c6e1f0476d787a2f61235dc696, 059b892d62476b217215065d991969420f81579cee7f20ee3a54dcc695a45a99, c066cd670bc01d28236247f0a81146c5788e6ce38dc0e2de1e1ecf23c62c0b5f +74394aec20b53636f4eed20c95b9ccaeea3fa38b89d61f137c90e17b56407d5e, 58a866a04d00e8d2be7bf2af338efdad24483265452185d34d91e212ca16dcd2, 109ee356cea3cfaeab4d905712cb16aba419cfa778b3bb4ec6bda923a0a19e6f +e55f5763b84825bc507851a76ddb07974c7b87b1fc247377eb27f711ceb12b95, 512ce80786d31722630dacf8632179718f453cb572b84ce93548469f985b9f79, ba090360bf533cf299ef28297d03ac13465fb190f879298e93dd8850bbcc1c72 +cc4c0b04af33a92da24b75ffb5876aa31e09661f7011010ea5da3f57383784d9, 9a143db95872f1eff4d179da4f34e388e003d394d1709eea734221188fed1bd1, dc3199b00057d182b651acde2d4209827ad9203a20f2a05003eb7ef8dc0f0709 +0436f5fe5dcf4b0723d214f775552a22bf4ac8c1b2f1e05bef50f75619539c89, 68c48fb0bd0eb7c3252bc31caad79969329c0c8eba00959338d487ca699fd9bf, 771388fc04391956bf3e555f67a897fc68b1325767c40e2ec88632f922285705 +7e4f27cdfd257a307791d88791d6f40fabbf40c68e5ac43dc0d102ad6bdf0583, a152b4338d5ab02ab4be35b4f76632b40d78baf5fbcad74af0bae67f8ca5eaa1, ee52e9149fc7496429068e626ede82a070440be8151ddfeac1cca337910bf3da +4a717a0baf634d9e011d1b3506d0333101c251e23de82225251a966d8290cce4, 671c757537f9e35b1185caefb5be6e75f85cb0d94c57b764da322694c69f4a5f, 07d52a4ca4f881bbabc03005036466e987524f6689f44fc00e1cae7f8620492c +b7eb84805ba9dbb7652dcd9037f676b18d6e5f696d3e69cfae3f92a3a94ba37a, 2314292a9a65d9c6abe6621263464409418117c41927b0b58784b0616803087e, f0a59c979b872ca273d5df9368153a7c269833a0508439d63e2fe55755947a57 +6d5ae4f99b5aa7fe284e9faef6d306d14810398a1a307e72077b8a40c3e14169, fde52d5706dc4066b0dcf410823f36454cb71700873eaaab7267dbf2567e643e, 89b807868f5b3d798a583d53d816e50520ba333da9b0cf59e8a0aeb8126360a6 +3181d9ce692aa1cd028ff711a86240895076831022984aed9dd0e54fe1cb5fdc, 3eb9a0e03709290661a546003616719667619f6612e8ae319bbacc63dc90e4c9, bdb06f0b8a95c0f532241798246c714d947f5d66aae44ca9d27d5071eee3dbe1 +00cca082ef77854cd01e7687359e0d8af296b8af92992c71f8cbcfeed8429639, 5a56716bd294095a953cefeae30dafab67b6342a44f9d6d06072e4ab7079351e, 20482d37f805f68590a24fe14aeae6c24c3cc6bd350ce9846f9fb6d531e0f63c +b9eb6cf2624dbfcda78d693418aafac6d9439dc98248edaab979ac4369cdcd1e, c933c6d92a6f8b4d3019f484d80cc7ab502eb124b7e85a979e62fe1e0721a216, 11299696075019cc2a16a63eb0d3097a875f1c1a7b580bebb16e33a87aab2bc1 +9673cb86a894f8c043e5ea88e752263cc026a02e363f2e9560e1dd5a19586a41, f2f7d5154b35e5bba24c62d36a68f0c56b74bce9e21d0b0dcc1af0e7cfb4f6ba, bf48b95295d2b7be354ce6216514a79b80a993f2a050a9c487e1ba1e0eca972f +70fb81619914506fcb9e79c6c15c9bdd578005aa5d307f3844bf46ffb48402a1, f69aa89ef1630a21335a41d640346bad29f450bdfe626c0fb6944040fc0c88bd, 304b48ce10c3e4a0c4723d3230f966472bd22df8425b741248d709a9d4655dff +e94430ec7a2c7a2eb256c57035bc1a6172928041879c1423c1f472fd85e39e7a, 8d65ac79f5755be70a88f9b365a925bb0c591dbba0c2364d730cf9d60a7e38f5, 954287497ec1ca9cd665798a4020c92acb6a534c3a7f01a8d67d3539c8907ef4 +fbaf30dae64eb14b90abbed3ceb853456bc1935f70c37fded5af1b4d2ec0c8b4, 184a2ad94decbde0c8dabae37957083794e3f31efaed1c44cdd0cdb5330071c3, aecd0f48a508f6dd22145445d359e43f656bdf7c6ec3f5f8feb23a2369a604ab +597d315206d204dcdc04cec0edd9c45530c901d6e5b1b033173c6e3bd16b581a, a83a8304f560c18011eef0d8a970b3264adb9969e1e952bb0007dbdf213fe9a4, fc89bc3565e09c26a963a8bf455a5d8398dc279e1a85bad8e2096ea966ee8586 +3a53f1d1de15ea116e4cfdee39a98ef1fc62354a6952f9d9323507db428c94cc, 402b2f36fffa3097f0e25dd7c22edaf12e23e062a3748b134c687b07fa1c5f1b, 7fe1721bebebd91877e4ff6447d76373f0e56558c39b09fce693c93d2ad165ca +51a7f31dbc1c9f6475310c93cb87fb44070b11490d691b9ce2546d755c59d5d0, 8ba264bec75d258f59c5e33b7bade85413b805db2adb615be2e742f06f2a298b, a0a8d9e7c46d8cc264e210ae2eef80307bd8cf9be8f54e625055a8bd3de49929 +8e460d24e705803fd9f068cdca2c5c472267b7b8bd3f2011ef0de00baf530008, 7c41c41b83da1779f343ff352f9eecc59caa5d50eaf2cd6957eb454d5ed7ddcd, 161a369f09c75b050b0d017ade6cf7b6417c6bc60d34f5b63b810ce73598be06 +9f23ea4bb7ceebd23b8ffa5987d983496980a2e497b6b132c0fe6a7d05c3ec9f, 4921b76c555d6ba02e67136e3a5f32d3c8cf6996a27f8f02a21b72d346d7dfd5, 088b37e720faaa60dba3e3fc8c0d8de060ef52b3c20fafbd95e4483cf3a7e909 +5968c4de23160b1b58fd045f46d7fdf13f946244bd8fca57b8f185e50911f1ba, 0303d31561b4eee05b9486663c0c85bd45c95b0597002b317c660a1bd7f48b9e, 378e0fe1948d67ed4266326ba7946f4801dacd390c9cbb09570aa8d0a78bbfdc +52507d1d8290d10e321fe2d751a254200b87340f37b6dd01f3c312f421ae223f, 622af0aaf462767c4d97bc07761a184a97bcb60f266983bb931962e9819bce99, 70257b70d9b0f3d688134d36ae07f5a7247eee70ebd8847baa8c49044a476184 +1b5f0076a48028ae80bd4a54001fbac6f65568cbb4e86928e0aa99e4336de6df, f61329b71e74b509257c15e5e242524ca6933badfec64eb46fb4956032d9ee05, 79cd24534899c582e0590513adbd60a2cfda2041ec0a436fe4d0c89e45f37376 +633e0186b3d0ff9f7090cca02ff3844d0b07028db1d3bdc71ff10b2f876ee6d6, b0fd8d8340b9e93c7098d9251b014882bd005d85fce2e57e0a906d191b5c3fba, e0da694a1f763ed64288cd1f014b09c8bf130cd9b08f86010fb505c0b1cdb328 +5418b360d1f69a66b38c482f3335e6bc75d36f44ec03976f47982162feddbcdc, c3e03bcb3aab0aee5c434a85e32c4a203e8b3eef9e8c14c685d9696d67d2bf6d, ab565d731ef678b03ec8719c062cffc7acbb0124d9e2c7550ec3113c05133f6d +d89c082eb4ca0357a2d6c818a9ff7c68b40c4614ccd963ef4ac3c7f34021c190, 591f500c9f0dea33b70dd77593b4f8dfc9b57e4045b48d6f7ecd62c239fd63d4, 8c823ff17948c6d6fcf5d0b8fb6294a23971753e5e45c412c16a08e23fb93095 +c1beaca255712399a2900641e9fef783e5b75bbb8252c9a07753baaf11130835, 9db95b5e09e42936ceb9549455c42c5867da36c0c39d5a5b68583d1f83cbca75, be5a980ababeb83decc6fc6f2acf90eddcab9080f281c7ef5a5cd4c128458738 +8070a828a7fb1a5430fc7bd17849a47a52cf917bb8655cff43a4cf9b681f25ca, 8da403682f842a8dde66c43b9466881ce0ef375efae37e61d36ff161049c04a0, 3b842aa990430ae92544ab042e525e9724c2dfa4aa6c309239c39b3e09b9bfe9 +28e0f690aabccb40fc1645d1bc823afafc6cf3ffa8799e091942448f0827c697, abd3118b4562c414ebbb17bb9394c7c8a64211d488a1e03bf7c9889988a5a850, 0322c9335d98556a01cc687ef4a35f843f491428a5195b70253dc7d928c9e8e8 +eeea29e26e8e80f5f3a56d920f9425c3ebd9c67a947c34bf6a5901d7da89675a, f68f26b6a17c309f5bf24c0b94eb200bd9de1820728fb27dbebe9fe3bf88ecd9, 95cb406f329aca9fa08d3c3ad6a24d29b25ded3386b54eac132f9622d14103b3 +72c016f8a5f4a51e9712c0484c855e3741096c432913df3993a0c801003da600, b574ad0be01921963d95e6393f5912a3de5e6a4902b08bc60aab3c8ea2a6e6f0, 3c77be98844c0e84cd4100fd3013478c0bd86c5276a3efdf6926b821beac98fd +7275afef0649a86a05ed4712bf0a1dc41dfdb72cb6bd6bd938e52ccb90c968e2, 3bbf2f9cab4b20d80a045badd49594dbcc0580d18f2e20f74a9fdd31cae50c4e, 49ef88621a690536dceb8d6c90b2ca638388286172684c20d6ef2f81b9747ad6 +c24edd02b9b0a6ebef234552cee4edf71581904054cca7f9643eb09aa9480e69, 9e645ed997fc2e5a7bec0649f493d40bce63cd1d0af78fb0bf696cf9b5a3fb78, 7a1ad377fddcf0cbb689496348f3c17ccfcb4f1117b5bcee2bd22ad0dd104fdb +4ea58c8dc3a2bb21d73b9d6abe248e4a448e7d537e3ceee22e13c7c155e3eb61, 17a2988a2c62a1bf9da60ffdfdb02e8824bd8e8ec0f7f47bc5b3c10e3c7d2f7d, bb75c34d78616c3b196fc2dc2306567b9d1fc17fa4b73b224791fbaaf2bb061e +68add6b03a43d32f0e8145792c82e0d9373cef6731555932b19540edbd674282, 0518eb13d2701b13a02ce0f3dbbf2c06129a45bc1fac2c4c2ed9af380b0ed48d, 6234d3729d42108beafe14e3e22ff155927e50cb45cfc6981bbd478758ac8726 +de7d84ac20b9390a746e8c3776da618fbe2338796bba1743523c4e95a27e83d8, 4bf2b17772f32d1ba39affe17aaba0b7af0d6b41e0be94e225a95567075b5280, 701e88c41146ad9a0b1ff20cbf69e20630fd47d6e656076fac9148675ef6a8c5 +1472e2f4e39d3c02bf9e460b532654bc9045d3a27b5714bb63a03ec7ead1a429, 19cab0cb64b0ee7b43e2db9649e9409ae50a5fb613b26e5d1f89ba9fc2793e70, 97851c49701d1cd707f3b406435c2eeebcbd69d56e42d421ead0482c95506b83 +41bc084940ca6d380c0d47ddf6c1421e22d665ebd8f7af08e6a99319f4661800, d44e20af955a477c1bc1d56a1a0132ca17fbf71003ef02f181b03cb38915e5d3, 85a1530a267851886b2abbe3ac9e40c7a6ba23d350e9de5342cd84fd2682edad +b980de4e495af58c8dd16a7cf12aa0e5063dfdf87ae9ce183a0ee26eb7e47385, e9c2dc046e8b9ac75a48f30eaecee5dfc86588b402073a44b69226c3fac90f7a, 197082fdc651c3876b8f1869f41df380a1300a369973037329a093a4a41baa8a +a01f586bf35ad81e20a8e47bb476ddc33af6c0dc823457dda5ce7a8d137b39e5, aa754362197b1c40d915d6ed7f828a39caa6db842a149235cdc3153cdf01f578, b7a32f7e268c7cb56249334650e7eb54f48ce9a5e4e83df1dcf3f7e053fe5886 +485556be8cd49267519524b0d83a44b970275b528fbf793dca8d256da3f2ca41, 2827c58a110854fd9006831d1200aadbb340efea30e447ede7157edc2badd9e7, 7dd2ffef503101c626177e0debe07a42ceba568e1695d5fcd71ecbff64fae7e4 +abf6921a45b2b582e3734fcdbf0d62c149761d62854404405c917b46f73ee90d, 6153d42edd3cee9fe3a53065ce88abf2ef6fc0e108f759dd65b2f55ecc2f2d7c, d45a45030b8b0e40a4dc74194dfb552ec37c4e783d52d7f630b845f349abeaf2 +88669b27048cf175e2cabd100e9ff8dde9670b9ec37446467e4f2a580c47826b, 6a4e1d1478069b7680365a8a9bd85b814bca16a8d5c6f865d740f5ececc8185f, eb59d311bf23419874a202b54c46461bfffe4e8bd6c15926f89628c2e490d84d +b326d1bd335cf51d518e89442539b1d7a318f3df383d53f9a82a7292c19cb02e, 6e688c2e0394f442036cc20ba28ff013f2b476f62e927ec7dc7fbefbeba10ba4, 9c69db61203fcd59faf74ae95d827e072f719cd3038e3702eda266b534be61ed +5e41838874ef79cb7de85b9b48ec99605af65213cfc82d7b80ec9c647ac1b10d, 5c330a16b81124e37aee8357226ed1d0fdedff9162f2377dbde03863720a4657, 44f67e4c63abe2ed6ded455f47778924333dca0f69b0e1de95879a930956b411 +af24c594268434537d3767ab8f67cc37af95c6ad5021860948c6713b925f1660, 79b2fb407a16787945ea8e1a5524dbbec192bd834e799b994bdf1b6d07191d33, d660eadc2c18768bed26d927c914b72064c072e07e4b8e8779928a3f2c6a2926 +ea7b4e6354b01488e3258a6982ef2feb9c12070b39d8f2edbaccc9d536ea80cc, 3a2b57ab6202f81a9c07773e3958ab9d70436a6f732f3c9579e9f59b8c70341f, deba5b6859bfa26569ee62d0eeb6ffa1449ba31683d974e2f7f7c6c72a28aca1 +8cf852a30ab9cd45d94ca420a9fa02204e1b5c2494b0976df92b1fbd8f8516bb, 0643a54ca470436c07ae3d67e236dc6fe31efc83cc4721673b5491c18b924e04, 92fb3eb56464a6078f4eaf5dbcfbed038ba472d37e6f75bd485fe8a0dc8d37d5 +fcbfb7d7cdaf676e18ee4f843817d293419cdedaa77efbe03fd62a2999c2d76a, 76d988ac4082afac59f5265ca3f706fafbeefe0658e143aa5f2b15bcdacd876b, e4ed43fe179677ad6d4c0d32e2d85b3906b5ec9076fb7feeb5bca8ba5b993cd2 +402a3f285db283d0e3c59e4450486eb64d1238e0af589f4e4c8ebacdbd1a56b5, f87e645b9ff1b1632c6b7548c04dd5afbea1f2f560521b2139f4dc1b10005179, 7380bf93c84196d154dddde22240ceb09d98ffbe113f6bb738728216bbdaed64 +ae1cfffe8bc4d282cea653dab13201a04f4698572b8a02d326828d3e06dd166d, 929935e23bd41e40aae8895f245033d16e5729fa3fdce74d9d174d263c79bac5, 3540ade4e9f5bf9ad7f60e17a89991e42e4233d2ba6e994287b822497096b6ba +eadbd91d5d9d9f096df439cca2e6f172518eb2b5d5060ef71690aee7e3223b2e, 50561ea3a6d31c2eb779c178611bd293e34eb23b7db895dfef60e9549581b4eb, e03b13c51e3250be5c2e0e55bf8b6ec6359aabc0789516db14549976a5071c5c +7fdb47965480997476070af1db270a87d60d55569296e1a1e421719b45b74768, d46586be6e875fb6bcedbfc8327310cd7a6c0b56e956d32dbf9a742c61f01f0e, 75a8737f8b80f267368c2cca4e20a48e9530abb9fcf115e6dd6d9f94916b3c07 +08bbff7c8ef00d2e076501be16d0922d3b129f3ab0b8d741c261c7dd6c35b2b8, 025b8716b09a1719573f9c97ffe06f041268665b133926570727389e1440ad03, f91cb9ef6c9982ab77c5b646fc60bbf8d49edf00f5bf53605691ca935a79ade8 +ddae3c06cb4c8ccf3ca262858ac4481a529d686fee05169d8799fc0e8ec17a3f, 9bf2a2b6aec3e5fc0783be7bbe5361f601550e08ed29df6b7da13773b44dce50, b775cfa3e317eedf933763421ff5dea62f40f60cb9520687a1c978acf24095e7 +21acab552441b39702c6a89b91aff56eeb42b9931b63e70e194f52ad8be0ae66, d2bd1210079a9a666267576677d08625f63c939b982f898f7fd55d8903caac57, 1bda1aa998fef34758dcfd66e2495dce73ad29f7fd2b4d9aa8160e1c9ed1e240 +cb2f3f06acb1c4bbcf841d724da705b87e81023a91827feaba22f7404fc6cf47, e2500d3ed028060b6e6db2688420d8e31e1ff3b60f9b385af95ea9add5a8b46f, e3b1d14331f60c2016a5de295c93b8932f67fdfe2132c17006168286b6a42b0b +30a47f9bf46dcb8e0fdcab70e5c5cedb6ab27a64b08e8fb226447d9f44d7936a, 2db59f34a4102fa62ec265e0d5eeec0c141f3cde6474a9c290049581f7d37764, 40298e44cc533e7610435aa258c52c20239f95f220c0cae9c4e5dc461eb308d8 +8a888a86c294557d369453b021c31f35f5543d2a9a3769385bc9417e640b1099, 656681f41b1fe353bf9398511ddb01130e05c37e3ddaaa6b2953a74a2a5001a7, fda8d0cb52a75334956333e0b8e0185e76cdd953079b3fcf21e71e23329753f9 +5f1fdd6e66c926c48c33a5984e0a5412e07e417007a039b330a36fb0f57daea0, c1ca35147ca722f34d0cff157932ba3fe91f5b9e5677bf485910874d33ea88b0, 5bc0ae2453ffd52cc3b5a4b810f88c1ed3741e98aef2d467c55c98135235b35c +26b53d01748c35a4758ff07268f0dace6cfc414db546521956427715e7c8f895, f2287fdf0f58fdfadb0b9e8e0dc07354a35e9bc15f502e16d6260eca8f381bef, 13bdd36cfe7ce512b4c1a00ba18267bb3c475e50b21f9219cc4433f1c7080317 +c8f629fffb6b62c9cf01f34821686ea71095223c3bcdb01f80c30206829f33ec, 7be24ba6a3d06718a4bbf85cd751452d1253a2b95e1299e3a537ba41fbe0b32d, c7b93428fe69fd53b550320e25d397f2477d1e98d1a405d897f699c894ac1df2 +b506a4156b669500c6a7c99baf289cf2abd1ca3c446afbf74c63a49e5974f4bd, 3b1e3d59d41a06adb865ae0b37fd367fb88e743a72f45e7f60a03e169271d2a7, 14442bc5416630672a45f11fbb8931502bb8764266d62c13169e1668aea8bc3d +45bef5607684b17ca33e72bd74996f077b2fba8e7d17e17a2a80af5aff341420, e397e3a0c5b237184c948165f843ec7a1143c9ec9a58a8c31c4043425051ab55, 48b4b38563f2cf8da6e8143e2b48350c7ad8206569585b88e28b673e0e9ab5db +d4ade68696428e5c813cef1f12118127e4b1c43506bc4e6c0d8f9d4f7e0d4f00, e25e504ea4b59a5c351cec908d6b3b32ecc3ba65bca5656d91ae98c02e0b14a7, 7dec9645b0e486d05039baa69d90230cfc4abd6f9c471b2e45a6da2fef5909a3 +1d945dbdc26b23e1f8a1fb7390d8e4dbf8e130e05505fbc975d11c43ecf5085f, 528970e1cc786d2a488f72f9c9b88797cbcd6d28479061fe55d89e79c5926e52, 002759cf969e823688f1150378b3527da6ed0e84cbb47e33c428d3c3d2bad754 +647ffb6a245794fef2940302e06c0760059e5198b51b034001241df3e98d2fc0, 0bc92a5f83cab25ead777bcfeb6a247da60b785fde2d18d3b96a145490a28656, 8aea9c10776e3a341dc0fcf989286ff6fd2e7fc4057e72d27ca330e961892a5a +0d05d5871dd29f66fa48bb69e2bb3e797a0789413298ae4eedad1f0a6cba5019, a404e449148cecae4a9014baa8e2cd772e96dc61c13484eb285f0f7b25125609, 1d4c59bd377a2c8fe24e556a081c646b69fef8f86b862b1113d8f007f67d16de +0a019a626d26a5902c0d9c160746c2ec341355c341b7bcf84d269ebf420f570b, a4b996e3cb88403e0725db0b7925b5ffbd857fb016fa9b73081d540c3f94de01, 015c84622eb5cc4124f97541c5322aa8aced7739bc234aaf946a6f010157ed9e +f121c2798a64d11d3cc546d30c0e2ccd8e25ae9e3d03132dc7866e8953ff00df, fd835e3a5ae70e02a2da07d093776faf2f89f8de900eafad384232d09f968aac, b1f80e8e88caf08d0ebac44e239b3cc765a040040be5c0be38e6a1fed714659b +7ebffa3c2ca187bdb028c7ade214697a16078399f2629e172cf7d8db560a92da, 55331b2a8cbde1132ed4959279f47df2b13c8697a9b8282cd8cdb93124c0f2b4, 4329bb9ee05dd211f371d83f7a3e74c0b0ca8846addcaaa877aacd976b3bdfdf +04f9e8e5665f3ac7c1ed17c5d0ca1ee660d1b92f8c6d9dc5c458c08724be9a5e, 14e1d71ca8a81e11c8e9d6ebfe5b36e3ffe3a082f4b6cd0a14b54b26eb0487e4, 3c7da7e8557ad6d49092b94f55be5dbf57966ec7835c5094b43b81657a5f5f25 +4a221e734f55a12d0e0bf837462843f52710529dd521017c693f16a70efa7a49, 038911cf62c3351d83df54cf036ff44b5b301c724f8b540e7ab489aed493d2bc, 1a933afc814259feecd12f3b4cc919610fc859cf1fe040456c8818c8062b3b9b +c47bec7c3c28c1b8ef39236129d9df48ec2feb8a6a97a382a6c05a9ce2ca5afc, fe1bb9e15fe99593611559be9b4fb06177d3f38b882901c67aff17c6279e44ec, 05b5a996fc1c9646891bcf8ca38e4e5f18f1192930da42d4fd44903095d593ed +d9f46859b1e4168e1ea16047143c662006b566e7d9d69e3068c2caf591ca4de2, 8b4467e140a7bd5b24ee9afa571f331dccd0f586d5849b5dd5a744833b3582d6, 58ea2a30109226a40f69ba211556bbf990cbac1d921519eecc183e22fe5cb0a1 +afbb27c301368fc286eed61e12db2b085d0e9abff37d4aaa4793347807f7ede5, 1b2a19219f371a487de07e7b87118e6a9d46257728d18fe0ea062dd4ec2f05cc, 5229c4f0f0747b5bf44a0da6524b6e4ee46f0e3e65e811d78f8063f6c16a84ed +75e73a56dfca1b7e590dab1765183c9f061daa2e91926abbb85e3268b0db3398, 984b7a4af0e7a3058018e9f284789386d51fa9ab33bd98fa1123f681878c4f40, 2e8e9e38bd0c2bbac69b9465327117cac7a6d443db6d24669f93f096f5708687 +32bc273001d41d08c88d6829d26fb8b3de339862ae66f6a480051a8b01792eb4, e1677f2b2869073be9f1f42a07a9140d4937da4bfdbfbc3357a0f145e3e6777b, a372c1a8bd277616d11caa60ca1986592c82a95ba963fc508d91d568cb5a3566 +fba46c806acb376cc06fcc03569e7bbd0e68105fd217041803984bdc1aa48ee2, 65073bdc69465bf2f352b4db5b5950d259e8c48b6a616fa5d2f4b98bf51fda4e, 6d6ef46f0b6901aa2c360d8f67ff97fbc437fa0bf934d5e54e0fa33c336a6358 +7b2017d23ee485e9bfc6fa11f4fc05c7d8653409e58c354ab3b8dd591a0829e7, 9471160b96737f09d61c91dfac0f7059715ab559a5ca35604ac2f380a6e553bf, f5cd520f7c868d06709234327aefed65e127a48b52e8c3cca2b3664acd6634ba +faa0abe2fa27a4c4f45e66e0ffa80754d86a44d80f339ba9496525aaec89bd1a, 41bd4e444c083a86b8571a079ab78736be3e73083768dd60e67a4ebf9a35703f, f44153ac1b3d5b18c3755b005aee159494b77304a573ccce67daccc6db6af52a +934e209c9b090c63e544dc1f421f89cd2c87d8fd44f8d2a4b5da090db9ae2da8, ff6d4903a2edc1ab341a90e7fb12c1b5a0731d1f5bd7d26b214b10dd7167c169, a530d1ed0261408195a198f7ab7ee534c77b033600c6245ad3f73afab7ae34b5 +cdaf09fa4a5c39098137cffc002dd62a2e7167216eb527192642e03192413480, c9841498395cb0a73b31daa8fd538c17ea3e2d448c0806288e7f85226e2931be, 660ad4f6668f71eb6c4ac0f485156c0459538a7858211f92688f4b5393fc7a8c +c8321157921042d07f86def117732e1c1c4c9ade6e81a2a833ed06f3a28fb7b5, 2ae274f4ae84a36cf834e112eea1db380d852005cf2875dd29876db3e5842f32, 0590563557deabb7e918ed0b7277d83a1d41bdd9a91c4fa42136e624df374280 +bda3f35f8255cff405a43623f4af37c683bb4dcb6660c0a218de967211d7cd40, 7030ac7b653b2fe141a283ae08f43594c74c0752788085376eeb2b13529fb64c, a83cf90e3fdb6093e4f57631365f7b160bfbf7551f892c7f9b16d0cb70e885d8 +a2c9d44d02c933ce70ddeea7b035e13c7577deea00586fd9125baa3178376efd, 929049ae9b3c94deeef4814404bda33fc43c1935b51b58dc514eabee987d7288, c3d34c90c3756f36edc1ff16da16d00ae55b539b3602c664abb1a6c46069c482 +59513dc4562f73a23506495d6b71742989fc8f50415632136209680fce45c97a, 033f77f25fc1602990a554b5509d9ca1b58de612369dd605785074ca3c3b95ed, a5da3dfeddbb716a54cbf1f90f1c60ae6f572f57c545ae599101c6baff14964d +f4588b9ed1a03e37fbbc53fa0fdfcabc639bb0ed67d4583b428a4bab44e56a40, 305d8e4e74ba94c879cf93d31743ccc047d8163e9c772c63565a934f9957cad7, a96805e7ec9981d92d8cc6180a664d22651fdc763de2dcf311f7b02f3622972e +529f51fba8275d3fb729c5ee13ba7a8fd3943f8d075c0c54022c973c7cc6e8da, 1561ef8152b029b7531ea1d72bfae9bd1572f60878703dea5288880294bd6744, a72f86d5f63191b879ca027dcb3dfe3cb00878ebc2d1836416e6ec1660fbf1cf +712d3dc0fa21ece04b67392d797a985785efa04c2adffe9787572e44a7588098, ca558bf1e7915a4855419b9719add4f1302850c857e01459145464137848ac3d, 1a155a4483c84117c80aa1fc7b541ea33b660e639a62cd398cd7aede03728d3f +0b21dae0f056a1d550a020d30e621cf68034580a2ba252ea150fe1d23a64623f, bdb5cf1cb2ad7f8a0eeb83262afb346d741b84f993b612b6246b8c7b996931f5, b793a41dee59858518be64568ed1574eb10dd742d4811ad42a8c4802f8787dd1 +5afee848741c114856c5f62b84d33448e154d05c0ea7394e3fdb694cecd4267a, 7f9994df6767bf87afac9831dcd5797463c1af6b9264ed6e16323e6db4da6e44, 20e996a751075201d9e63f9cc9fff39102d94f60d13752ee44c0ad19c57bddd9 +6ad392694652213ea251a74577957397ec692252cca1a6ecd9c24adf528d85f0, d3b4dd153b5c9b6045c1dba99008f8822a7c8da983a7538415872aff9a066861, 3b111a97e43f1fe8d1e29897b2e6d2cb365499b8447c8bea14a69f94a31713fe +93f40d01475ce24aab5a323b5613a1cf3bda486bb5841e969ca783e6ba0e8eac, 7934556b461cd8a7618b64e5678c29d30cd08a547b4f445294288c7749b77239, 808979ed4c982abbce7064d55cea1edbf8fc407d3b2ab38ceaea5113e550e4a8 +d5aceed6dc04d050af40ede0d7649509d2b8bc5e1fa07506c1adf4b54ba33508, 664e653fe696b8b0d84bd72b74db422142af300b6e5dfd935d7d4b4b4e7a7c00, 471880e2b194dcc470c8dcfd11a39fa436653e7711dec6f3d93ba7849719998a +d26158582e47ee60bcff376841cbbc7a12895531de747d3f7484dd198d9f4903, dc652b53b9ed346aa1d37615cd494ff4c0375211884e48b37c8eafc1a8d3cdac, d23ac8ac2d8adfc332003764e33c17c8391d549f41d381276e06859f6ce7f57d +63c1e043bfb2903d82d974f14d9618dbc79edc0ded2a45eee119229ba6cda4b0, 87bedeee778ced1b24c0286c9e55dcb8e8a83176cd27b5c7e7a8d14bd209f09d, 58283210754ba4b8845f8def8f3938baa0f99e4da07c18d5721feafd2a0edd56 +d1736712ddb4ad99435e6f1f38a59ca727f28293a72391f03e3ab119b3f15fa5, acafb7f163d33cd127efbba1df6763ed8ec75e6036627cf35e30aa7cce6cabaa, 11ba2ccc8682506c4bf9d9028ba95c3e21ea7648374e7d5a277379d3266446b5 +702f3c165706158695c2dc3057a4e56f8475943b9f687380f4468bfbf36adcad, 34999a7e30d40e22c51d167c5faa1ff9b7828614c41acc46aa2d7354ef59a3d4, e50cde49232d7e2912467f426ed026678e8d76e31fd4dcdca0db8014946568c0 +0872942614e024e06ba3a560635252f7b840e1b4a471d17483bdb85bc9b51610, aeb6647d58a3b0fed04a84b3183e295ceefd92be7519e856b18a914c8f4e6cdf, bb03b0aee1d24c7570c42e41a732b1793527331b9a00d2d77038a8e24d2072cc +ae218ec74cd1feae5f41be0cf1673995ff3e58a1b9c767b0356288b2bb101ff4, ccce0a708f4b9d898ee2b9e31bad941489323c5b149979c2bc8a0e7a05794ed9, 194cb6340e4d0072bb6311b391b53e7933b1d808c59285e237b1e9746eb2e8be +ce913c952a3703439082c9af9e2da37d102b9845b2aa5f9d5544a41b7b0e26cc, d3201d98f0dc164a38f3c3f7e9d9d5bb3a3768ed84f91512881e33997cb0a089, bb8a9baf082cf8fa2b88b104ea923f270b1e5a37c58929f7f4c2f55c4a55c3ee +699a97c1d9c554d5cdb5bb5671e70c7c84d5223af3de7494e70b1e8464264789, d8e4209067cf0c302c68145260fc710290b131e8d5914ffd22372d8fb0ca8e52, c00f6d0afba9a3e5fe6f4751f58cf2898a28a79052ef2dd3c876b65cfbb3b167 +aaee6338871c4b68bcdb864648eab6b8927ad94662a309d102df8927dbce8797, 5e4721304d5e27be7f955b804da52b10d7dd9d0912576bf9b196ccf1d2a83a2c, 310a225d5107f8ba226356c95fa3011d1a5ec6f19395cf3be9bc0b436f67d96c +6dec773d0f39256e3c92d51fe097bf9d3d5d8d33c8972b3e943f2bbfda41573c, 1b1a895d75f98fa046f8abf29dbb26ec8825375e90894e112674d269d8f32977, c8666e340b055c73a7f350dcce9b674630b7aa809079867e7c38928e77787edb +1b4624c833cdbab11a04045ba8860e51eb0d56bf98776152c7d710cc5a96b321, 2b22b27f0e34d599d01b41101bc482bc64958e49a4679f00b0fcb287833c3baf, 2b633a7917f819b3ac8eb8b1819bf71293b58ee8a0436a4f73b74c3149d7eab0 +15fe677b3bcd2bbc844a808b853323dfa3fc207d00c550872d67e2b5c046e4e8, 35959c9f4c5904232b99f137db33b015b7b04808e7cc63917ea25bf38b52b517, 01fb46d1dcccfe68a7b85674354cf9af91a05396dcdce34ef4ade7fc4f029c3d +1375e0388b861db01088cf568e435af7a4be600677267a8e4ef2f914e85c2dca, 2ae22d36c7e32d2d5d8150fcf62e24b8f13b1ea2b443c5d5ffa03ee443edfeb3, 52ad6e58a7bd775ae53e69bba1a1b1c8fd76141d28e8f63791e23e2d9f04efdb +e4dda0c911809d1a87e734692f482b0915d2c642f31b4ce4a1d2babacd7f659b, d433ea97d94cf2ee1b360302d91d8214371ee4b5639ced44086c4476161b9a4f, 2c8086188b87927fcd37484ead1b1c764f6654d67d0d6e60eeaa6e25266a018c +5f7d5a266058495da66ccd00053ff54fce52099885406241aba7acef59e61c1b, d396ddb6c43276f9462ed525d5e20c9e2b971affa71e3c545458cfd4d186e540, 508102349054305b0499344d618573bb659426f020fe56cf4ab9c05ba0fa075f +2df2d80d35549baa645f1d00b7c807703b7d674a87b42eb5947ec53c4326f2f2, 9c13de37e48a44862fcb674030a4e583aa063227fd69e733a7fea85d2fe79a76, 526649e7572b39929703331c20ce2f7ab4a573815aa87c9af549e482afc65169 +942e59c122c3e9eb78a89116c4af3334b30783ed6f2208428bb28dfc5b884ef6, f41dea01f312b14e8696b09f3c992f848e376bfcac02e3eb81e7a6837d7619d7, 7926013dce33ad48d8ce88a0515ff85ed5be96fb0fcc507ec0487e334d211394 +77d7582120c340dd37ece7e3266abd92cb911769b28c7df5b4947e2e9a476d42, 5104463706807551de63be1f625a9dd563e2ce05cf778cd33c7731c8025861f8, 98cf6291477bf62c2e1c2e983d0b533941040090809ffbd25eff48e70e8e2452 +90ece7a45a0f68e7f7940beb2f4bb372cefe67e26976500647f67bfb725fe454, afd3b99deeb04dcbfcfa336d34c9305cd7ddd7a08fd262a5ba0c3cf795f3f8ac, 04651e1544a0bb6f60e01a29aafcf8def53468a9dd9b616316ea814ac2a9b1b9 +15276c0a5c6d087a02dc9014d20c49a94299e3e604f2eb1f9ce45646fc6d29f0, c798aa61370b5c450605c159d0e16054009b0a8c2fdaa3cf422d862671766844, c31fc1c9b6c0dcf68a33b6928fed0e477d3cf85d4d32eac3d707234375a7683b +431d1b7fb7dbc4a505ddfa9e5c5dfdcef474ab916503b42c7b477e86cbc31303, a761ab23fc207f6bf9c160f10bc7fcf343093023f8e9f6560b84dd198cebcf43, 68b1b778e7975b97ee9dd32bd733f00ce1a1d59206b7077bb0ea3d3aeb4345f6 +226112a180634d338fbcce761c3d23aa7a1e9fb2db41bc2123baf2f281ba8ea7, 6ddcc6d743d6ad877c8b89b8c5b3f572f971a1a7e3b2fefc12ca06994640e565, f65f2dc844e9a40f737459d46bdd48792572668a201da68b2b8503491db874fb +59da1469827eb0424a2d52e9e36328a508cc603fc80b588f97768d249155d991, ace02e2ddf2c426d4ca68fe516aa9d20d99e3af50687c1f5860a0b657cd2923f, 795eabdc88ea5dda82e1ed929a33910df0b6dc258b196c828d4aad998e90f193 +f125e767c6de6496b07ac7792d60d966316d9eb7750a182764b98be22c94e8af, 20ec39dee134a628f8f1cf5d58e1393bb3872ad05178f103b0a64accd2180300, 0f3881d4449a85fd1e036a5139c26de960d74c4e7f5888d3c07edceeb9303e82 +ae6f4124a53d6d8ada514ec9e0910aa41d1dbcbbbb69be0fbe7dcc53f8f05e36, 16a33169d5c5141f06b64d2017e8c4b7ee2d4114ee2e9db5252d8f6cd857d9af, 62722507184889c50f789b6f50620a5d3bdb747d01fd07e050b84b18eac0e876 +57c4f2308e837d39e49eac0cd011ede8a81093cb259d3e31cccc6f3d2ab0de2e, 03747aa040ccbbcbdbdda10dcf821a220718a3365a31ff2845a5888869c54497, 189c1a6e9a665cbc230998731911380bd2b16530049d0d54a63ab31a985a89b8 +614f8b913d12bcb6330da78c448ed7a75d6c9c09a81693c07aa37694cfe784d4, 7e0385f510d6aed52d8e6790a2051b9f2d457109171cebfb2674101190749436, 732019d9c8c2b0d4da2c15d90d88823e93a4b77c2f6cb34a1840afbf75995e35 +b7ed20307effe2a799824969bc1af0d1623d7c6758625764e36c93d0efec986e, 40f8d7f774b13b773ff070a05bca8fc516a4e3ee17cb300c4b9485d472cd8b91, d289e375fdbd885baced7f614f4f38cdd76a3b2a63f04f06b3ff782cc5367cb9 +e7d1f25b6033b28e11b821b53535d9bdbe996863d11d4bb5ff81895b1a7b8963, 4a41b8405a40c24847e2fe6bef93c5fa74b980db4d377387dd6ce5e44ffce940, b23e0d5bbf83643e8f3d4ebb7db897b000e11ba427d086253e5699a3b412ccfb +435b9115f6114e76ad27936cdcecf4780c9918a1b228ec8e4fd400b6697f0f7e, bf7ef95b4d2b3e15f7b7df0f23cba86dfc9ced13d0aeef60da80fad691385d9d, a7f9b7dc3c1ec2d7f0892d1eee31427bcff90846dc2835bf847fcabfce9413c0 +ad64da2e4d7cd5752abf10e5998f794abf3119a5ba23413f3f1c410404eec112, 546733cd48808d70efdb6cba3b6cf22bcf2b69540b88d476c62c8d24b23a0a6c, ce6714cfe02b5182571e1a8d0a2828ea83e76ed30b1f142e3a6b7076de7aec81 +63ed580717ad3ece2864cce83b89cffc3dba36f09444f3591eb956e03af4f735, f0c5ba7102bbf8f653452c318d80ffc4a67786985510ae748548e1988d384fa9, 0654eef367138319e462389a0c6004c1843564c5df6ae190f4c3eaee501919bc +f048210201ad9aed711f0a1006d978b7c02ede5c6fb120fa789605806e77e4cf, 92e913399ef60e9fb6c4ada6500f1f3ab607430dbb59638ead8be7b6ddf5c20a, e60504021df667a5262431c87e322028c9dbc078433c82b3184e3835f061a693 +c52b1d309e76e8c0c498f29fc2462c5721cd928b21ea91766700be50a5dfca46, ea6e05ee310348d53b5171361808e01919b62966775bc18abf87652bdd158c4e, 70a44c4f5decdf2571650d72c198ff26da48fec964adc3bfee8730916a848617 +cfa39508004f34ee02cb8956e89f29b0870ed6cf0ee9c60ac818501305c3fe9c, 08511cd8c898513c1cdd4438bb0c13564f0a8c21bd19268f6943153b49e4b2c3, 02d8854c5f98f4e329ae6dbc0d47f2a09fa722947e26438bb7ff73725d399112 +92b51bde822a1a47ad423e9a7ccaa698bdc844920cc53eefb4f7488907daeeab, 5b32f0f96445a16227056ce45d81181ef82dab56427a844eca1afc024065aafa, 8719a1a17ca6d492a4742984fe4ba5b22e3e6751e68db1d40aecf67065974fc5 +46bf55d5bfd3924fc0f57c1401a0a4914aa767aabdd2c508997f5a549ca283a7, 4f1ef2f2b1376dc8bd9ff172688c94efd886961c731cc09929b6ef6d7958576a, 16beea8e33de5e4302cfbcba4ee3e1583e2ee4257944d60e05907cbfa7999e9a +074393e2a53f05b3c78cc354ab8aa126d8d33fb42f56b2c0a7d906e9cecbf3d8, 0676d1ee7e9a57fcd1c0eb8b1e41e22e125198a26b889b741666b8f28cc36f98, cc769e2e1a6bb2c67b28deb9c4e63a043527d25a87b4215e086080d1f7128b17 +33bf1970c979f8b2a4dcb2da1940a8446744b6f437f040f09a265cb223222824, fcc438a1125293d832336b4ad930c09756c6973c0ce0b9150e2ae64c8b4993dd, a7f90e72a1e0e17d6b1fac9750bb3b698c03daa0d04bf435bbf4f20fcec3b297 +196525eb836c5c1d68d25e2b830fe1a24ca90ae0dabf756f220c1b47b650de20, 2d35e12c4c624a6e1d0076b2b0aa31657bcf8d422d2600de5023e388f87ae106, 6eb718910473f987a22295aef0716fd62a3cdccbd05e26ef5c9c094093f70e8e +907e1977cd95f8765cbf336fb180f5e44cd0122198c5509e59c0fc0742fa58c8, 09bd5ce91a75d3e834783d2da2790df12c554a3060a12454289d57c5ffacf05c, 0dea3ebebf0ed186e93fdcfacf8284c5ec81b81c777d2ac6b3ff5c658cb46591 +c7b4e43d43e017bfca5017fe5ae58b111ef0ac65a35a735265fbd9ac611fe7a6, 8c9fcf4d9b47e9ed373b3780c799b781f145056443eec02cd8016ed4e3e37ca1, fe54a854da38077b58ed135d35ad0a4d084f9f897c22cd3679351d38c85364b1 +d285577438b2c0dd98a908a1b848a7edfeb2db39aefb82ecd0ff1c69e4dd17f1, 6a7ab0c70140117f4b84034c7d3b89e87ab8e3132431da34c7004dd03ba4bccb, cdb3f8db507d477d57dc9781ea066ccd056483d412968f51552bfedeac15aa24 +fa9af55676d31480b640bbbf7b3aa89620c2b4de11ddc1c0b7ce680f0a092670, f410ed90b3fc2d62a8b83bfd4bb9dcad74d67c19c8a4e440ff7f1ac0d6661cb4, 59452e5853a5f274fba657b35a709394d4baf73a6f37d1c320b4bc368f051af9 +b638de5ff00b1fe372db0597173aeeffa2ecfadad390a6cd3dcf3c43a8a27d96, 03f6c00e240d22e2073535e61e5d8521dbfe7fce342065a418d51745f32c1363, c7436381489438729b475feb463bca8b5b27a2fe65faa05a59f586dc9987ea06 +fd6a262c224f6cf54ceef450937905f3cfba2633481d4ac7f45d25b7bb6939fa, e3778a541393b0af5e7698e810ac0059d6f5b6b4012ecaacc07357357cb6bd80, 89533680da34c5b0a0fcecf00f730b21ce6bc60d788f3d024ab8cbf270544b92 +66123db9b938d6e506a8298511487f1b90630a716fbab99792a83d20108edcfe, 68eaa64117ab06680030dbbe443daa1cc974d865ed874758244de693379efdae, dc58d31a46e441ae5caf48d8c711e785f8d57f89e3d8b81eb4018cde0e652b00 +f1daa52d44f55bbe1c2cd42a3dbaf16799b397e3b1cf631849362de4e1e490ac, 6351dd93864580aeadb4561b7fb4309d03d8c1ba3a401194b58995758c52cae1, d60341e754bcc15c5e2849ca7947dacbb9084b4971308493f5f5cef0d2642e83 +c6d2518f5bd2b1c8b1f7db101950ed5876763afe68ec09ab803857c80da1723c, 429319b259156e707bc6cca16e1986f85bd68d9493c2fbdbb189bd318c42456e, 0ccfeb890714e599bd5702d4b15acf32a62d83e015732bbe303da32a35e909ba +8fed604776e7de59e95b529c591e6b46808ccb3cdb903debc36bd58b39e9a935, e0ef77a70289440e3a2bd3776c362541ce14177df48a36af94a4c7a5476d86bf, c0ed578d481dd3591ffa6512a61b0d9d218e48a7cdb3037a064e43944b2f50ca +5b218a99dd9395f36df96a9b9afe1f912113458b63977e96cf3044a405b75c56, 63ae448796c07370e17b2e3367e672866538b589828b4f91255c612422f60724, 640d23feb36f123215f03917f2d10b39aa7f5fd571efd47899dbad0e3181173a +f66e816bc83468cdd134e5965c4cfaee514d9344b68e814f1de5363f53377caf, 6e5fcfa132be502a85f303b3d6682ea545eaa29b3d339a49182c30117d51176e, e5ec79687bf8020b22ec5b5637ec1618bd47fba0ad441e72aaf33d32579bde2f +655ce06048945bd81d6e7b79eca3f34c5c99483d35e86bda2ebf153005422f94, 040314cd7f80fd5decabf959515af6c35af8324eed5aa7b5a1d5dc885f69a71b, db85a39f6370a42c2f0553147be09963c648f703069d5b01c6e076cbbffd8a61 +2b473bdbcfbc91147b8eb523110638ad4b0b236c3b741a52a0fd76e250af9d9e, 0747f52738ba188c4d914cede9e78e4a74c31306ed80065fcf433fb43ed82801, 8ae14ec0313c8b6abc3fe93201e9dd12335871a44ead03f839d7f5274fe1d1e4 +d18d7694c82d3fcc908fc13819386ba1c843425fbed5da3f864e1cdf6a476dce, 864482741f1d122fc06330462552336de13d9aad41594ec7c8fc1a8c4a1ca510, 9d0387697c669cc239e4c0e8ebf112b13b6214d704fc3aa8b7bfbb95b9505598 +c6dc04c901afe199dddfb731b1e78d0c35ac6d02be7aaeab38ec2bb181c08832, 5607ed80437b3f68f7631299d2b4a34675a19bf375680677e636135cb08d4921, 8d44a4f0ef5476d18dc25670b1b862e57c3eb726b8c3f387c7adef39383e6294 +c03cab306872cabfa52c499cdcc1fe1f2366a9f4b4ddffeda4ec64f69dbf9403, c1192d6a82212edc4f47502e1363d6465bcdf7ebc61727b1bbebdfac35d5eabd, 53f63dfef4b1298643df488bde21a5fb2e1d67d8433812791507abed2da2cabb +b359d7e920f1879fb92406ea416a6e30b6d0ea0c8afa28b21c6a730d4d32a07b, 160749431526f814194216c21d344ee69e340c1d7c0d1dc72f519587b91dbf84, c87703816c90d17d3e7e0cc173389b110830a00a4e5ef6ac810ae44e1cccbf28 +fd1661342b5468be77d13c81b23fa895f367ff905abfefdd4301902eae3f2a86, 96fdf4a2d570ac89189cd3f33bf19bdde98511d5327011c461e9afe552272139, 63ae7c6a8dd63f5c2305a2ac6cadb0eeeadaba97a1692c3431109c92c275e516 +6a29e50db5692f13ee3fa89d3b6be22f4de212548744b92ff9c4939ed07b708c, 3f5a130b9cff2344e164b672214b2b7c9cb743fdf5929f446bea5ca7dcd65037, 78e9bff304547a8c293ec888fd5d5402569f12218fd40ecab98d43faf128dbb6 +c8499efede52f1d9d691f765dc968aaa1d0a46426b4371af98f0b088dabf8b1a, 49255d84bc9925626a8634c7a87d50981c4ef30b03d2937a8fc36282f4fbd57b, b733286f05e26b8a69bdaface83a98c776437904aff747999d03dbcb1ea7b2df +895392a14cf8807bcd35cbd77201d21469d558fab43945dbee0b4e017b646fc6, a1c30958914108f814e45797d21b5df5aee55f30298828eeea95baffcbf076bf, 3eb54b480efafaa0d88fc8d592def96ee6cbd25e05c655991d1ab39421b2bd95 +8f9e9790d4812b5f8c340b76dc2f4abbadf036e5c281d9d9b107b5da88fa6e3c, 98fae71777f31b6aa81992c58efdaca55b76fcd9ec7fa74969af3ef32d8f54a0, 058084f0110ccada030ea0f33a88163fa29865ef5fff71ad1d54d34f7f34410a +298e3afe813478aa29f189326e92fe7cf4ea38fcb4fc76e39f1074a0f0567d3d, a9da4de1b352b852ac3890f5fae059f20ccf7ebdb815e0a82c7a465d177b917b, 03bd119db55d53a588f5f10287d220359469bf455b84c32d731918e18fb009b6 +9afa5427298c00fe6351271ba51ed7bd7b7d8be88e10f832158c7a3f7fc0297a, e7fba61662dca347217877d039b85f3672c0b1f32b080a8f56cc5ec8d5698c4f, 3005ba08279f36d05d5f9b4599748e07d5a91b331463d60576ddc80c07f4c6fe +fee59f1566363bdbb68d791c008508a026dc44e7fbb32d29b384c139028a9d8a, d85f6f4c9b58c96c8649eaf32a4400bdfc0eea4fa8b42d22ea559ef91293d6ee, fd57c49c5eef4c07540fade485247ba7dfed669eac257a8d9bd3cd21ce199611 +d3ca209fee80aae05f589c8977647ca52e97f6d00a0423e019d988296ed95aa8, 49cea8fd63affccc17e6063922b363eec390898bf74e2dda825b790d1af61487, b2a97943834c6facdddd2713f40e963d78aedde2c4423627bafefcd49899b057 +34e06033f8495bfe9d67792a68242731ebef1cf6e8c7c068dd28ffbb2aa6c7e6, 26e30853ca889dbe1dc1e33a2e31d1958b988ff3e3147b18b0f54e5dc5b92911, 3d9925ce20fdbf78dca6e52aee9a68c399eedc6e67fba0e944a5a17c6fb0a5a9 +ea1318a2c593e4ae22d01d05d89301f2d96b55a8290f21c9209f11de1aa4a63b, 3b3b44ffac4b680f231d7da4dda260789093e556d9301c22c4d401b719bc1be3, c5267d552e6b6b9c4928cc1ab3e2e9bbc199fb100d030cc0112e76a1850976ce +e7a621d49c438049c360edb084f9da053a04a042889a42cb735975108dbed3d0, 058173575412bdc44d98c966766c74526533af47067bcacff11c109eefb7871c, a65d42856569492a31d0602fae1eff1c64071d616820f6dadebca69e8ab71b23 +fb3be171a6f9e3b2b012228e63958f7e2eeaafbac739ea3b724f11b04cea6802, a59229676a2546c4c2a842bb115de97d8e8eb2dc7701868f22ecdbfb5da91515, 1ab6659e0dadc397f84a6ea14d1c2886b41746ceb9ae99dc992a5cd4112a199d +1ba6eaafe8dfc8a8defdc1a9aafaad14072a6375cf1f97408c0e2c91f9b913ce, 6e9871c9a3f9a59c72ab52b8a8ba1a136b0662a08c60f6a45fbec76b1ea6ceab, a553c4b49176b42f862a009514b85ce2af961d942ae33af2f449e3d664ccb230 +f17d38980c5aa79e9298057547f98b72d69bc6731985d530256a8e50f7900d16, e019ca45cc1f355804075f65d08f0d7b911101f73e6187b28aaed218e25e66c2, da73177005b812481395f4b7419a30bdc7a3d4f560ba816e3e19162c3fa4651d +b665a20bb2dea0f1ed9aa77236ddad2f02a516ccddb9f0e91089f7a9d885470d, 70320c8ebfd02c78fb068515e1646b9581c4c3e158fddc1cd9b88807c6bd04be, 772fe3a6f3f9c10d23493afd549024839484f665e1f4a78b5b90a01f2809a116 +f6e954cb33d57a130769d9f2e1d5da53b76ba844b25da132bba8425615d5f2be, 98dc157d2d6d6e2e8de48bd766365441062a6d6983cfcabf5aa67bd6df64ec42, d360bfb08f09378245f9a5eb93d847a26c78f845b5966ae4d6b5c4c0f9a44c27 +5193ed440c4b628152a46842d1e2e3f5287892f34b1d907183064d303fec5fae, 7c8050bdba8a3a7c91cf52aad68426d4b6e28a10a3650440c8902a823f60c34b, c8d2d72686003f83606efc60d7bcd86eba7659df766289590872752810132342 +8603f0ffff4416ea8b4bb0068efbef2eecc896204d7569ca78813a71f924520f, 593bcd1b1415478832cfb30f7c1a33a306a3d1ab7f5ea14e429593e1068a0cfb, d9f17776b907d94aff6e4b1ca5c86494739787065a61addb73b784cc98cdc42b +b17eb0cf55fe1d5fbfe19ec43f73c819de46fcb862f577b775d4ab3bde726563, dc5973e70ed28dfdcdb556d794f73f45d101b421dc99f6541ddaee1928cedc65, bca48020a8af744e247dcd2309b65044f8e23ee19f69745ef35eb432c962d206 +7f1d7cf72220a062a3eee77b27146eb1369394b312e8c66b2c0556fbeaad5e7e, 3ee7a3f55b51184fe7fae2e0b7020e3e0be6671335ac6c570b820fe5b002b7ac, b3da8eafdaae4402677fb492d2cc68d1aea78916c3b7b5ab0001c810ac493b34 +2ce772a743573f232e668af9da977ccf050d8274402c03aa9973d26366c5b2c9, ef5f0ab5f8f9c844b049f376b68dbd7f8761c5591b5cdb808c1c47c5da127bc3, e8f38889b62d030e7893aa2c06713349a8f85bd496b38ae56bda0e032311c3c4 +26242ae52d1ef96ad42f71c29fe8872eac885f7acece150877cd199419d3f367, 0102da202bc67396ce74067653fba176d60bdceda07b1c0adfab614ec2ff40d3, ada8567e2d64308ff7f72ac2ba16b5134e04e9f7ef047afa7dcdc1df75cdb683 +4175ee2c90926e07927792c96000c3c133f502f5f85a204e5f156939eacf8f41, 5106a729a70b2c3a73c7f1a3f79d2e66912cfe365fcc5167b5a7d789fee024c6, 4599d90f8c47661096e1aaa3829ade04455f60bc9b2faa46c5fec3ddff986fbe +ed315221b8b4086f2a1132235eab7331c0fb7cda01e756457d67bbb6d15d37da, 3e6ea549e66dc7e233b81beb2af9881768979c24139e1106917c5256920cbdb5, f7b058f43e04debfb6a38a0ea17be350dc8a7562decad90da4ce728e30855fd5 +b33870fea08a896ff4e387dac04bacbdb0206bd4e54281d8678b0788b92541fb, 88ea42e0bfc97bf10677c1bdf8370b4389f263c4b145bd7013bddcd6c3fe4418, 68d06cd3869b2653319117f167d36699cc8edd24475dc135282ede536fa9f7b3 +99e0395b298baddfad60d7ed9e5904f4bb3b521142f955e1b4713e5db8b6b569, d1c6b5266376956eaecb043636c3f7b388932c5ef3c979940a05f02124a2719f, ccf47788ce187b19b13f3c6865ba7f442ee55b7105cddf2c17feef39f2f877fa +ada2943265ecbc90e3c2a10a8bc011ed42ace94f8f008c243fbb4515759addfe, 0e0a01c783ef2b82b9562cac4836217aae3077d41ffe7c0da5d9bf3b0e7d7ae0, f6c58f113637d1086b756cb821f72a53607933627f1489911fcce1eb349fe930 +fd5b142b80d2817d3360cd190233d7093b3d89bc5e3eba4bce60c9652d64f3d9, 9a424259d4504c4542d76be5a6239d6d9876cb9d89c2c0889b9a5df698392bc1, 857e61c10bba505e8b9728bc052169b0e561f63c9d1b8c5ca2a946a8f8e1da31 +3a8a0562e3b95c28fbf95ae1bd2e7d961fcc0780f24303f68c37dc2e533db21e, e965d302ca135a5fee8260a04eed9f58eab04ad01589d23e1fb95d25f55b788d, cea8065ed50ba40964d890745b0bdbfa22543ab2ea5864715264503675b89ca7 +c47304b059fdb79c406d12df4f840b2a0f063bd4c984786e73da61c14e459907, f39e5bf575b4c7bdb95abac15b0972012f955c2f5c60eba7610e8589c59a033a, db60eeee94c1227be8074819f4fb5be25fb45401da6df5f93e1181d336af54cb +83ed5549737709a2e3d3eec9d5a5fcd1baaa2669a3022a987723f2caabc41de1, 4ffca285f2a03644598d6fbb8063151de23e5d1a3a261a088dec3fc36381a1b5, 0a9f25a51835bde58936f3161876d0d0ec5e74c24d7bb45bcf88e8d9be8add49 +c32ef02d6237d8ff75e541eedfe43c5559b7eb7bb9a54bd5b214924ee210ea96, 4ba59fd17832df0f140cf291f331129ed0cb672508419b4187882c4cf6a122c4, c40d29791d60252920ad378f81de67afe13b7ae86c46428fa3f139a7ef299751 +743c92f32616f6a6ba6057175ca286fcada696fae8d99fef18a590caaa4c33b8, 119a2db9b9849ec898e6b734dc5df745e691e5d53949e5a29befe5706f1a1173, 39dd4dbe9df46b63af3b98168308a5e63629221594faf917ff7d812467b4c839 +7650b9d39844034ae820141e9066b11620efe969a2e5da015b044614e928bd47, 5c56fddf88d68d23fcc0b13d1f7b823db5b9d1bc5bc133f54cc5751582d80a7a, ac6dc120ef62bc4efc5f4947a38dda80baf56c5d91bfe2d9ceea85f28b061788 +db1e0428a7b891ed3a1e57b79a9acea64b2362232e353a4cdb5263cf9660d966, 660b0f9f204b3f10e9572abec82a8f365abe5c31242547e9c766d09c13e25677, a15ec0f89255e4d63436d0a636de650f4c387cf50b9421e84a87da2d2a686548 +d1e36d6175b88d0bdb9dd3a01490cf3722f7c795c86051436befcc0ef9b63bac, 3bbf0662375d2083bce3d1eb9f2ddac1f0f3499dcc89a7f5242f3af0d038a498, c0c0bd8a0dcf89f6705db78660dafbc1a5d968c997eebebf79bb7ed814c883a9 +41057151c15e07fcbcb71121ec64043019ebf1ecfed3fd945d08c716bbbc0646, de858375b5a60ec6530b21a2caf9654618cf5c6d67b8046497e030e3dfc80ee1, 418ef2d7a7c500bd0c7edcb44718afa5f070afc19ee7f6a4c90762ef9e5cfe44 +7264e44a766a36f969b1ee0ff1dd76033ed116c066713ddde7e20beea7252a6e, 9fc6aeae2b2ee304dd218ea2fab801a8109e56d1e69836095e08ae9d677e03f4, 21d93c0b10bed0442043195796fa241ae2c34510ed69eeb450967bd2ccb69d04 +94d4e80296adf1d2457a8e8eba7ca0f9b04af7a60963dd1c8d194c5b21dc11c5, 1c5408c7dde98aef66f11c96749434ed257ba61583421f5cf4cc6d534a6992d0, 3431c6c768640eb3cdfa7469d65051932aa7409bb5191c82b68ecf4e48eb3b29 +9c357152ff9bfd5e7af96fdf76a989cf65b0305cb483b8ffe0e598081d2b6e3d, edae309894cf38ea4b1ad23b9d7b89bd60ac0e4a76951dbb3207aa00705dac56, 2b54c30e0432666dbf47316dc660394dbff5566c5813354eef1a3c506e0de667 +e927405f7acb922ef58a8ac42bdf1e121eb9bfe6e87b5f20cfa1727c4c8aacc4, 4aa0b60675dc855de44553678b2336488b8274833b9ecb8fe481c417ff5c31b6, 60d0797e4f8a3551c5b9d7261f735d57c752bde04b736ff6e0fc3a39a0ad75b3 +ded7b14119a2bba1fa7bd01267eef137b268647dd92f08bafca111985b4d58dc, 85cc87989b11688ec326da243cc8c52ca4fdd43862d7b9ba78587840358df825, 4be674731e225548c1aeac1717dfb45f7a80903f212c6f96aa3adb424a7df6a3 +030bed7be1adcab133a7f628ab031632039537f6bd4715558e05e23fbcb4a35e, 2ee82a6a89a3cc104c2bbe0024ab27ae714cc7bcff005612690fab2150945419, 61427d8a66897a3781b43ba0196c2358a5742c33e3c53e39b3262a7f604d0f35 +c4225c52a2581e858b4b43e54d879c243760c69abc2948653fb56f813c447e3f, 351e3b0cf5d241e9df6ba88222c873dd87a10d861abe642b784c2ad8152493b8, 849d974f0e006e0b10cacf18c8afe95c1184fd53fa325376a2216dde0e255fb0 +3cc4afdd22d31d1ec1cbf7d737417a065f1251f4d63dad5f24b42b6d04d2b25d, 7d8f2f07b2285d18b57bc78b0c3872bafc12d4a3516a586dd22775a598e96aa1, ea4fae363365ad3bc1914f17471213b32218ed0c0a8ad5154f01a852fbe231c5 +a187339102c09f69fa682afe59308efd5dd8f9d59ecfba7d3d9989d51343cc41, 905197e1bb022e48c47e4707ddde5d4437d4526ab340f1790c8a44d5cbdc014d, d6742e500b944f60b2ab83f3ed04c256e8a8eeda9405cacf254911c9e870a4d3 +643d4de094c6252e22dcf606352026da68b078d8f111f3c08a098333c01c1c0a, e65ef52fd9498ed3f050a0fb06249875bc1ec677fcddf1a072110ae83298b770, eac0dd5a2356cb7549e1ecdd28ba173bacb550fdf1dfea55e5acae1bf6731b91 +23a0044c210c71f4b4103fb6c5d92801bff6b583f78d65330ed28b50fca15339, 3f5c09ca91ed23dadce3986743567eeedc88bc4ed7c775abf8d8fdd2f28df1ee, 365a789fb42a8512234eb1183cb9cf9d4dfc9830b22e3d05f9e30b8350f6a511 +f77d887e5c6dbd75fb40bdb150e5e34e121033e6b716999d6018c54d72de483c, 43dc046476135a5d812661f4b9fddad49797013749041c575e9609c4713f8116, 531bed28231af68114e2dcce46241fcb714cc07934a58535b1c72bee372fd9ba +46fa044797745feeba9e8b868fc51f37c45cdfb4a1c360972fa48018f28aa1c2, e7f3f14e9cd96c93e7f560ceb75d753b816b98f5c0188ebdaa57443f3e170036, af5f3bfb8915596d37a55bb02402c460a7b4d43f1842d75c6d6a74212d39421f +29e0cfb8e3fdf905dd359fa4db3333679b3d21d49b42a0f06b686e30d59a37dd, 2ed5076aedf9156c5d90bff9eeac3811a1019da35e584d6832d204597882fb61, 3dce65fd2f9a383e45286ac14e10ed434401215f7800d1e7ae0f4453541de1b7 +4dd448c246230bc3f11be1a301fbfec02b60fb7fd38b1a04bd3f0ca7870116fc, f48240f0fbcf926f260be5b7cf68dd7776e117c41eff3c4d43a2d5b3121210e7, 7fc84b31b390655a947b7476b055cc5ce286b49f097889cedac9712509cfba32 +63ceed5b765f4b51ba23db3943924654ae8058e0ec8644eb4f05ec93ddb64935, 3718988df681796c5919837c92cd8d6c894387deffeb4be147582355c8214988, 6b9c6566a40f4c8d6313c1593531c3f95e48a562a30aed9751fb2e3f08036c0f +34c5ffa58fd5050dabd2a26b57aa12c6e897645f041331eac298618597024ce9, 16d89875d663a3708272fcefa0ffb73c1e4d93c4d3baa2015cad1e67bdc09542, ba4e9dbb7e8ad7f86cafb9f4de18038b223260d39cfc70af72db3724b6db047e +1c504fe2820f2271fa22d7f4900e7c6847a9adc69ea5e16df2f98aa2c77739e2, 982d5c7101b10c2af008fb29b072c185d7d4d79415484ea7955a66498f109ea0, 845cd014f802445d474038994453332d2dda40469f91913fbdfe78b18c78406d +a8912024d2d19b63b21875db68a1809f03f2c90bbd042ba7f5a12eff1419ded3, f9a6d177bcf8cf4599e3004e1ed3644ec2c45d854edcb9df0988a466ad6cc83a, 5d7e0023704eca645ae8d0dcb0317e1be4a7b91710fa2782902b8a44eb9b559a +6b7a7ab7cb19aeb27473dc778414e19e5e300ab37517e81d547d7b03251d57cd, e66f3e18d1e7fabb0a83dcf8074b57f90d529f3f7f5192292f6ca10266139816, 4d23a39a5935527b9cb6e03baec756bea2e88eff0b2630d0edd164c27df97753 +05eded4550f09869ec75b4e5106d06cb65e28c84a02b999c3d9a932e5da34951, 5ad04f3e38b3ab49fed60c151f090ce8d4812ba4752932548109f5605c62ed7e, daddc53bdfb790434e9c9d482fec8ea35c2c1af4b3d21c9c42d26c110b35bb2a +558c22719f4e7d11945b97a5c76f9fc438ac57e31343ead0c32cbe3f9bb63b16, dc1b8c0848ef11dcd44c6ccc5874bcfeb98684b649a26cec8881aed9f7f674e4, bfdbdc6f94d04d21837e8ad52b91ffaf512915eb8e400f245142b1b92f8913da +8caa196aca6eea7e1a9ffa8fecec6d831f6f7baa3b5f43e72686121edaf1a185, 6ac73dd34494fdc673d0341273a3ec3c233bdba8e29c37ab7511c3455d6f9b79, 13c917efca6ca014ce9223c154f376b52f38d69d774116250592853656556c6a +543d9996f124168630d5218c635ab04b51c5edfe4add36895dc8ba17fe591d1c, 9ee486e0ab297f9dd244ac9863212bef5ac9a04d442afcbdf29f7dfac7f6c04b, 011e4ab3ee33401fb993ae42ace726e503ca00481f929bcfda9737dae15b8e71 +7b6ae543d0ef6ea352b0ee0615c116cf71bd2bbdee919230810c7fd70732b37f, 93b005b2a39e239e0dfcdd58443bf12824a16f34551e06a065a88bec295ebd38, c7aaff013090dc03e5fd640fdffc53506681634bc4b7f8d66076aceff71f2b1b +5c903f4d9c9a40a7a8fe893ba012162ab463e6c953f77556fd14b3e8b22167af, c98e24852d6db1f823fa6e8103228285f4b231fe4f360d63ad18787dccda6cf1, e4417bfa86a83ee6fbb54f97568ea71079373b890024a4728fc9595693c07269 +3b36f28bb22b971285a6bc7cc26431f99d2419e54bba14c5eeeaa539e7e92e79, 2d162b7f8a135835d9f941d48984fba8866e35fc32940f0f207c29e4d58847db, d3f9c1a3a3d57764647b8e5421c08f1d57aa2c589b38a77ef3935b208ab78399 +a1109eaa8323d1675f84d0ac562c4c79c114a75b92192a375290240d5090b8f1, a9956a40eccea973e3c815614ca5e0efb86fcbc1b1bf07610b109127c9ae9482, e7dec54080b4368576ff366000c9f787506e5fb0f76c47039cf59e4e1118f433 +a3271e5380ef1fb53a7d5bcf891728c0c6f6e0ef0acd56eabb18699c0b16a66b, 3a0a797deab4c9523c9fb6a6630101af0c8fdd7498604cca74a13e0bd2ee366b, 856ac08f92e0db1c8dc4a640fac823fd048d7705db856b61c4b690c13b4d411d +c83192dc0e4f9dd527c076c5001c27241ebc21659ef86c859464982f1118adde, 8c9a6d40b8e8a4c18039755baf2a1f7fe22ae290a59686d1aef46b34b3ced665, aa040a19abd33d1c3e11931c23f0f0dcd9cf6e8cbc6e3557a59af565ab7f2c0d +5ff50d1d8a56af42c4b5a72787f49cc090f59e9f8f69ad1d5db842be33cff9c5, e80f9f6dba9b171856cbaa51a253ac69147defaa5d162adf049fd00bef1d38b2, 6032efb102c7aae2f84f7d4bcc9bad306c27eab7a8bdcf1d91f7a330ba55e329 +1bd2028f4e6030972aa78793d8cda498d08662d8c5d075bd1d22cff3b95944b3, 2814ae876ae86b7b9ecf89e893a38756c147fcf7a577f3fbbee1a0b7cb80ff3e, 6321797c554664eb31229564ed1068d590b5526e46449b4c96f555fdfb587e47 +08236565e69a4fcd88a62d8c44d78b95629c46f5690e5ae28fac657797c6711a, f15c831e99b497555a6b5881e143455c31cfa04e598c4626b9ade318f2f55765, 182fd58b52b52950eddc28975b141724d47cea488d65381e29d39d3c29926ec8 +0f6a4437c4e4aebf4803a036915e9908f82380b5bcd401fc674a4f944445baaf, b626fa4e09a9c6b3ba9cc2631da6a5179828024198f1bb0d240ed045f6a4b6a1, 40e1af0a1ab20d23369084ac30ed1aa1beb067eb9f15754fb1c202246fdb1035 +ba717a06996b11bd87ec9ca5e93a0231f3e455ea2fa8ade9253ad4eca574a6f7, aa23e4528e300097e0a9dfe2b7e6e7db71f902a2c768296f9eb15a30b44ce8ff, 2fdc327ef51e8e074a5ace52b610bfbab0ada652092073c7e4cbdc11326c51f8 +5cad8e962c4a0d7b00765e1c6f4ebbd17468d44deab07ec600515151ac3ab7c8, 7579a5185a34c39a3a928147add251f011a7c621872e444e6ecdf6a006775beb, 091b8603feb97c48c75ded60cf38bc12e21662c143380565d9af4d1545fd415a +3b36db409e70b1267c768d431152be6a8312a0ddfca88e565e09ea5c85ef6fda, 2fc0f040a45c8c3140f005caa3cff9cb2e3c5efd788a214a3eb731ac8d082575, f769e7a57ad73ca3ae1d4b3d6b5d00122f0db96ecdb0302befe98140afe3019d +aabdc34f6a79d9637ac1726b1fc5e8c35a5e55db1ff2e0b21252c3553224cb72, eb7f52715b1e66f699bd3a116f797e0a12d30ffbf947539d26badce3015c7e40, 5f042c9bd4d18c0e7e67bc61d9b62ff025830bf0cc75d64a3192d04276405dca +70dcd66a7dab955ccaa265ad5219482c9e9721c22bea95100d75935988569ab6, c889f45fc8e2c996591d5fcd19a5b83651015a260cccc95da344a88ece7baedf, c3b76507ef15d27d96d7e22d17b56f3e8eee359a492ec3f4dbd73e02bc2a565a +1f4b6ee8735b02bf6ee2c02851e99f3541d0e73930201635861440ec78d5306f, 0e8bf9734f71731b7fbe633f590abc84cf49e8f9b57d2984d4bfbe76d546b33f, 83444048204aae392adab314bfbdfd35a0a5c76ad93317c9b9eec7d728d301c1 +0166a11056b1d820143cc4a4ada13e1b3a5f0dbe198be25ce5423590ad6618af, 1cff13cd82b0689f1e2ac91a6a5bc6aeed6515a78b61cfd4532daba15e5a1c08, 9672e23777af26913d8a6e433af1a4a8bc6f837da63b1a76918bcb152878c031 +cf54ef27b6711fbab47144309b6e90fc3e72203c83153703506617f4ddc283a8, fbd29c2187168967ee46e56751fb22be376fd55b8f689323656e37f6bd5d78e1, 33961812dee929de388bcf04e517391e87ab8d6cd92ce4e9d46c69b046011bcb +d4065d4d6bc5b13fa486a9146626174900d4b3828fd954fff4d431500bc9bf13, f22afcea9cbaf36a6c2ccb5ab7d5026a571af0ebe9cf104c410e14a009be2ae0, e1b94f1bec56bee3ea3f04e2770a3c3090baf12c9be828e184b66ce098fbf3a1 +393e1d55746f1bb98c5bb7d2131643f37bc4edd3d6bc5d07e02564fa37cb549f, c924ce7c3b97f9824edaf88161075660b51a9ed30ba37812c6aefcb3fab7d86b, 82b1438dd13b572fcc8505e6cc225c9f7e1e60ffae7af599de91ce1bb0a6fb7e +62bf6c30ea482b6a9a17f26de679f4d36300a683e3c82d19d7a7e507c3ec1807, 99b5d9f294b724999dfa436acf62bf711a49edb5351df982b84ae880596af50a, 932981258d722c9fdb0da862ff3ba495cfa5c7a2c6da18e0b9687507ae7d3291 +4bb7ac3ee04340812b4b92b1d9bcf8f8c1ba683958702f59a93c98474211def5, d5d9ca675f68c661693d5cb5431a740a36f8bf8eab66da82b4a3bbd4e40f86ac, 323a958c5ea0a6b2f4a351a1a1a27bf315b4e40bf63041d1e96b4110cf690302 +54f2cee88a6d4d735056b8b7eca1aec1c34f229c01a25bb6c34df75ceecc6722, 3598cfdae171e8714d820d8dfaad21553f38711924a7e2fc0bc65f8930512e9e, c620f5e5fa41f8acde5e37bdc543631b749886eb840c5a2bb51bd04f1ff9524e +241ef6d53ea7b83fa0c9aa81bc7cdde94875f4c054cddb9fb31cd07304e9d005, b49df9f3c62871ba67961d9cdfb9e7633bd960a52b5781148412301c515d88b2, 44a5ab8725bea6339991357eb10a4c7370ee5f818527f685ef1c1ca5c3a3db48 +1d1d52dee61db17a01738410e280cb6bf472d72137111cac509f05e85042795a, 95ff4f33bce14fa9ea25ca6b7ab41a7c90adf291dcd0579b69ac11aa89039af3, 699d1c402b4666fc6bf145a2a1a52c90911d0d2d95b3cb8c8566fa99f00b2f62 +f743134f15be64cbea9d286091441f3fcb23705d0d0e7bc483776774c9f03ce4, bfae0c28c07d30714ed7bfa1847e3d32666a795d6e7d909ce6ad930f05f1d23e, f663c18c86b49910725241efc602f6d6a7afcf23ba3a2c26f6e7f9ba5c93d83b +6b83d27ff0dd0a52ee32c5ab5917ade346c39fd8b6e2151e2b679cfd6123ee60, 82cbbbceeed6e6cbee5c1ea2e6a03d112c7a302f548c36c805bc24e3ba2d8ba1, 8e4750478d2d2411b1b0a74df66863fc7ca55f897302a757d061a626eaf46863 +a4c063984e850269ceb421fc1a8834d2525ad137be67bdc134f0a2f2950c7d15, 77944936c17700dddd03fbbc5554fbb99b0fbb179fce87504e92d4318cc5d36c, 941dc6331cacfd787060566959e13126f9543a7aaecc8b32014a8fae0c012470 +0c2a4fd179c9901a73fe0cc06ef0aca6ada875660b134c575a4e21ad3810b501, de73c2f7ff1405c32b1f3abbde49762d971834e4ff7cb0129e3af65b9f4bbaa9, da26ba88594b1651766dd8cc259cbc76afff56d9fa96e3251bd314a1a9295da3 +7c15265cb5eb653c2ff23dbbb96d07cc04ec558e9319a14cc95643370cf02e58, 3218dcda1e655c74ff2ff00cfa58347b49e128f9cd2827e449d8f8953cc177d6, 75868aded4a3c4df6a6f6f60aeade0d3cbe494fbf2752de483a363fc8491197c +2ea0f6cacf829eadb9c24756b0f91194491d0518c9646fd77746ef40235413de, 459d21d494c893ba50644e2af8443784bc9040bd93fe1067f5748252305b58ad, 569dd3476b890049e641e8530bc25a734c984902a7249f1980fec414b4cbb257 +220251966a0a4a3a5247409dd85a36d264ea4a4d52e330c211a617ab232fe448, 8e6265ee6927b610ded261b98ffea44b26154b66e82e9fe9ebb20c973fb1f81a, 27b2d947326e4ad3dfa7e9363bbb03cc01a5e5c98af622afcefa262a6f2cba56 +db10ed0c306243746cc4ca398fb9cae16c21be93cbed67558fd61ffe5c68da5c, 0b2b8ae06d15ccffb23775ef57e559c223e30058a6ef39fe7525b31deb1fa369, 4ac94a81a71f51b459bd8f028c2fdc05f667231af219aabcbe1a2e62604f27e4 +c68bb78bf14c175623dbf65ea9ba72d35310867e989f591ca594782c60a295ad, 15b5913605f4e155bac6a87c28ca0eae5a890ebcd300aba956fdddfc31235bcf, f39d0ef5ea107b3e85fda47a50e331629268e62f28c1d0536aeb7510fc8ade26 +d2ab9938cc63dc2b33a62bbdff448200272020855d931b8bb6ffd1532b143a54, 6583ce731b6bc12753be76049d2fef5b32fc260ae2fff5dab815be3188174306, e1fc9f1f8846cc02d39976e069ca939e327e74f390aae9b393df413a0a1e5dda +e1b45eb5d28306e5d5451266a1829ba3dbd88e63cdec61465e618dcc7ab389b1, 5781cc82ffef86199b3d32c9b3a06d83a1a86726498095cc92550ed31a5b26c7, 0c6e98dd3389ddfbd0b74432d6d8242e7588011f02637991bff1d4ba16015cb4 +2791dd0c7212c8e0b5fff87cdcdad19365e7a1fdd9e745b45ba28f6d57aabc55, 83cee4ffac53f23be86c55c47a1dd7dd0160c8b35860df33499abf59d1499ec2, 4ba8642d57646de179f2bc2d974a89535ddc3c2506e677e2d42a2d93b0a5a891 +89099a83c2084c393d0cb043bfd38bea3b21ed0e6cc6a19a9f2f3fc4ae88ac8a, 5df36c4e0e2163cebdb5f168531daf42338634295d9f22f5146c6c613599d5a8, 4af209c3cc1b69290ceb0c3c1964fd502a422391a92cd5f4bf07ff8194d48bcd +480c1f7c2d24f5e246ed4ed0fd5192a796fb768c8fe0b053f3446f1ca15a8ee4, 3a4de30c0cae2be71810e96cca76daedb90fb727ae8e45b83e03e129b99a23f1, 7b4c7290890ef7bcc4598340eb3ad5c35ac3607dd96ba2cbf28749cb14b7c6e1 +9568488a4b0b5ec9bce29b298373db23b87f4dc358f1632260692c95378d6d40, f58837e87d4268c8e68bb0e266580d1d3ba87e0f2301b79acbf86dd5251d6b33, 2043417b7ea26a0fa15f70c5ce71eeefb5296099a2798a0b0309d487eabeabf1 +f93f2e0c440e73f59eb793305b8067368567be86eaecdbbcedc66d20d767987b, 7889d83ab945110f4d01cd6a07b197d297e68b16ea75ea7131b04b0659bd9efb, 00cf3b0b3f98f95685b8e756595744aaed2bb03c5a0108fc530cc699ded246e0 +f140f54ffbdd10d1fa0baef2274b9fdab712467a3fb550d15bbb62685b6f62bc, a13fc89ad924f884ed284ca53d56f9cbd580bd80c323c40b6368bff357498f7a, e46699bfd36144b8a06a562d20577967d889c349e78dab709c398ac27b2fe2b7 +b8dc36657dadeb95bca8f5ef2d4be3e7c25cf53d8f21608b96be1ade01509f20, f98c7be277e63c8c652bff69dd47450b092aa5f57adf692c92c8794b4d41a61d, ec47e382c7e3e5457b40b7df422c77a400b1a286ebf0e137e8dd00ce7680606b +fdb5520269d6dcf9e51a6fc0f039351bd40574c11b5d5684a5f12ec6a88f67a3, bdf57317ab92d00785d96d3c98f42238bb5821254853ad9b1714c2f12a64920e, a509f5c850467cc89ed48474a7e322396095ecb4b34f2975595578fbdeecac91 +1446539c6b8aeb8fdd4786fb49ed0ef79c043502c5ae5b5a1b8dee8077ec36a8, 052e2da1e6a1be3b4bf60566a699207756cfa6f23d2125d0d1e49de0480efb58, 8dc5de46c0fe09c5ddd649d9e6a59f4231568fb121d38108a7e174f9257da2f4 +7b78daa73045414ddb10c8d9cd092f3e6240d64a1c98b71a6cc7b6818b98ac65, d44d4cb916abaae9fbf1aebb81c1fd1d914047c30a9005accd72925e611332f4, 1ccae304c1241b6020d36b1c77608dfa15b54fedb60bed63ca3f89f1e4e25d0a +e43daa06d74cb9d91b4df806ba730fd4dba793c722000d8d0195a628e585d338, d5f1e3f71a23a5b1e045b94557d19cbb7d2a64633096b4481d2ef8e01c26bf3c, 78c918fe31056cb65fa206d3eb2e150bb85896d05ed54e4a8a3433268586fa71 +709600e730c9f0d8ce84f8d4619dee97f4be3870ec1cca2faa949e5a30d31fcc, 3ecd551246d9cd0cbd0a3178eb070480c1c4ea7dff0dc4d7e2e215fb96be1d5f, b9252f82e3c5f759f9cf44705f4e5c699d26117928f3352033ce39959656e4c5 +1c5c239953d8e8a962b34fb041895fa9af3c64a9e53307f35c2988a85eb50fc7, 29bcfd8230d28f53ea70ced0f99c3fb3fde045b0597730428de4adb297453350, 3cf8ae2d5a22c164ac262715d8e05f43b3c0cbfa1885a469c5b33e33ef3a4ed2 +bbee804d1fc609813811310bd258bcded4cd782bce321fb784e24b1313352c0f, fdc05083b3b67c830d5ff2026792fddee53b190c00751be347f0c28d2cb7a5f1, b5d5ca781d60bbf7fbc1949f39bf4a0e36bb0acbc9738f0f75eda064a127992d +faba7c9fe6ad755b01fe92bd6fc631a367ea377c22e8fe6b21b9fed86cb3afb3, 8411113e01ff799bc5491b2c68a2513f5a911ac8dcaafb8a037cd748706e3039, 777c4f3d7a716e9e69adec815995580497d499faff335bec1b7fc85f12bd74cd +8c980cd2466cc24fccba044576618ca7793f13ce0cb8c4fc4aa74b1f11e53e21, 89a4de9b1b8d7f03d6f4f6c2079c2d31ab35ad72a0de0d831d35c9c961215b6b, b1c478de71f22dfa169aaa21d53726a9b6a3efb4c2abb672beb9e2f33b8c4c4f +2b7b495b8a6d483a895d9d3db173f9f9570d5bf156315ce436ec38bab04b652a, 9c14d052b18da101b3dcfd2503577149ef1f6ff19e32771ca4cf6cf7c968ddf4, 43a1d8699d57da78e33c73ae1f90894310888f79865c0b0d5aec43abe3f7bc3e +a176690ac98dad32894887dde853e8186c92e91bc99262939deaf4be79fca3db, c4b9488ad3e755ff3df72e73660ae2a9d2e94b24e1feb7337cb840b11ebe3bcc, 8811e5eca771b0ff3450f912b1a601b3e0b5be78730aa9dc52830a75c8c7989b +1b637a78c9d2f43196f98303b08884a1b45f17fd29cceb6f39bab4db0491b5b2, 8e59b26ca567269919d9dece6aaaef17e0e067bc40415f279da0adc1b507c4bf, 6bf952ff73202f54c884522dabb8580756e370cfd390b8f26d4282d87541aff9 +afa2f83fd3f74364a6e4b70f1736ac15d4dbdf0793036b3e6c4c1b36427fc471, 952856c49105344a1e51e1c60700e8992a0055ba2c3a9c8edf8336e2e4d9f4cb, 94c97c81ef89ab87698666c274aac145e25712e69878f8ffb0d3a536729dec34 +a404bcf7c9ad7a8a1caec3b67682f21ab12108fd62731ced1f7913f9898ba9f3, 6d1902de673d54ab7cd25616bee3511813022077031a7605a9acad1d8351a43d, b1d5ebfb1209994217ec64f1d26d784832e198b686503220388def7a5a3db0ce +b5d395cb4583f470d388d8355bd062db6753b8405e41d8e19824911ef1f789a3, aeee22fb9901a3358944ec1bb275330c1d71fdf34972a75afce7ec7496af335f, aa291f94a82362823210c0e3c4378f1017d576e6f95a91390c3b9d5b41ddb6f4 +55af59d5b12e127582d95b301854d33d324d0a2df9e90b90b9644182df98e49d, ea1f0c914ff4ea68e7b52de56d33c9b4ecf2f5e5102f5cef49663f86cfc02720, 8e33d68c4a91e500db2472960cc996a63802cec8eecd877ab4af095de4b104c4 +2ae79ebf07a1a33a3df94f29a0528f1893c1550278a12cc997b80c8502eec967, 22ec1831d7c15b09b45a0ff709c36d2d7fa768bc2b0dfd7931d2f36ef4b062b6, 2c9b495d65375d5e41789734186ba60d03b37dd1b7764e59513ec15370e82e73 +1d72138cf9e1a7b7e92da3e08866c3e88cd5126c7a79a63cbea0a820d1a7db6d, 8bebd8c514e4d3deb8f453bd650885a971d4002b576c4876b0a383d343c0bbb7, 5c137e50b52c9895467c2ebc5b17a7af391f946b42ea54dec388baf7fcbf21d8 +a14619bb8d3edf3d9b2bfea2303a18869fe3b58b91db4de74fb8305972d93956, 7f546a8b97600c3c93961e83328f704b716f6c560f7e866ad03b501a320be871, 0b703c4474095582b5c359ea9584c2711184521fb823cdc2b68468927f2d677a +a315c0bb11a9ec759df49e388d6f02ebef328db2808455cc6f971b94e94dbeee, 0a41453f00155db70d571d8b5133299fbf805aee53e5d383a6f369d5cb9ce309, b3552996cbb116485d033bde7f6aa7d8ce776844e6aacbce83ba81ea209b1287 +49532675b676356de6f2dcc5ac299daba228d8e5c9d0188e324db1e83b14bbdc, ced70fe6c67a609f5c63d21e1869c46afc8262f79aa2fe198b3cb15293e2c8f9, 54e651162f46a6d0859c33c714d8b7ec9e6810d5c98d9cd600b46bbf592a4798 +4c043654b67235c41de77858491b74d5b32964b85884be45ec338602f56760b9, 75740e61ccd6ef81623830a3c09133f80db70dac4e402585f6d5409c957db44b, fc2fe9fa828ba1e9ddad03d4f4ed984c3b4bf7b28618d30b5b2c95c76c58fa08 +a7a6975ef3e4d375b09d8c8291632acc56b7ce3211063336256e44580fe85dce, 5ee86c2d2dab6b437e4805fd9fc4a818bcd42ff4457cfce2dc50719939e7a7f9, 1c923c8af753d2d24afc5bee94d1f350bdcaaefc603a77ed36066eb4e69a9573 +485bf2a8fc11d690e3be651258a61fdc87a650791c7b0782a43b4cef99d4a4a1, 6b8c6c57e94e27890851e71afd687486b0f8aa9a5504dd22bcb7dbc06a48400e, 9824bae3dcd3dc48050b32a193c41d4b5e5d1d21155fde839af5828be2090630 +eeaee5149657b70f8f57856e418283b3730fd7463371f2abd1e530f75005e442, 6a7a2317a7614a43423ee132e0de9bfde462beb940d85a4d171325a1e004bd22, b195a6d7514a5a4edd0cd579e928c5a35dd51ed786f934ead29f71a7f5323b68 +b5541d2c73f347595f60f64202de530059c5a3d9addf81642429264b2d92f678, 5b3f4984f40d18b985200cf2aa1990d611a86bc46dbc3f7b16aad0f028e0edb2, 82df9e32c89e91d6d2d7a471dda9e60909defc58b044a6ac2787e02b7eae6e5d +962c914eed8380fe39c93022c3ef5dc551b7293efeffeb80f99ce3d016e02fbc, 4306eaf8e72a0a8efa53f347220059e3a1261f749fca5c1760cdb341e8bf49e9, b42ce84f22b5874f415ac3ce2c7e1ea8e1e2ef7dec37b25c0b3e68060a3c71a1 +f544ed01e23649841824aa2339c66874531e8639107e8408b0fbd797215ee0a2, 768e571a3f5288e58167af8a2b9d9be5ac14bbad46f26fe2c25d28b31b4d6b36, fa0197aa9ca10a15885cd5821961fed14801fcbad271c0c2f45625ddeb155d7b +e919529cabd35bdc2ab892b198b6a4856bbc7457489eccba19a17c1dd79df72c, 27ced58831fa2197117f570fe108c4a9846d2213faff74f1c1fcd3f2b3280ded, e50d0cbf42faa7b45b2239885b458aa459089027bd7525bac47dbb5652df8eed +cf62e2ce4929821d604c0aa068aed2f1adfd3fc923a90e1cfa87f6681ba0eb39, a5fc8e0ed905cfec2dbd0a39f6535af08c355c76ec06acc3e00909edb4b2554f, 74797cae7ca3c1e17b44167dd4ff268c3f07abad802cb0c2c05018b1faeed73e +4297efbf5226e2d49260f1ec6881cb0b3c574835afd28ed57feeb305d5584a29, 8e1821efe3163a9aab1a1735dda065bd694bab11750f2bc0105581fa6c29d4c9, 0231b729929c556cb3fce169e88379885e8b57c31085d39b43a9ddab579b9b66 +df7f21dddb99807141985eee6a5d85d049203c3d87a653eff65478481f4dfc3b, 5136de5e155aee0dd8c256f57de4a72cc88936ab90d2682f1c17742811cbb6a0, 210ab8d6bf090250d9d9f73367685c0b958e7daace83748076236593ee35409b +494d3030ebd3ac12a605fbaeb570899704dfd2fce6203c77dfad8471e1eec190, 09f7775c2c1c53e4e36c2808581c4e91c215f730dd4e72b08c19c5b3b59a85f1, 4be01e70457fac5d8cab386a03e54c5a7df3f6a6db1e099a2d74097e5ff0de48 +83c5be1e6bd56fdffd699a0a6ba5848c07d27fb6a7958db198d6e92dc4aea62a, 1ea615debf3824439046c91b2452f5d66f6b38f99a3a8a2d1eca70e4249585cb, b9043a1cab196fe6f308aaf046bea32afa0b69ee30ead8da290c5777dc594e91 +8041200bb038632c68569fb3427d6628bdf5965657c4ce321f4a653fe789fb58, 60f5c78dfc27fe03ac805e9520f6c21c4eda8b8583bf6f9b11ec2e89d0e97097, 2e33092e38ecdc551f5b1bd9fdf10361f558e38b7b7e720907816a6f676d26fb +c8482e966578aa318d32137049dc56b48af5a55c0b6f26ee7aeefe0c28457420, 6e40f0ee21779ae909656ecf979f16652edc32d374ae2444c528c61b85dd2b64, 95cd5e0303e23f38ceb6cf15ae2012c988758278eb923b9b9afcb1ae17184477 +fa88a17bd0833dc26363e4f3b8779a55c0bc100d802016b5e0a585ce0a33f666, 7f2d690dd240df7f182e2a52993cb18ffc8dc987217d8ee01b3ce6fe3345c61a, aa855c9bf190138ee46c41b52547891085adb207a409714430e272a548e3fcbe +510a9a03b006db6bd6f1a20893d8838ee9da62ba4013dca60d29aa97636226d2, e128bfcfd6cb085f097364887e635a9bad9d24ce056e6023be4ee24c22441a27, d2c0f9f5e3fc0c992690c8cf2b9c76c0389a2ad421b82ed9118f0f3e0349050d +8c6c2072517e0db75c5d0a27ba996a074fd481a9ba084677607220da9dcb329d, 41e94f5d87eca6f53e30faa0847ccecedc53a780d5d39163d985588f524e1585, 67e9b3f53690c3cdb314c112404fc40a2ed11c7a7041ee2803cb16d815691196 +8773afe2dfcb9e41d3dddc280ebaebbb29a03b4b3b0107ee30a833fd4a16bb78, d2dec1b9697d7640743699876bf368efc87d64aeb35c2b107b20277e88f7c492, 306c51ce6bac40f80ccc790b5420c00a2085e923b37f77218fc4ff61f2d1b904 +5b4a86006e54fe0cde72297ff8dd78873200fb9a34916ab8ba7dd600294ba637, 2f679212dbe8bf6b7981ff37a0da9259fc2bcf4ae2430511a5c0d5644c2ed551, 5f930e6e75fce9c85c02f8bd98c5d6d802069fb95fa544a4da1f6839638ab1d5 +07d87d19c500b4e59ef6750247d7ac917f6fb3fedf779afb21588387b5f553ec, 2503037a3b41b76b55b68daa96c082822085958ca9b00923d451666e1f73eba7, 802b8f28b6e95ad43c4e741e8afaf67301f443a8fada54be707e7877a5f9bd5a +63500927b9683b878a3e9f50407b50ebe31164eb45cee26ba66feca4c889ccba, b20ddfd0ca76da8d38054a4116de5b37a4f825bd63d429df39b6c8275fe1feb3, 23e98a3fb3bdd79e38b2fbc034271037a2468e1da2359e386db2a354af4292c9 +66fe4bed24f9aef590e2dc6fc4642605ccfc153b727821758f5e6f5bdeb9aaa1, d140bf06bfb905ba6af7f9bd37cdc058aa4de79e6f9c3e97467b9ece787a8558, 50e8b2ceba232a51631a224b495b8fdfdc8e36c75fe6a7719c67bca6584cf56c +03641c880e60cc789ae13a526d4ca1e50ceb3273970fc441c2e958a7c40a93ad, fc1ee4b911faf9510ab9dc0938e30db3235b5a186251a75f875ff570f9ab4f8c, e755c393d4003f5a7ef8e6854793afb02e635716ed8ad3e21f20658d2a25ca40 +4d8d77e86f61b7633232648476fc0699bf12a79999fbafb130cb4b74876d4ecd, 172b93e50092fec99b3b752d183962798db4353f6ea734feaa73bab23d6e1f39, 96335a8aba41803094a70b438d2b54e17480e3afa8446081776c9ac2b64d11e3 +b0afc196864c9399cb5dbc77e34b9c66e1b8e375106ce09358b3375d99e8ab4d, 6de81dd98c7b62c37a52bd7565232843a154ac2085d76624546b7ab1df5f4352, 6042ed47bac29b576d5f084772b8374c23f068e0c0dd21c319620a6db5a1a415 +df789811436e1cded18da4536dbf4e1b2d1280037003ee6ea4ce679683654185, 100ef2f7393abbcd755190bfb58189bd1537b66d9c3a12c810a220f4444617c3, 13930428bfb879e675c8427dc22c89349128c9b3686b4e7d23c6d7d2c3f8e142 +9a3087ba71f10f7bb33458a8a7ff52a2f57fef37ebf14464b7f2d3874e0715e2, 67a6f63fe58b0687f036e45d55ada7d080fb486fd58fc3f0a15144ca83ab0167, 95d92429e259e87d434dd74cfda10c5f199e19aea0be83a30cefc818fd48e258 +a2121f71d2e3f0e2331af0b5831538fcdfb242ba523a8abee0693d16a4ae9343, b2104b8428847da79fb479c7168a2a88b8a328c5927c7f6ae534d93d0f7cf70a, 25664a34378c5e0cbbdbe88af9fd41e565a9c781ad32ea48a01b2a91e6b31278 +7a04368b80e539dbb100e5113c8b9a8c5d9352ca4b04aa7bf47a421af97915a7, 17d617dad5c17d81a9aad9df33998e6d10d23314157657440719d78325410f78, 49591b418285cf528e2f17af335d415eadd37512415fffe554e527e6f07fcfe8 +ec859bbe5ba1bad3fb029f73dca26b98a208e7d1a7a2887ec2ddbfa8dc454ebf, 9d48e28e17866c09ba9eb4fffcb61f11cbbfe688324f3c2095ccea91b2836c44, 80181615514dd0cb79711c5b6804999fb2f31135669acdd794eaee7476f66224 +97e22cf712f5eefee411407bb57229e16e28388d4b3293fc2e9a9854c39d7404, a05c8c852ebcba0792fc2151d8df1f1fa5b22cab63d13a2c4a994dccdc063bd3, 997be930d0d631130b5453bf35c4227f6ba35e6565e4ddc42c48e1e4ca4a09ab +fb956a0899726ef43bb72607522e671528671184adbb674d68d374581f72805c, 912c626c1c1f1ee35c684aa742f23c3ef51ab0e6d7ad1a17ebdb5cefa1428d90, 2b49b5fed7a3ed1739e7daa610d2e13ef7bb125ec24ebfaf62ab81d1ea70533b +ba46b79dc316204f934eb238e5731f8d37c8cdca03b74cfa7f4e8d6fcd1d91fa, 84ee417b50fb735bb76bcfc2476b3615b00254b0385432d332767f2bb5fddde3, 438c5507c28aa3d3365b3c62bad3c09a7b8aa3fa56e9be2257fca75c06024cd8 +68cb6b13b7869e364c9991b80b6c83a4aefb046c5a887a0ee3f65fec32d82fe0, d992000841e78146d6d737d48358acb3cf94222717f397f292246e3c0903b59e, ab09dc20a529a26dd4dd679ac70aa8bba1bd5d1571a706305bcbdadc7b2bab33 +a771fb668e1746e1be75888c4543c7119bd4bdc36462f4d75cdbb4ae5fca87fc, 144ec2196f0b83fb327fab6e8dcc6890bd88a3481adaa3777b5772ecac6ec13a, b89fa2ee6c5a9a671b6c7e4098a4fb7eb7c725e75f276cf4ad9338accc5ffdf9 +578d27d5029b1515433c4a908d185a1e45548f72beb9fdfd7f34bd2bb616917c, 92d9343584545f70dcb4ea0700395099c0da1b3f9b305538f7e7ce4cffe18ec9, 17c5a302a02646d2ff5bb851948604e2486b69f69d53dbb3f266635b7f6c96aa +1577c32a24632369fa3079c256d04ef09336e1da7a988ae1b792270395411d36, 58ceb0521d266cd7c50f5013a41e590f730d58249d91e7794de77e32a4e2ae1c, 74c1eef55d15f6ce5ac34e014cd33a704df39e8dab152b3718181ef2783c4f03 +9452fa8e98c51ee4af4333f262a6e27693d406e8dcfd856cc25a0170fa5358fb, 90c55c78375e7de9305fa2fe1f5a3a004014a457fde1b7d4ebb8cc437b7e6495, 0200dcd0b492d924adb85483c4bb9dfcdd5d49c6391fd12420a9b5099364895a +c49f69a8f9054b39bc61af83c8f583ffff7f23109af45a5055352b57203748b1, f8751ee4af6f68c40cf11a0f4e1b021e026e1e048b2d513785d42e9f888cf051, 5a72b29c3f4bb257f2fadd35c935d539d488d160b3d2fcf1427d43b25f3da4ba +73c9e636b40f48d40885de931c4303a3226b899d5babff66ab5c2c5183713722, 2d25fcb8c74c1bac673dad09af824107829373d87d91016de16426a069ef408d, 14af5979d0b32ad9f803a719f1f3e952fe588974ce983656965d30ae2080a7e6 +7dc9ff16b0e13c99dd0aa62cfa7c2f0b29bf25a12771f1b3236191dc7dcc98e1, 1a0f6a2fb31bf98f1669f26cdb7189ac2102d3e6fc018de5e8648a0fb69ecd31, 1dd04b68e88cecbe7a1bdc83666fb907d82097ce193fd6aac0010bae07506c56 +1b39981d2f96af24fcfae7fb947461a6d1595a998b9d78306e6a474b3fb34af6, 2a9b092fe4d4c50c495db13faf8eabfcb7de88f403724c9e47aa64e69c3fb7fa, 5b6f21ef9f740d08762549058b19694f7f9d8f370d06d33b74d8f6e98dc8cdb6 +0cd75d16e1547150f4b9c424e42e8bd14fdc2bdf50b139b10ad40b9d9b73f825, 4745dca6f6ffbc482069ab10dee7785c53ab0c187478bdb3f574e75a3b4a4be6, 219009aff5cf35fc9e78ebda0b8b850633af1953f7b5785ea976628fcda7f148 +fe50eb4d4d4886782bac81100c0676ba2e72ecff8d802ef1d9c71691acfd5b5c, 52e670f2260d4a95317a71013a951fda207e2978d2cdf967c2b2ee516bd12866, 256f737c569edd5dd87db1427ed7b831e13d495120aa3ba162e3960f94673a78 +4c9220e2cc51fd5e0cbd91f3b846b1aad7eed16e1ec88502914136cee6e0206d, 78d01eea0c2938210e053f2e6c82f92b2e93818392f224cab0a551eae471a6c8, cb6eb0ecc6510b6c1d24d612bcd94cd867a0333e6e57f25d066bcd7c30e32045 +ea491875a404200a8ac0949aeb426834f8682fc80ca60bf3399b2ab1470be0c1, 18b36cd4614d40cd4f755af697630d6ce601f85268fd4ade4d27dff524871e13, a895001457db58924b3f574392b75382878d3d2be6025bf67da43472e83e5b77 +fdae785d86cb3cc41b12aa8031b340072cdddb562601f4c7e3963318e0908fea, fa170dfa7b46b69638121af04c3992ca3336413143970ec2178596cd4ec23c69, 85850af8fa4bfc011db39a7a1f5c4ddb1a3ba0c61bdc74976fb09a7cfdf5ca65 +4a7cd0e026c41ac54ba688ad4ae30194338756dcc92bb8d7412d7a9a7366d1fd, 09d771de71399fb74dc7556b4b07fb29a36d814caa5ba0e77941cc776e7304a6, 9000a3702fac0b8ff51a7a9ea2f316f50960d0abcaf5515a3d683afcb66b432c +27ca5bbb78925da94f6eed74c398dd718879593b36d462ed6f2e09a2fbfbb1a7, 04f49a4a0d0fa282f12d340bbf406794fe48b0c373b042cb0703fefef64bae72, 54209faaf8c53611bd51e0d9c36a4c7074ea3a001b87f5b3946b9be5a37e5621 +92c43dc32d14d29316ba4f15969be4745be0b2358ad2f69484916f6da3beaf90, 868cfb33be2d95a483dec4a4e9f62ab26ac8d75790d0b49f407bc7d8f0290ca3, 5a3639390ab550b514cc17746268ce609bca006ef5374355784c0330989ca6a8 +21d633eaeb698e090f74a849720aadefb10b304c92140b893479ba3d50d99cb4, 99c0a3f7ed4126f29a60ddbf7f25454b87770823b6dd68a34ef1e0c7da5805a1, 7ccecd87b3a40161401997eb8a660d962a8cfcebd2881df6367cd6641b60d9a7 +3a4a2d36ee8848bd9f34887f194a2da656e0fc670db8a5157cd11a2192645a35, f149f567cd8713214113618f6afb0bd060c706dae52d87a071302fc7b3c5cfce, 097b2881b7bc560f9d10c2b4e67b69fc300c15afc8f60f888df9188982bd24bb +d48d45e5867fd6f259f0b09a1b4837d15e6f7562af93921816c97a1563475892, bf1fa8742cd733831b2e514dd5406fd9706a8e43ac7e79d338a17f0cb2c2c226, 28b11b91ba9630cf55167cd52a5078d63cfab6aa4ec887d2f78925b02eb4567c +007c33b64fec946b35b8454f573d1fe640701d216cc1251f0173873114e178df, 4ddf0497df1bdc8dd85f152fae240ac5d508e6315005a5fb6e45bf7f2ee1fe12, c3ae3b8e042ebafd53bf433e819e50fbdb3c4c5f3731e238a170e07ecba02056 +92bc97f06f43fe23ca449b310d42c8eac8b6ca3a4d4ec08e22fd9bb9c336242c, 29ed8d29138f576ab0daa0ce53c12b7435302d2f6cd34c868c8fa5263d81f942, 50ceaa961753363dc71cb7c37485c2823cf20470ea1dbacb26f328120ec64f2a +278b86a910953c61d6c1c280e505933c1e62ed320e388b6357fd6d8491d0f2ab, 73eaa50733532133831260d36d328890d025e0ddf4e80753fd1f0fd75e26105d, 954680249dd10cb69c83dcfce640db95c6374bb8b2acd5f49436b4edc57b486a +122d53bc4133f91eaa8c0cf970fdfeb35f14e68a0b1c1ad00b583194ea09b5b2, 32fa2faa17b12332bd06f755c1b089246e327e987925e4e1bc1a185e23c53dcc, 4653824976f1c38fe20bb9326afe40d296c75c032499edb1d5be9cee08da446d +8040b21ea2e78254c5527527d813ace0b051791c51c927f7706b5f5f0122c513, c668f503541995285f3fdfe7d908bb8e8f59a45498948ecf611d7b8a030f059c, 8acdec94adc97cbd4cc07bb99a7ea3800c42ca47d95ab1c63f064229689e0eff +c3c0b20a6ca8ef71325cdb72a092e010de87a4a4c5d306c29a7538521b998283, 41f58a061b698abb2cb65f1728a1ebb20acaa678174c2069eca35018c7eb296a, 7d3536ab695ad1d95ac21951eb68a6629ac58512722116383a1edf5eec004831 +bf5a77cc2fa598d0b7ef199010a48fbb924dd68fa82c9b05e1cc8b9e84d11e1a, b1b20ee0607329dc28d1cd54b2a3aa86526048d53c5accb45049a6fd5544824d, 82a5f79d0c1d390c1fbdfcfbc9d6305ed7b6eaa6f4ced45e79303ce1bcfac408 +333cb2db1d3b33d2c627b5876a672974f8a551cd633803460ff990cd470e466f, ff6349784fd87b04a923b5e3b1e0517324069634ba01725bf755810f5f251c84, 5608860ce02957d48a1e7ec28cfaec7fff64227555e69de18328544bdccaca39 +92bca0d1eb4d20305a23745a6390ce02bd6e117b19ef4fe259f25c5a8bf569f2, f3418378b547a1c15dc52e1fff2809b022d3c47e971c9747b64c24c4e9595628, 49055a7f8865111a4e18b471005c54ce2c950b11670c359284d5c8c7b3908311 +e38ad32f5c40ee44a70ca75c447f7a48ffbb5f22cfd7bb89adb68b1afe71c38d, 837c107f71d7d2c0eeda12bf8767b3c4fef2ab8e5c6d745fbe18592a458f5851, 59a37e2567aa80e63a57e19974eb5f7e84801fd0d18377cd6d96b3c065330b95 +a78e313c740cf1fb68d2c9519062b45a0afb65241f77015557c5508a1c46df4a, b0b1395a3cec97db670762a1b1c0cf5b722732ed5be0854c6bad09f7eb33ce87, 1205f1f0108729fef7d4fdc0860c50542a2e07c05e1d3e84e83da9010c49b524 +d66ae7e05ada20b03d0a47f8ef7d39d0f99506ba42ebafb748dd3cb47fbd1153, f1d92f213d03d044062d133840cd380575a4b1f0e83f879678933562169c0145, c6a0c075145614273cd7064b7c149741190c594e08b373bf7faa13e604d5de81 +3a96fd3981a74c05b1b3b8976e7673ff3a2e53af49d7c486429b75e179b2da85, a93d71934b9bb6b752e45e7a15c2eab928557754e9794cc039a16145f792cdb4, 0583b600bb9343634a63e9e9f01cb5f88af197cd9169604c6439e5a7698893e9 +32396fc1c4dcf0cd3c055eff536fd719102a31830642d09dedd11115f4feff4e, f9d46680e8e7475741069561cf99cc0370c4e6b6bc98a26a974b4e0c512b1ab5, 3034e2bfd664069ba292ff75feb3f4f6924222ad642d2e6172d153813e682996 +d802f5dc04fa78f4a0ad928befaba5a0dcf2b6133fca96ebb32332c760a8e187, def85ad79ba4c6a58888d83bf403e8b935e5246f29845a3f24a2869a30269263, 6d7b245912aef1c1ee8b8a07d1185fee12a3f9f5fbbf7b89a4918e7137221dd8 +78e36910d28762d7292b08506d0bae4e6b19f1786787f849ad08d0220a7193bc, 8ecdcf60d70c830ceb6f381a7fb3fe211d9a125b2f884bbc3b6d30ca8aaa081f, 2bd203e7dfb3440eb21bc63cc86bb0f17994b12e401bf619d56d1b713ee10d8c +4aa7d9ec3f34a01c684b640a7fd66cfd91baeb1f69b641fc768f10afdf0baa52, dda5a3931f24edf650ebdfdf9b466840fff4cb647589fbde6a4f7386f76ad723, ef31fd5eb808a2ed9fd32a1ab2648e5df6c6ce55c51809ada921e98c9af04615 +538383c109030c8f498be19671caf88d7879578adba546d6adb5a864fac04e9f, 65a594efe8edfa6bce02aae1cd13acbc54cdf1b7e790ae64806a043bec3b36dd, d535557d78e9622496b50324f034527e342ff1a4ae93675493c924c8c319f36a +d5aa73009b803602f8cb5ffd8b25517ef21f682f5e2e2e76063085f9b8817112, 8705504d84ef8ca170d7a2a05b16ebd73b28349561c35cf94263dee1b8c52ce9, 77c962d39bb66de539b9de60b68babb318fdaaf3fde8157faaa3f3a5bf46950b +730a8e8795a9a6218fd08d4d9c105f5720f972af6979a7551a24a0044393587d, 7c1207600de1aa9128998af53b4ede9766e97640adaa7cf44b2607732f41683b, 2af80977ad369b2e87bfc801f309e729df35b452574e306de1ec34d10821d986 +8fa34c107bb1c90c65e6652c37ecf192b5cb690b868d69c9d05c1bf94bfb7e59, f75ed5e1c7637799f032179d25bb8df9359b91c7d1917e7b3f9c4e98557f3a83, d1cccb623261f4ca568cfa1b63de67055cb13dae19a75e435f9f6efda5e2bde4 +f8644d4e4f90d6512511d3cdbf1cc605593243065f773d2ca1e3e179fd96f820, 5ad5306220b687270cdab6d1c02afb03cb7bf304fbdb3762882b6e726d8d04a9, b699d235a505d02e65b1f422e8bedc385ce77d784c46ead72e5a23ec34abb432 +e755596b8aa5c015132fb79ffb5b6b117337e67fe6cba9db8cf88621055ac4ff, f53e99fee14313e1148f9bab50b698f1608429b5ecdd9a4570ccdb651400794b, c098375d688d819b4eaf5b4aef89f9aa5bf388b07b88c55c589935c58c675806 +b0eb184d3cf442b9d8baf1123f047fd969c4236634a4a852107efe48711e00be, 1197f080bc924bfdd4dd1f0c8e33b6dcebd0d9705ecaf11ec216f5529ad60222, ce67b1b10ae7b3929f3f324f6515d1b5cd1227990ec9761a5e58ae987a8698fa +88d4455e87a15e0e7a3487d6a991adcf9c8cff58087df0d6a3b6c9423694ccf3, 20be381711e7e5ca58bbc0e5d31373bf10a314b84378a8aee3f4ac30752f5725, e019189d45336008459ff95b46133ac968ce95f6e7a2d40100d81d1450195190 +55efccc9c24d64d211d9d21f76a7e93883f2a3abaf5536d03c5fd4b2ea156143, 1272529d14ffabad6fadb18e590d68653e2d8c565f805a7a546cc66890e117f3, a17016ef22a22713a9b45ad6837f0ee644e92dd27967b84a938e5ae90a54ab15 +6161f50bf2d0d41c67273f36cb5cdfff490530133ae5ea5f36d33c591aca11e9, d374a988835424b079ad24e3272e42075f94833d8570c8f9be8890f40bee8122, 4d741aed5b4d1f952896643efce8b901f38a428dc3592bfdcf151415c6d80958 +07eeef65fbfdcfe8539e8c341026e02b51f8fcf0d411770a7540b77e84a3f759, fe3545333c3b556d086ee11695b6b45f27ef5adaefdcf5499f4e3c5df7a9add0, ccf3b756db33c91ab020a3e9892bd77378a073e7fcbfea64db00ab494bb43363 +d6466f348e57bacdd9a970193cecd327984da221538c97f1b5d54a828f54f99c, acb2dd560d14f60c34cf8fc48c4ef0a6d58403656b065652d91f103f74bf7b03, e7e16d7cf9a584606ab2dddf10dff774d03f7e6544082800c6f1442ecd6cca4b +ac45d3bb363ceedb9c46c737adad992d04fb00db8983f2bfc5fa44c0b40e1614, dd34cc29cd80b00a0f1ccc040fd38d8a2e8ea42889cd799c6996b1d8b2c14ae3, f3ed23574ab149e73495c384449591f55199db11f387b4cd994ade57a0c4767d +8872e3fda4361ff9776e43f6011622e73b5a5e8eb856b175835cc89933208df9, f80f689e1d0d2d30970fc80fcd6a8cef8e4e85c790b4123cd678ab5ab28d1c1f, f350ae1254de39eff6b602e23ab58578dab2fa77c4f80a1b938bf71d0fdf2a90 +c40796a69c57ada52d591e4d44b0bc209d6c986ff6fb91b4a2921165b699c8c2, dd8a41fc5004720366e5a61a7007474d994d0a4cba2157de74bb07a31771f24e, b149fab2d7d0725bf6f0b9a0618c53670f409c50887e70818b29a110ef278dba +8a4a465a524e6ce25e658a30b9f1bac240cc8cd7579713a634fc0c14df279c2e, d19e120b6a76bf0a568ca19fa159aaa80564d5fe84067208009b0dacbc17bed9, 2be368f62d5d2bbe6604102c484cfdf7dcb4c9624cdea54699e3a6ab13dd3169 +d31d0ee7c3a4f33ac2475f7cb68d84d454af126547dc9b4abaa8d3242bc64fc6, 82ab5074e654a0a0ace5c61d6a5ecdbdfb1d70b11877359a463a47ec0bb777df, 3a66436978424e99acc966b18dc80db3300422aac44c95bbdc1f88a9eb7acb5b +62477cef7453cc6cf6ea2f3d2e360b6d0df0257e610a10db3cd35b5d26ffa130, 11ca5eee5e04927ca270a10372830915a33ca49de6dadadbc310cb3619a52324, f1254f639c851d5bb77fd2fdcdc5f0a413a4459c3b64cbd9b6757d53e6657cb2 +80d5f1e25740872b1fec0b895b3658fdbed0f2b08ce0055088cdceec8cb14feb, 4a6caa27dc9fda079f471e24cf5ad836b2b4135f6bd643e39ca673c6ec99666b, 649f0b881bef646ca5d68c50fc6d9269acd09728219f6d5ca9536811e79ce224 +907e17cbde23c6538cae55d2013130264d97d704053287d4fe555fe31d11fd39, 8d0b98e18d6eb1b006901acf6e4699548bd96d28482bb3a805aab6c03bd450bc, 464205f7bb7c8012e5623786e2f467d13f0d856b658ceb0930265cb82db75509 +89e5a7ba2af7079869cb6911ceb879e83b56eaa1e0a92893fba1f61e2948df52, 61a616104af65f40eeea515ab40abbb9caa93958346d53f3bc62ccb805a5881a, a23b97685af5267fe794885925fc28b04960c62762c1ec2323b238039abd0181 +cef17c4afefe6d8ee7c4494c9e8e918ea9e30794862e2e3fbe7c143cc52ead8c, 44c68caaa2f151d49c18eea8759061e60109e1cc5afffcda98706123eaaeb29e, 4c90b6d15f752ea448b5aeac8e235e6a7a55bf6263b558e12818fdf6ea7d0fc7 +4bf45a8769d6e5fecb33cb0c4da477818c49561c039b6041924f31ed146445a4, 9bb5b229c2ddf8152f1bd6cf3734cf335b7d86eff6826ff80d51d923863d3294, cbd6bdaccb8f49254c2ca2d62936ba9d34ac18ca283c42eb38af8ecce8b35c27 +3bfb4e5a4c1aea39126a8a3cdbff21c7ddd07e34cce5adaba72f1c40828ae491, 22e97192f4a0ce7c50841654a731a3fe3a3c0161efa10973acf6aa0647ce6dd9, 8f6a271ebd0b63a489f544a352566ac7fe10c858f89dd5ba382b09383e09819b +47a87674d860be2caa8eb706a10e4dd84e5ff324c52a581ae5e4bdd30e327b4a, 01f4ce4466684df92adeb49dd3ac0a7c30ab1d69a34f513f2918f590319175be, 832b354ee78a7efc29160a988c5673b9ee7795400e0c6bcdee5494e5cfc60d59 +2f4d855c542996674997b352fd1efc9bf332e5c09fd8e65713c396a17ad7ca15, 305c57a28728e221cef70499e49a7fc9ae742225d5ca25df82e942527089f559, 5c359a4e45496831c6f9a3496ca93448e0f18e13dbc0def3e3cfa8fa5be61b39 +5d7485b280513ad3c0d57af2bbff321527f46d92f213b431312293aef0aa13f9, da042de761379b5db4e862db421a6e90390687f474fc1db649b09e2a0f3a4632, 235d9d964b4ad9c7d328791c1766969ebec651615fdf164571f4c0b0d87bf410 +b0e068bd269772f4abbcfd8e00b886750b4ef4834b5aaeb0e76ab2d3e2a33e76, 29380f02b7ceb617509268141a3b5bf809331c89ee8a4aedd3e8a97345ba6245, 02c00e5c14b7e93b00f35570b790374b86c8d5b658271f06a86578245914484e +6d5a58b1c052abbe27ade1f8e209d49eb97e65bbfed52791dcde9b70cde6eece, e6b6d92ac86bb32ebc0c91ad831439b596f3aca180b80419718d0aa015f1affb, 62e0623cf313f38c6c741c6415f069f12b749d5c6007f305229f5f94f6d73915 +3a5df40ce2f9615d6d29cb5e2497f59e97240e7028572afdd7ca8dab75aaf39d, 0fd438bc89d4c4abfcaba6a9b1c8d783534c576cd6ba976c5893d24b1b97154a, 56f085abac0600d339695b54876eebcc4c36dd87f2ee2bf95775984675bfaf9c +04b6f0da7548fbce49e7a4c552dde847a4ac2918a57d26ea6536f26003aa8622, 2ea8dc2f03894f0262643685102bf038d229e208dcd5aaa85a5d4784a54b471e, 31226364fd0d6a6241f7884f0a7719f7b4eab4a66e49680c6cf35e91f9741f8b +b34abbf74e38e476439ea10dfc9ca93f1f5346647c6da65b8ffcdae721d5e918, 7ad61a12f7d0aef01b1df392b08b289e59972a69ba7306ccef66f0a59113e4aa, d5fb5a2c92646cbd4af891187d07ee707ecbbd65065808b0acef20962e06c929 +98cb427b06acc9c3b9142c2d998c2afe9b25510c1776dcdef3ef0fcea2a97b10, 5bd1e69723c32aa37c583ebbaf494d28562c4289d845f828d07e1e351f2b8e5e, 8890bba8acc1f70ac02646c8722e2d44fc4a6a8501e056d26d96fa40f7993dbd +58fd00ddd47086831cd219b09bef2066d8a6d278f8a7ce3d7e4117aaa822f9a2, da81398ed4b28e949d0bcd88f497649dec3b51913198f74891d0a902d7f6e0c6, 3819dca4d04fc7adca4bb64858dea119b4e549258fc020a52c4250413e4e279c +707c65f7fda2e109fb8567000613827dcc46352d613dc88560b33e3bb5dafb01, 8a920cdd2a9ca2829b7534e648822f05991c5e3a10b57dcd95baeef9a38204de, 0e749b3a62d648aa5fed220255cdd604d4b694c1b27e96b82980b782ca0a1c03 +c454f62ae9194b44b1b896f63581a01fc95b21405cf5551fbfa13471da2a319c, 477ccdd6e24e6ef4f24c84a153f0e46b1342f8315d23cbe03751ba0c0455fc62, 76137057c4c3b9323bca401ea8ae66f971e94ffac07c7d071ff225543cda8b55 +5bc1cbd21a54c567827295a0b845e2aa4ca13e2ea2c0a3a74e087b8214fa092b, b67130e2afc8adbb84d1f555674a2b515a3a4a5d9c146c96c5c9b9544bc75f74, 8bfed72ebafbee1a1ac5caff60197df22acaff232f85ddeef2e1eeb3d4b9e40a +2cce12835daeb93e52f2b78eb79307ed4867fa923838b92a8bbc211dca658fbd, a354399db860de6d94f8d892e28f11c065695891fc48246c6daa4205f30526a1, 1ba1654894df4a396dbc068dd9d0d0fa6c085e7281689f2a89ecd95fa226be0e +ac5acd51e6da3d2d1ea3416d13496b75087e2bce7f6cf3ad5c116597f4d8e819, 792af8dd9bca67f62f6039dbee3fb2f9e5166dbec0412b71e7f6645fb692d376, e6ca3f88d3439cd0ca383f2c0501bf9de89073b8baf3c867af9ec466c38c009d +49a262b96078a552437ccf9bcc31215fbd7a117d8bca151b5fefb1b2ae689896, a9a9c9d46abf3baf8ffa648d48499bd9f2495ce4a0359a7b90b66751d484ea7e, cce70f2378ee4c2a9be79d249ee225322348f2a88273c8e7db1be36409c8f8e5 +7ee408f96531baebd5217391e57805c7affd1dd2c84dc8442634794a1ea828ce, d2289846186035e3dc85d2be57e347818f4426389df791730f9310a95d4de285, 03b54ba429fd7c92a27ba43356bfc80c0bf22379e012f6666d05c2013a73202f +0cf6374dc48272d9bfed40681943910ed88a03a10546e9c5630f7132c8bb9bf1, 76b409b4acd48cedc756170940cfafcc061a12719a24f7955400d9929a87423a, 692730cb854e66e9201bf87b1df3ce72e27797d87fe98ca1e8277f4dad68b3dc +1bdc39b18cf0089c21a34c0b66018a51f6e2e071d3c7e87ccea8905888b619fe, 5747ef0d20d68623231d698572b8deb731efed176fe62ea1f6ceb7dd1c359151, a0cce673d23e6784bf934a1cec46cc301ff07a2e7f8f173104da5d433988d2f5 +c043e9f28f8201bd0045860fe4a8e4ba8668399d65f7725ce3372351a48edd46, de2db83dfa06eb82f5c5a0e5567de653d2b0885ff810d1507a540d0edacb7720, 80faf6c4e122a18e762714937fcbfd11bfee46b0abaaeb3ff72fc9b35d457de8 +06b4ae91a7a59ca73c5413cce0545a0030a09a999f23bc388d983a11d24a3e53, 912333cfb964152e5b93b61c39ad9c3f64aaf555228859e4c5f370acd6af05bb, 21d7bf363ce657ba61e4cbc83fa20fa8796551db907bd27728600f0ef9bb59ec +e9031ff6688694095fe865b87d6dcec648e6a0f435d701c36f362e6885aeef7d, 04d58f65f032a6f3580ec530a34a0d2ec388b38d6c8062ac1316bddc787facbb, 2049ac59c80badc9441f2736317572068b2dfd60b0c5f1bebe1da9c03c2cb875 +4210aa958d91728c7d35c68cdb5aae91d6a5ca8831e83167bba7e1b975d781a1, 186d4f42adb0242c89f4d0464d548e04db0d5477b469afa5b5d22aebd5a16811, df758350f63754f8264177575faf171f510bc5fa5ffed101c918a3396ea34581 +7330c55f2f023eaf768f0c904000816aaeb6af1a4f49ac801d0a6ba00435399c, 5d0aea68c5f1fd72c341446d8e2b03cc29077988daa269cef3d75c7493b3ab19, 6ddea0680bcc3b950c720551aaccc20f6d2e5a96b86efcc34e7daf61fbd5a57f +cbc93f8c8229e3e03893fa4913b7a17fa6c68efdaad22ba1c1d7de191adc6683, a4d009a556301beb04acc92fc2b7d06969881961b468828bbbe99f55aa3d7fba, c83163684bf5dc392a733fce91c133ce64931f0e5fe16fdc0eb44d21c8e1f715 +77d8402d5cca78c4706a3976d89ffe1c7e4ea92201411da2bf979d8ee80717bd, 79a3d1ac6a704283da997a36d246245bbcc243377063c7758edcbb15749adeab, 0c099d8af878aa34c75a3381a651300be34eccf42160bdf056aa7f421e3f607b +52ec890636c38659937e02a3326a212f1576d6ffaafbe1025af3d614dd3d5fa2, 3f096e169eb8abd19400ee245b6f276e4f9b078ef7f5f876abcb209a4c4fea31, ee6b42259cc730afb4e106a4887efbc21fa740031b3ce42814c9a33b0af23fcc +b26d78ff51df02cae29d00400934090070c815a95c6ad310cabdf7b968888d82, 2691e19c0e15ebf91518d5185985ac347ea279e9430649bb152c0c524e4ffba2, 9465c3ce6a02a9291a045957268795782cbab595d831c30e859178f9d95bfa7c +704777c9eaafdea9aaf428b765b67a48aac4743b417d27fe3284e6e608e06107, 37172fa0b88328b59c6ad5055c44201937134a3fbdc12ff5e4e7b53e822f5f75, 51a15bc1e7c32096e938a179715ef8a9f99047a0ce48a58d17c23a50c01e7503 +5be72e6b29093346b282bb3eaf4e430f59772d8b207daf27a10a75579af61a64, 76d5615d90af56bb1fc97bec545c90b5a5956ac1c96d20127945ed8a5856682f, 32b7673b06226516be198a533c2f60f482830f0d360971900e42df5393538223 +da98b4deabcad59e77c65d5ae4a4893382806404f2ed6bedb3afbb846af222ee, 31030f7d385af05aa01a852af697ce7a7499321d2e701bc5678139c94c88ab57, 36a1cd1a39c538d26f175a4e0c27411b2e7d7ad5ec0193a98c2b2b769459812a +476480323bb112e434baa19ba600581c1fcb38a987c6bea8496fdbb68ebb92ad, e31b244f01df0a8ab24cf6e1f556f571fc406f6c4701e6c3ccd3102952b493b4, 2f27b0023302f2df659324ed90a090628484b2ff3808f03d93b00b9713cf8f07 +66607e2896e4273869a1f3b3d6439b360645a941e773ee8d648857effd2c2aa4, 7d550424183cd2589819b56345a0800fbbfb3f979cfe2589bac64776e9c8803d, e1136e713dbaf3ef97c62eca26a7d2041e339b0410c012cefb3be3d8f702eaea +8b93377b5fc408c3648ba5c4eb91d77eff778eab50f4ec98edbb92c58c09b6df, 80595fae3fea5524a7da31dffe364322beb752a284dd375fd1818f94ad43f8c8, 49c25453c7934b5f1c95126aca646cb7e2e985457fe09374efbd9185d108609a +3ee566dcaa019f6afffc5ac76daebf2da9743b43e8b32778d645df140420847f, 15e26f126585351acdb93b63adc462bcf9d6cd82470209f0017036934a3f3718, 41cd2e8434a798b83909089fbda6b355727ade1f2f692afff393e0ceb9665889 +d870d0da4d44a718e64b6207a54fe44a92791fe7f546672c74b5c7ffc03c5baf, 85792ea127eda8db6e41abd601b911fd1c5e1725278399f8aae6c7a92a15238f, 154049ad9532c71a4d8ffc444dc10f69ddb696fdf391b1e2d004e56d60d3ee2f +73e0107e351022dd208620fbf9b4639c9c39283644815c73c74eadf491cbe93b, d971a4b0308c8d99d3c72d7ab59d6f240bc9a49ed413a7355655162553fa5dd8, d051b89a655ba42c0c88c1c1d41dd89e46bec470fbbf99227fff84a86fdeb308 +8ef060cda043c8c9b1d50082e0b5865614982631934ada510e5f889f2fe6f6e2, c3de387f59404b6658a0a70f62fb86e36ecc5b4914de4f6ec105fb400854d297, 3e03e7e10f77614fc5834954ce697ae9077dc2db744ff5520f0586d65965172d +19d9858aae7ec5724a618b67d27a6dd448677a2f5a1b3184900c95c95cb2c104, 477ea4243782c65524a506eb612f0aab32a2ae41ea2f204f75634f7f77ab9d86, eaf8d51417e9748918fb97c47f5a6160c6142778e15abc8cb372bf710336d801 +12ad67df1ba88e7ea6217efedc505b0e42d1b1fd58603889b180b07d283c5759, adff9cc4c6e54fccb3a9247e385168ee58a4bcf131db3d30da6720e2a6af34dd, b871b0f8bdb13bc8b81ab055f68819cc0c5ccb95989a50be4aac1232526257cb +f668f8b88faafc806ef8160ce4992f428ac5287f381e20bd3909752541aae357, c8f247e0b003c7e9d6171d66ae581037065dbe0975bd0aa6ebd1396a60857d87, 8f1ab371070101825137edf4b9162ef247b92d651947c4058a9ad177d83b7edc +609c71325afbcf644c23283c9ad94c843eb4656750910caa10b832a5493de8b4, 1637ce001f23f0416147834c88fb6195281b7bfd38d5d3c7c47928f1a0bd801f, f0fa06710efa88b3ae9a03d8777095f98dddcb05ebff2374eacb3a0a3d065a6e +b45a8717b47e34cc1c159a76c30bea499255b21a59d11ff4200c726b9f9bf998, f3aa9fb0c8c589934a3d0050e887ee7e6b62fccdfffd4d0ec0f706ab1317bb57, 79af8a2eb95b64353de25a7935ca511feaee9b87f231cc79345b52462a3bf8a6 +47bcddd12ac3c4fa381148170c8f8c92b7dfa45d0edb4e47446c7bbca91391ec, 0eb28a4694ea9b3ba12365e110145ed068b76e9d15681725270687de4358bba3, 1d42b9dc4af6823c270b3002237d4e48f231470dc793233dadbfda0c35f1409d +57353a3327445a3e558edb0eda678e88341cc8a19ddd03b4a222fe24775619eb, 070ee911a686f991e1fc61b29cf4cf6fad2cdc74cc1d2f3ae4f4b291e658aaf9, 581311c62d50b89797f928ac7c7a38feba36d9e66b72161025b0fadcc4f72845 +b14e671295b77c3aa4a81af0aa5ae851ad9ef01ab298ff89430dd68bd02f2942, 61194db888be5ea26edd2edad27573430169e2e2cf91d34273fc0d4c7d2db17a, 4abf33712e5d59fa4c83099ea5374c72a1f2aed408cbef8f186b5a21f169b134 +7326cd36d75ca090e19e0c00324c4ca5dd9840ddba01e7c75ba4b7f297031966, 9d197c2c6bc16373eac42b2de6ac7514b55d193f7807641ae2b602454b214d2e, ec97ba5790dd77076e3c54de03e3fac812bfbdbff16ee8bbd6212b7aba1d7d2b +abf5732c9f9a4899b6a665432efd5483311d52542d027d0b9b369cb161493943, 3c501dcc66265f49edc14302523c7248e00e021e0daa7479138945eb779a0273, 456e115dd0ad656b9934ab7997c87b928a8924d20570e5d9bd7072e3b1149faf +cfb0e6561ddecb93ff86ef2415e46787c05a04316ffe60cede0675f89d4dc0ee, 5b91183611fb4f4fa6d2472611264d54ae2e945af36d201aa5ac4895d5d4e36d, 35fbe945a8aec6d6baa1a031c3505689cb9f28f637d8d619adc3ff8fe378be54 +56a3946c34e82528e36af5a184f8122553e06951282f23df1e5461fa4a4546c0, 76199001a2402e2d636210b1634dbabef26e1f290f3840dc7a5d7fa0c13eb3bc, 26c8b006f3b92aeac7888ee46fa625ffb44b679789c8d1331c1ac5c00ffc9457 +a64ecc0b90d3f56e291d8aa3864e16860bd45e293065793b521f07f69b48d808, 8b8437af86d8d30b1b24effc1fbac7f8c16af7bd41f9c2a8c8ed50bdcf5ea58c, e65c2381e273a13d9006fbe3ca28ac5d71023e8d07577f94d1402a6d09872b1a +04600e2d1cf8bbef2d3cc045aade4edfde895ad6021103eee732a18d712ef664, 9361c368eac01766114141fa53ca57484dce0126c1005c053b135a5c38fdab9c, 15dd541c7b220e303a18d711de96e779886cf0f7fd05f8a1a26207933874a6c7 +1f8246b2485ea617fc067719ac0b2e151af9dafbc423ef614bc40a8b29443561, 68c72c7db152d38232b140aff8ecae60ff5887100000737130f005e4c4894bad, df8f8683c8b4c33461d5809d3eb8b9aeec84009270a5114a4f714e08b9f1520e +e1e5c2e423567dd129dc7a6e399786729ff48bd7d8f9c45a2bbe8864ceca5213, a5f1d4b510eacdb614cf60e8f903d3724937ddee548ecde29a9e5fff37b28df5, 54b6b771059fbd4007d723d5264d269eaabb6929581df5b9c766c45e60107ba1 +74ec2db09a4c36092d6e89182a7d3cc75fbc07a0a40b5fd561843d7cd8ac59ce, c04e6b44d9a1a7f84fb2fe8ba3140dba32cc876936646983c176deff16ec4bd5, ecf5098edebecf7b22b7032da3956527018eb8caa94980498808283330752cfc +0d73c6863966e53602a5cc0b4ed716f9a4d924d8369b90bb631632f5503bf500, 797e30b8bdac0ff20296b16adb5f079b7af43b79fb692a2b02401a71b8114246, 75c0546e62ab9b1e31449548c279c5ae56c36413a32c9d9809455ef177fcd728 +1ded08743fd1cab7a8c940e18b1c4c46486dde4d7eef808802520ca5ac590b09, 6245ab37910c644afe752e9ae6909b49383e13c62377a5a7e3a7a0bf5c0b03cc, e37234a0cb35e29cb89eb47332325dbf2ad68f6354ce19054d26854dfd64ae59 +d6089e2153c5625159111d63b5761ea48de24c8f3ed09af6a8e9f1456a03c965, 2acff1fc203a44a40fba7cafddb342a549c5448f4fb5d59abd99aa149da26386, 5d468d6b034c85988a79adbb56248764cdfe4dd23d812d152a7de6f50abcfd9a +2aad0822991dc0a584b390e5b62e972a007f344e7d6c3fc7da609c581f9a3d30, e5f3d761a257597253699ba4525ebb8b02d40a4cb55cd97246cf67ee69bc7c32, 1637ed8a6dc28c74cbf9698c961581b8daed104f61874d446074dab332c71b10 +121fdd8ffe44451a83fa8a39d508238fad1d877b68dbfe5c862c791ec2699c6b, 010fba950a44c44d41bea3120f61bdededcdaa70d124be40514935cda0ad5ffe, bff90137591ccdbfdfb05752d3d2d1bcf13f98cd8f021346c148ffb10e71c3ee +d879e60301bcedb25a760cbca12dbcd0680cc91e6783087d752dd2ff0f623391, a90cc24e1c069e60a8960b4edca71253fe064082d767e4701113099e0c27df13, fb077e43c8157c846fb1ccfa3393b643be306f9719ba05eae49fd01400e4f93a +5d3818e8be093f45f28cbc5cbdda16245489985aa200fbc61e425c254933708f, e68b884fad760c0167662c98987c6b0b1b114a573e580a04608f84cbc00abcff, 8ee1196406ef4fbd77fc0b705c8a0ea80d970a7f4b5104a7cdf14901f436bf10 +e05a834b27b07e886ed5910f906768b4f9889edf3fa2c737d9e0e39a68c40059, 8ee8c935f573eb88f42c6bbf64b6fa4cfd608d3ef6084946f91161f6ba33f45b, 8c2ec737277742045884bb706b2485e59db07b0addf511e23c677bab60727015 +1ffc20942eadfec2c9372923be6ede74a1c24e2dbfe56c479e84f02b7455e408, 0fcc1eaba8e68e44b9250f43dd4c4d7f04f5b15dba4a8609fca1fc089cb47c30, 2e47c1fa85e51041e8208f52e22fa7f2b403ec28b314c0f77aba87cec0fa2f27 +7543795b1b381a6a024d914a65189c2050bf81d3fcc0fa854535af740bfd9c14, fb94d99c164fe43445670afbcfce2ae4a3c207f9129d596c591c5df9bb5fc8e5, e5101bd41cc86c9d025f127e80c4fd6a74c61cac7a4ce577dc9f4f47daaf9bc0 +8cefacf5b798f1a7d8afc551d6b4be8c7abd9b0cadc19d2ec1fc1f192ec047af, 55316291731a7851d451dea4287148f6da2dddffabba752822b0cceb413c4fa3, 2baccc9adcf09634caa795e25511ff0b45e51002dad7fb1806c8dac1b4defebb +69bbf83c1e01d5bb2f9b0fa16b3082b08e3ccb53142771f600f37b14ee5a1304, a55b50736838ecbcb4921e419de81d6afea99e91938d5c8c83b53cfb4885328f, 475b7e5cd98fc10c2d5f82359114310ed7c0fb89b496633aada7f21c9e545886 +1109e7bc9717fa4364dde4ed2ce56b1fcb163cb7a48a5a044235e75af0a3f0b4, 30a7655a304c11fc31e539d1344af235b5848d7e6d26c4f2d94be5e68de76dfd, 99969f9dafb8bf81a02e7e4557289709c923c934ae8c55aa691cef498915a5d9 +5f124d9c5c4774befde56d4b5bc8bc554d138263e05b042abfbf492e9b53e72b, 45e6a457fa8f1bb45f41c61ea0aa33c2ecbb00fcb2668aeae9d20d728d6309b8, 96329113d1a5c717eaa79859ed163ba9d5d263d5151aa8ddbb972adfdef9100d +d8bf73bdc89ead65d6e94f2f27484604bd8838caf4cae997a03e2c0164bc9bc1, f8767802ccc7c01f016c6b170fe043c20d2072e598e15dfb83d70ef1479c45b6, f175f5f066fdbf73500e0fd908ca3b29fb4f6b31e3c012e6fbb7fd18ee73446f +be5139390ef9d741f1a3fd2ac22cd046205a22caf9d6335d20a2f01ea6122c6a, 16a5af81c671dac13ea4114788e2322a33e20d2af8554df300b7d8fdb356044b, 6145844a3d2b49e60ffae0405bd5a6f2f5596e08be09f6f8c9046a547d146fc9 +a18d72accd6000cb704a3f61874794336bd4793cc1c3b8b921e4b07d42e26140, dc14b211159e7ceca46c9e1a0f606ef20181c5942c0ae35961f48fedf54e0f2a, 65151595c298bf6b1660c65f4d45d75628fb65b1ac6d5081ceb2780770fd18d2 +1fbfc8fc104ae695e38d8773117d3a92b2b1a0e522e6a2b2378203c76b03d3a2, de3710675def99170f95eeac502d48449dbbfb93a8cb8e72f3bc55968bfd842b, f3e2c043a441e4796543777b6f2476666fba7dad755ccbc91c8454c8dee8de60 +a1f20b6d4baf8194f43dcc4396efb67c9c76f10aea5bbdd5a3d5e4556ca2aab2, 814f35583f8d64cd5a523d41094d22515ef6b91e0bda3318a4f6734719725743, c1ecc37c12f05cd2649200bb3d9911d4ac7fe400d45c6e27148ca95a38973024 +942fec10b70a7bb7daf9613a71f4fa7742cae623ef735f29c706bbd871f8b955, 30cedcefcd5a84a004b358235ea549896a576cdd83e5dd2a1780a467802b25c8, d096b485667219d5331ebe93c7e16f8bb9f73ecb1e795e98310cded15c687f0e +1cf6c86007a75e6e51e8ff387a1d7a7a276650306706b25c6fc0eb546f2a430b, 969bf270082e0cecdd26d88efa1c5e592ea23376ee82c020d74e316c0015740b, ab530af80b803fb3ccf5f93177a6288de6420fea6cee44ce9859d745ebfd6c19 +3e62337fe7e2faf71d2806d6aff95e754bfcbeb49a137238f259bd13e067b007, b031f59721613a5b8299a1652738675d810563dd0b3ab33f5f3451180af69981, d40806f96b9cc3f5f4e2f6db626ac8c507022470eb7181f3ff4bcfa012a07bba +76ec758aee3c439ad7f1daca3ad52dcd60b16be5fd534079ee2266adaaa8dc37, a94e9611690756ddf490dedacc6a2565a000f52354d23d2761d3e2c119d8fe1e, 275acfa8e6a37686cf6b48da1d02c8633a68718900a6be45766cf181727a2312 +6f549772df13ec15f440791da9104e967d33aa59307474d551a5da4f99db9664, e5d8974420d7d1c253d5228bb0a2dfa6db654aa639cfec38fb7311bec46e34f9, ce0d37bd05d3ec1f7708d23df02b5add4926b9485cb7bf301996208e3e48157a +6f2b37b78c47f8770f8829c6809a9afddac3e0c172c13ca98a2e2638abc613c1, 1f7908203b08d3b6d69b9b6b0ae5bbef89f8fe2ab0a2e631210b18bbf5ec2ad3, cefc62331c73489013303a5360ffb0ec3a116d2bb69e12304f93e6b8228a6f7c +ac6a05390df8870bf22f0e2d68e979a7a4cd602e8d11a04f881b4d65b97d8c0a, 50bfb3bd5dff41bd65734447d48286b05fadb29dbb9ec727824b7394033564a1, 86af5daffd89f35d808caf3ae5166f34a43d0252e17dea1ce99963ecddfed2aa +e4cf5f736c1c18c2a2182108d9fd361213c7e4afb1021a212f3bc68cd66a4465, a5df7b7612f8154630c0d633de228527cc9eab4088a5915ac9c8f367cb74d5b9, 48931d482856aaa6a04415b89fcdeb41cf35eb3b7a972211c4970d14933f1367 +605603eaba49c11511bbdce8cad426d69c718ca4d0978e03c55df02c04fcaba5, 6eea20696cdbcab6147936bc1d229dddf40ce923608d853caf7356f0d8d6b840, 0879090b9067abba3fd8b9216b95a42bef903b460f2c452da2844f6eb18d0576 +c0f33ea34bb9735268eefbb3cc315706020a6589ffe9ea4025845cdcbca5574e, ddb874e5f90a5018e87973326cc741f7baf1bb56d913cbedc77a4771eac6d406, 64d8e522d949bcfb3c6ab0010f33216693d2c7b6e795734990978785377f5a76 +0a390136231ec5926ad849242bb3e93a5c69ae4afd29718b48b56ac14ef705b3, 06ffbd0b5fe1c467d7bb74cf2a6276e173b97163d7a3f1ed458589e7e82dd5e3, d0c621f607b7a6c3f8e7fd4fef6f6e25cd276761238f467a0b37232ef20423f4 +740e485a0dc2f4399d8a838c6a79f79a79525cbaa303c36e920cf1951b85afba, 43eba3e92f9ca37d7ab5793dfdb136896f82e732676ba9f36f6e8da1fadb4414, 262d0b685787fac92aacce2cd9fba79a49c9665c430552c1aac6619e8cf5b3f6 +76783fa6a19be2a7bcb65a9e13b8dc60c04e394467aba20f0eaca02dcf7f646a, 6f9b05cb7e52e78585c0d950b7debdca8d483926b5836ef0612d2806d3605ebc, 1cd0f4c9d41191d16220f73b1a68173d8e9eb8eb41a0f7f2813822721c4af406 +9989f0d3c1bd981e88eaf9a741cd007f5cc6a9af705c9c85e911d45c76a3fc99, 66b864d545d00bb40159d3dfbc6874cb49c99138d965eb3bf13e3cc78d07965b, 2c6172f08c08da11dc1a9efcb15f5379764ebcc93da733b2475497cbf300d451 +6389e877c74bf1ef4d0527341d080f47cf0f486115256b2e90d12e9ce5d1bec8, 38e98a016b7e495f68ad593f753318510fe567903e2efe88965fda62b681be51, 6951ce3d9f99f27ed07608edc9401ff27eb075289b99d57d6a1704df56ed88c2 +ea18d5e46eb70628e7906dbf4f169afe4bc5abe876d100c2ca40f09f2c2c2837, 2477467b39501ed65cab5296a138d8c0dc1e4b1d4eee4825b86bc652888e2fc8, f270be53ba5928da2f6379a12c826e4ba78a5c5d06d668839c682a750feebddc +6b340d1167a387e2239ef7c9d6fb92e56b9dd7e70dc59843584fd8cc5072bb48, ee395934b3147c5d0c95ab5e84bcc5f8176f53b0f566459817258144ac04e771, c370e1b52ae9908ced79f9b7f83dfecc8e431509f86bea368058f520f06e6f04 +9a72b26852e51cce8377a2c4058346a760ab824bd3f2055732e70a9adb597f52, d2c2c210eb71cec68ecb8eb5763b48c73426545bc11c244f3679e399fb06aa4d, 906f9fd06d7e5256926e087d55aaae229796e57ebec013a6df703f4a7e4ba2bf +88dc10906c6bebc22bca3241427fcb090cd1b5b14790a16c9a7bb1cd735d8f55, 9a2b1509e855879310095f3528891127db71d59da54bb7906fbcec82c18761c7, 7bf6fe0c68a45b98e4868b1e604ac61467c32e6445df7f61e0fa8c26232673d0 +ed5813590470914c2fe3f69ac67f85f424060b406da58dd868ff4b64fcd589aa, 1bb189b511858270dbe10a5996e83bdf95ca162805d1e82b81d67f3d01f037ea, 0d052eec1b3a2a2265af54fd415cd3352fdfebaf7bf57a7a3b0965c4f8d10511 +e5cb6a10359814aed137a987f7ff29b9b79e13161367858c325ff4e3cf09675d, 329334af14a1c97a1a98730cf172590239f44a065c446b31e1dbb5032f2bd6c9, edaa088182ddc36e15f49dc6c9541aa5e3a0826e0d234f2c604f359ae2b78320 +30c5dd294a49abe4b74a101640845ec029f94ef1d00c22c9e624196cc95a4d53, 4de03dbf26077c929ddae08e31a6cebc093da6756fabf61cfa500ab95fa8866e, 8bfb8ffd3a314c88fcba94c180bc7adce5abf84763c1078857157f3caefa0aff +a3830eb3f04f7361481caac17488bbf6b00d49031bd7ae5cbb691b6081e88f28, 866cd264547e5c3fcc7ae06c14fbfcd40e773088ad4783563c45b74390d4e4fd, d78dcffe89d52307b08e5fac364693f220e4cc01c85d1a88df880cca104becbd +9919aa5cee30d867a187ad687846442058e8ad616a52f48578e59e87993c409a, c556d1f7f59a0d62bebf1b36e4b6935312216dc64cb7d1546334e8db245ca930, bf57b1d097537b39ee8e83318a7a69043ddaf75428245e71ceb2cdbb05773789 +39610c4433b295fb61877039122e40353d41d483c2caffcad979c60b21d51928, 4c5cd01ccc4e741774fb74092a1e3432cda16b9906d722a92ca2ba57aae97694, 6b0305a8c4cf240e3a621f281df142ff1dccccf277ff879d95497c51a033f88e +09dd9f1a244b03032379a9b8dd89d8fcf4e296fe300f7a237595932898351533, 1a0859ed9c9499a5f168a4cd03673340780c446a5b0a03f93635b19a76956922, 67592d8260773c4c8e003e104d844583e65f4a3e517edfb736c4cd0170116ad0 +a34f5e1d0ffc653c05e14967830d1a35afb9d516229260404f1d583b3fe54451, 62d8d7d5ec708414e38aeb42075ab3d9d8ff204fc65ae17acc8021ed7830081a, 06e843ea8f6ca0fedf2819e053f84269b695041f53af744667a1fd16a13ecb0f +fb288aa8b0b342d4df3b4bd1cadb71d7181ae697b3922bf9bd67a8ba78b03ef3, 94e7ba899ec6ef40ed851f24db3d0c9fd5f8afdc35e5dbeb97fd7b5bbef8faa5, 44b0af56703ba02c8318815e601e0e27943162b2484b6951de4b248220fa0bf1 +96c42493b136f39f734484bef918b055b662c88d0569d0972ebc340d6e431f6f, 09bbed24ab46fbf9eba629c44ca02a8dfb3d927dd9d639e75c24a1f896e89ffb, 0cbf9f0e61a5e31da16787243bdd29036367fcfb6c67ff8aed343058096e3fce +d1b5fb7d8b35ee6fa235aa76be0d67fb3f8e3eb66f00496350bea43fe5b84604, 15d3a73e7271b1811708a1840c3804f84866d95eadb59b3c5f810ab00f14b2ca, 3ca42232b07a2d5cc24dbc933525f3d5dce5f5489229a4ebcd37d17bc72e2780 +38a4507fd8c05e2c8dcbddf9f11e8e02e1303f8e6af0b1ecd0840510bce6e94b, 1231645d0b136810bad8f610c1b60ac1bf10fdfb9da2cd4a8991a88b765b66c8, eda9c2a2635996ca8db64fd94885580cc85731e2e8fcdd2435fee8bdf8561670 +1ef1e927822aec3fb082e001cde47190c3c2e5b386783dfe2da3dddd388a40d0, f31ec8156dd1b4855e13bbdf3c5e36c7897257e4c1f68b3289d0aa496e655767, 6ee3b5c862d9c54560d1b6478ea7775baa29e3a27f2bb9afd578d370a9eb2caa +747d64c0f3df3c0bebec1b613b7837c0fd7a4e1613ebe0dd47f4f0a3b7ba86f3, 4a8d69c0ed8cc7db1c66c125b11071f713f1e9264d6b30166f84d7cc2c756ef5, 5007362c4afda612f7aff0d2191bfb64436834bd5c407a3bb2300a54fc88e87b +a5bb197ba47605873487ac1f17ba5fc26349e3f1eb72bdd400c33671d91f0cb3, 0693facca3e470793786c55e5ed16b17ce4ea28ea3b9aac71adfe93dbb45a78d, be9efde18b1c557926d1bb83de68f3f132c542eda7ef7752fa380110f870df9b +5eaacd8809df848778e17fc836ba70ce97c7e2f2cdd9fc398524711dc4685621, d43b24a33afb37382cea46d8ca34210676035c1170a6bcf11d81dcc4e126549a, 065529d4aa1a7b0057ba8c0fb5cf49b5d103989cd9dfce9f563313aabd4a0608 +76233f983f5fbfdc9034515383e0a05aedbe26819a11dfc0646b225a8e3916ce, e37c44ed9fe67b0c9c21cada3612130d8a23679bb15a1c735defa09addefbaa4, 0be6d49ab7c145771f9c96e2ce52dc7cf89bfe3de9b157699d7a30425ad05630 +3370e3eaa2ab6507745c0134d43a280558093e8bd64edbe549a1ed9494dfedba, 0d829439c3f15a9734e1e74436e0dac7a758fff116f6625ce99a98bf539bf9c0, 5f200565dec0b3443c60667b6280fef8fb8711dc80c1d972ece1583f0eb58b0a +a97ad0e703f0ffb017a464f9ad9ccd2e9377c902c831d6b0bffaa49b6b7af548, 81957929d85999c0beea37fef876f08dc030bdef2a07664c9280b4622518aac5, ca0f0326128c0aa46cae04610a08c0e36474ad052fabaf7cc888ee443b09c559 +800b3a3bc73573a6802d79bd1609a57f88ab16df9e0d8fb9741a2f576d046d13, 2f4c56116f1496ee24703dfcf021d49a0093dedd8a9d1a692b8318813c4345f4, 0ef9c1f225f180d8bf8ec3340deab2f5fb5f0bb5fe2bc57fb4c9bfcfad0625d0 +bc30737dbdf21dfea2b3fc09da649fdbc0232f2411c192322dc41087e2b5684f, 0d7cb63e218ed54dcd82eba02385b5acaac0b3db463197bf32bdbb313b0b7f58, 4ef1f2e30f02da171093f742c7db6ab1808bbdd60851808ecec921d377aa5a66 +75e7fc921cfaa4633cd04e7210354d40e7bb594d64536c38bdf9c320e283eb09, d6f069952a64023e8a30a8a01880220d2c2fc287f346743b8be79a7671101825, 7dcd3bcba0395c01b68a235bf5fa6d542e1a6d77f2ccc9c1b1e084b004c1d8f3 +3b9bad9421464824b66220da821f925a51ac838c5ea9f61dd37ffb639338fe65, 55e2775c0837cccf90d49aac62f11936cf43b6a6dc8b9bdccbf0974a9895b860, df7a5ef5d9d36889be44abbeffd7f9df279a8301c1ca154b2cb5f6bc28c6f983 +84557eecc6ea62237c2085deac96c74a0f06462f46a4a17398a2bef1093049ad, 0321d16e6099548e59d7c2f8a68a7d66270539c9cc4419e12759f89220ce835b, 0e55b11f1b9f6f2b852fec36a06c6579d60cf7cbe60612a14a01d1609761f4d8 +1ba126ccd1b3f7b1b26262b6ee9706ec6dfc870271ae379597086dfec205fbc2, 401761f50703f1a7e1497668e77ad4c95d6247f1bee168704dbc9e87fa926eda, 17c876f11e4d2cf3be58944c8dc63d38c2cfd3db1925201db826240818b8df2a +8224f769799a40e2e17087710695576456bbb346ff39bd23afc75a6f2596a4f0, 179cc98c1ff592cf4d446b95664c5533711800ca9f33801e474f037ac36a52ff, 98c9198bb56297dcc7842a2b498a1ba8bccc2c0acfc6e2d59798c195e50c8717 +5b1dc56ec8ce89191e9c5f6d06ffe4b2310a1790e113c55ed8fbd9d2b66f4b87, 70501a6071777bbf4b22bfe42b6deb74b67a0f286e97e077888a891335c620eb, 88bb08213efa2a4fc6780b5df0dcc4c79485cf85a407d858f676e2ca356132f6 +a83e26856a4e4c3400e8fd28ec61a87801054be560203164d3e54ece579d229b, 904a4ce1df032cb5bd581567a08bd548de2b7ef83dd6097a14dcd0a3ead32920, c5780e0bc53bacb1d2874344f2095b560babd2bf6faf4a662d8e23ca724b43f1 +a6f4cd59810c590fb39b897eda9b4756e2c700355b34d005ff234422e3d91b18, d94c35faf30263099df9bfd87ae8af055eec50ff6d784589376a4b9ed059c878, dafd7a25524d31f42998fc377c9e078568381dfc7ea853fa6a365e7bb87fe9ff +1bfadfb7429ed9aa7c3e4523dcf5a828632ce5faae9a8efda8d6ffdd74c6fa24, bb7f38f4444c4a75819fa3d46ff4abe42179b516ddc2b5790b888e92b7aeacef, f43881d19c5d772618c017bdd7e998c948559a21ed2ccd0a104b215a282b6f02 +5e41d7d408c8358a1426f29e0a764b60223aec2351864d84dd3a779d0aaa09f3, 1b0fef36cab9f932f3fc4adf3924c08faf1928fae0cf75e549d4df0e19d40e67, 78f041e8e458c0fd266be2c70f50be02346aebdec1a6df7cad76490fd5992ece diff --git a/test/data/scores_addition.csv b/test/data/scores_addition.csv new file mode 100644 index 000000000..5aa5769d2 --- /dev/null +++ b/test/data/scores_addition.csv @@ -0,0 +1,1025 @@ +4-3-50-64-100-50-36, 8-5-120-256-113-100-171, 14-8-150-512-172-150-600, 18-10-200-1024-228-200-600 +28, 682, 66416, 1283571 +30, 702, 63538, 1301601 +26, 647, 66304, 1314976 +24, 667, 64428, 1288193 +28, 634, 66832, 1321928 +28, 715, 65520, 1292861 +30, 668, 65772, 1285605 +28, 637, 64367, 1308512 +24, 718, 65062, 1316256 +24, 640, 65127, 1329976 +26, 660, 67472, 1309517 +30, 610, 64966, 1361152 +26, 748, 64768, 1325100 +28, 704, 66802, 1317252 +28, 652, 68400, 1309717 +32, 616, 65867, 1334816 +22, 692, 64736, 1328712 +24, 633, 64318, 1305683 +22, 668, 68104, 1340880 +24, 688, 68512, 1291670 +28, 674, 65135, 1330138 +28, 630, 66020, 1309504 +24, 702, 68616, 1276059 +28, 644, 66430, 1274860 +26, 720, 64960, 1331512 +26, 660, 62840, 1355506 +28, 665, 65582, 1333088 +22, 665, 67209, 1329600 +28, 669, 67076, 1331636 +28, 664, 67783, 1271267 +30, 648, 65610, 1289288 +26, 660, 66116, 1335335 +24, 644, 66144, 1356608 +20, 642, 68192, 1313408 +26, 658, 66678, 1270965 +26, 662, 67488, 1352880 +28, 678, 67660, 1311585 +24, 589, 65756, 1296210 +32, 624, 66956, 1295984 +28, 644, 65360, 1296090 +28, 716, 64944, 1297520 +26, 640, 64938, 1263843 +24, 697, 66358, 1306240 +26, 693, 64672, 1320208 +30, 674, 68043, 1331591 +28, 688, 68304, 1285812 +24, 640, 65958, 1321510 +24, 615, 64064, 1349360 +28, 638, 67016, 1336864 +30, 716, 67032, 1331360 +28, 665, 66190, 1300828 +30, 641, 64121, 1336409 +22, 644, 66416, 1325336 +30, 637, 61786, 1329824 +24, 698, 63098, 1268192 +28, 680, 65904, 1328354 +28, 644, 64969, 1310384 +26, 640, 66786, 1286465 +30, 720, 65615, 1286803 +28, 704, 66700, 1322128 +30, 710, 65182, 1323520 +26, 683, 67902, 1311200 +25, 686, 64152, 1292614 +28, 640, 66817, 1341696 +22, 636, 67785, 1288112 +28, 679, 63725, 1355736 +30, 683, 63496, 1300480 +30, 640, 64635, 1296379 +28, 633, 66668, 1285018 +24, 642, 67584, 1344814 +26, 660, 67816, 1279632 +28, 657, 64990, 1330496 +28, 664, 65312, 1355520 +30, 654, 63792, 1314839 +24, 622, 64280, 1277120 +28, 611, 67904, 1299509 +26, 701, 65456, 1309256 +28, 690, 64445, 1324198 +30, 660, 66384, 1336576 +22, 684, 67680, 1334012 +20, 608, 67312, 1326864 +25, 665, 66928, 1317492 +32, 670, 66379, 1313410 +28, 652, 65936, 1318056 +32, 706, 65258, 1323340 +30, 693, 67085, 1320795 +24, 706, 64473, 1315072 +30, 741, 66448, 1307425 +28, 678, 67320, 1357312 +24, 690, 66110, 1337766 +28, 660, 66295, 1300768 +24, 668, 67704, 1282720 +30, 632, 65168, 1326864 +24, 661, 66784, 1362480 +22, 662, 63968, 1346904 +26, 669, 64837, 1332920 +28, 636, 65000, 1322202 +30, 685, 66038, 1339904 +22, 624, 69920, 1297477 +28, 669, 65551, 1312064 +28, 602, 67421, 1331200 +18, 634, 65659, 1325885 +26, 643, 66211, 1311028 +24, 648, 67160, 1312804 +28, 716, 65011, 1321747 +24, 663, 65088, 1336800 +30, 685, 66396, 1305792 +30, 694, 66160, 1297696 +28, 655, 67024, 1313608 +26, 667, 66216, 1342480 +30, 686, 66235, 1307299 +28, 645, 64017, 1271930 +28, 668, 63439, 1347473 +30, 676, 68374, 1320624 +26, 666, 64619, 1287665 +26, 675, 66556, 1318736 +28, 688, 65739, 1312320 +30, 662, 63618, 1338750 +28, 634, 68230, 1310240 +28, 754, 66714, 1322152 +26, 628, 67774, 1328303 +30, 602, 67088, 1329712 +26, 630, 65680, 1313256 +28, 665, 64136, 1290353 +22, 692, 68728, 1307660 +26, 671, 65542, 1314176 +24, 662, 66976, 1312974 +29, 684, 63687, 1309184 +24, 712, 66352, 1328192 +26, 724, 66556, 1306086 +29, 642, 64880, 1321571 +28, 700, 65454, 1348512 +26, 676, 68400, 1342765 +28, 634, 64893, 1326384 +24, 657, 67184, 1311423 +28, 648, 64302, 1314368 +30, 648, 66784, 1344064 +28, 693, 65728, 1349531 +20, 681, 63160, 1291217 +28, 640, 63952, 1365037 +25, 616, 66120, 1340416 +26, 665, 65429, 1290752 +25, 706, 65198, 1311920 +26, 672, 66052, 1358488 +28, 673, 65772, 1312587 +24, 693, 62752, 1300959 +30, 670, 67121, 1307448 +28, 674, 65806, 1312008 +28, 667, 66144, 1346328 +28, 664, 68690, 1306856 +20, 652, 63280, 1320213 +22, 688, 66384, 1323688 +26, 661, 66364, 1314343 +23, 662, 66732, 1311360 +26, 660, 63776, 1324176 +28, 608, 66576, 1301148 +24, 672, 66969, 1351712 +30, 683, 66633, 1307308 +22, 657, 67940, 1330816 +32, 710, 67726, 1316264 +26, 656, 65680, 1317648 +26, 637, 64736, 1324552 +28, 670, 66775, 1301842 +29, 664, 65872, 1354304 +26, 701, 65880, 1341368 +28, 656, 65771, 1317742 +22, 674, 62987, 1297944 +30, 662, 67445, 1290324 +30, 713, 65698, 1359376 +26, 625, 64336, 1309728 +24, 593, 66818, 1272161 +28, 704, 67128, 1368890 +26, 666, 68048, 1304116 +22, 656, 67312, 1309436 +26, 634, 64815, 1326717 +20, 680, 65104, 1285574 +30, 640, 65999, 1333792 +26, 640, 64672, 1299211 +30, 670, 66559, 1276638 +30, 681, 64010, 1285968 +26, 737, 64112, 1337368 +31, 716, 69626, 1308288 +28, 681, 64801, 1328387 +32, 685, 66176, 1337004 +24, 692, 67128, 1317696 +24, 736, 68607, 1329814 +22, 688, 65536, 1311247 +28, 691, 66960, 1305565 +28, 710, 64997, 1303087 +22, 681, 66380, 1311448 +26, 666, 66734, 1340811 +20, 646, 64941, 1337184 +28, 658, 65412, 1292424 +24, 690, 66323, 1340528 +30, 682, 69472, 1283726 +30, 633, 65199, 1300671 +28, 668, 69205, 1357319 +26, 611, 65690, 1302360 +28, 664, 67088, 1312256 +28, 648, 66039, 1340208 +30, 682, 66240, 1291776 +24, 680, 65479, 1317206 +28, 685, 66885, 1296433 +26, 663, 64624, 1337392 +24, 709, 65987, 1305092 +28, 710, 65048, 1287764 +28, 682, 66592, 1309147 +28, 647, 65739, 1294067 +30, 644, 61816, 1299511 +28, 688, 64698, 1339034 +26, 624, 67161, 1311498 +26, 692, 68112, 1340220 +22, 648, 65551, 1309456 +26, 642, 64264, 1300172 +22, 650, 65664, 1326612 +26, 681, 65794, 1328768 +26, 631, 67168, 1320280 +28, 683, 67024, 1290723 +24, 614, 65321, 1335120 +28, 648, 69600, 1327232 +32, 703, 62888, 1343732 +28, 656, 68544, 1297363 +28, 678, 65917, 1336952 +28, 655, 65472, 1307044 +26, 677, 64440, 1304342 +26, 662, 64362, 1312107 +24, 703, 68192, 1311392 +26, 668, 67152, 1324818 +28, 685, 66816, 1331028 +26, 642, 65440, 1310056 +26, 700, 66414, 1313568 +28, 727, 66186, 1341517 +28, 646, 67408, 1276080 +30, 685, 65895, 1313088 +28, 654, 67139, 1316065 +30, 712, 63584, 1328974 +30, 655, 65807, 1306208 +22, 716, 63684, 1309237 +29, 676, 66370, 1324852 +32, 638, 65364, 1293264 +32, 686, 64536, 1343696 +26, 684, 67376, 1302826 +24, 598, 67156, 1328906 +28, 716, 64008, 1308640 +28, 698, 61862, 1292850 +30, 728, 67377, 1291264 +28, 692, 66016, 1316160 +30, 670, 66457, 1264827 +28, 669, 67509, 1303250 +26, 730, 67013, 1344368 +24, 632, 66844, 1319520 +26, 644, 65040, 1302131 +22, 696, 66117, 1348096 +20, 640, 65508, 1285365 +30, 678, 66207, 1325198 +28, 718, 66825, 1331536 +32, 661, 66011, 1325036 +30, 636, 68067, 1321532 +28, 653, 68320, 1298386 +28, 750, 66536, 1317253 +26, 657, 64897, 1320184 +24, 704, 63946, 1330492 +28, 609, 65860, 1347648 +24, 664, 64320, 1316896 +28, 654, 65059, 1305703 +22, 628, 66403, 1301408 +26, 646, 65686, 1300938 +30, 693, 68288, 1302463 +26, 643, 64453, 1312439 +30, 679, 66407, 1315888 +30, 668, 64488, 1354256 +22, 633, 66668, 1334736 +24, 624, 66010, 1331235 +28, 616, 66190, 1322031 +30, 628, 67680, 1268868 +30, 629, 66700, 1323096 +24, 662, 65200, 1308803 +30, 656, 65372, 1320808 +30, 683, 65532, 1312535 +24, 641, 65010, 1311367 +28, 636, 66463, 1311226 +26, 654, 66688, 1293520 +26, 712, 65241, 1304717 +26, 643, 65559, 1323416 +30, 667, 64768, 1314248 +28, 682, 64452, 1366592 +30, 580, 67124, 1289632 +26, 644, 67152, 1287616 +30, 648, 67312, 1353856 +24, 660, 64798, 1315888 +20, 669, 66312, 1311272 +26, 710, 66544, 1312544 +26, 644, 66464, 1326382 +32, 737, 64927, 1328880 +28, 659, 65451, 1317906 +26, 647, 64840, 1269152 +24, 654, 63544, 1324288 +32, 636, 66776, 1331398 +22, 625, 66699, 1303145 +28, 703, 66463, 1327736 +22, 656, 63719, 1356452 +30, 726, 65666, 1302528 +32, 646, 69056, 1326720 +26, 697, 65476, 1305343 +28, 599, 67061, 1304532 +18, 676, 64337, 1316116 +26, 673, 67440, 1314953 +28, 714, 66057, 1327440 +26, 660, 65411, 1334688 +32, 748, 64142, 1351609 +26, 629, 63225, 1314360 +26, 686, 65808, 1327577 +30, 664, 67392, 1302806 +30, 728, 64347, 1307276 +26, 720, 64920, 1299448 +28, 663, 65889, 1277056 +30, 616, 62103, 1321252 +30, 704, 67320, 1317344 +25, 662, 67061, 1293696 +26, 666, 64398, 1331736 +28, 680, 64301, 1335127 +28, 700, 67816, 1306408 +30, 652, 65878, 1349028 +32, 688, 66483, 1326125 +26, 646, 67885, 1316823 +26, 674, 65209, 1323296 +28, 704, 65070, 1345203 +26, 651, 65564, 1309248 +27, 634, 64682, 1267840 +28, 650, 65012, 1323755 +28, 693, 65768, 1341185 +26, 660, 64639, 1337656 +26, 606, 65314, 1304716 +26, 644, 64448, 1301376 +24, 642, 65778, 1294720 +26, 688, 66000, 1330152 +26, 672, 67472, 1349233 +28, 668, 65088, 1324544 +24, 652, 63534, 1335152 +28, 640, 64625, 1352064 +20, 705, 63000, 1326656 +28, 659, 66211, 1313964 +28, 674, 69388, 1300224 +28, 685, 67165, 1321456 +30, 644, 66506, 1320720 +24, 718, 67869, 1307547 +28, 639, 67731, 1327881 +28, 662, 68992, 1322792 +24, 682, 66336, 1300876 +26, 640, 63568, 1306918 +30, 684, 64011, 1319835 +30, 668, 65259, 1298921 +24, 647, 65728, 1300960 +27, 682, 65872, 1316464 +28, 634, 63434, 1312082 +20, 672, 65840, 1292032 +28, 636, 65968, 1324095 +22, 692, 65858, 1326464 +30, 694, 63633, 1317792 +26, 686, 65947, 1298048 +30, 641, 65778, 1291440 +28, 660, 67688, 1322386 +20, 668, 68224, 1283870 +30, 605, 67073, 1299376 +22, 691, 64726, 1312764 +30, 664, 65563, 1315290 +30, 682, 63374, 1345368 +26, 669, 64219, 1318720 +28, 648, 67056, 1309259 +26, 672, 67588, 1296179 +22, 648, 64756, 1306165 +26, 688, 66924, 1319320 +30, 737, 65391, 1337248 +32, 654, 64851, 1291823 +28, 669, 66561, 1294298 +26, 653, 66244, 1285015 +26, 668, 66605, 1309461 +26, 647, 63345, 1358016 +22, 671, 67445, 1303242 +22, 672, 70144, 1267699 +30, 636, 67392, 1286212 +26, 624, 66896, 1334367 +30, 708, 65616, 1324695 +24, 620, 65013, 1333579 +30, 739, 65536, 1314490 +24, 656, 65524, 1278698 +22, 713, 67904, 1303368 +22, 642, 65460, 1237428 +24, 609, 64918, 1308564 +28, 608, 65712, 1323744 +28, 655, 65608, 1288824 +24, 667, 65470, 1314592 +28, 672, 64375, 1308343 +30, 672, 65793, 1313108 +22, 690, 65896, 1354272 +28, 643, 64656, 1284424 +26, 630, 64904, 1319035 +24, 670, 66192, 1316832 +22, 680, 65736, 1312764 +22, 693, 64128, 1299834 +20, 692, 66122, 1319088 +28, 680, 65470, 1338127 +32, 649, 63862, 1283968 +20, 684, 68852, 1285184 +32, 654, 65711, 1301472 +26, 592, 67210, 1295675 +26, 654, 66498, 1306733 +28, 685, 66133, 1329248 +26, 671, 64652, 1323936 +22, 640, 66444, 1328000 +28, 699, 65408, 1302612 +26, 589, 65965, 1303686 +28, 716, 67455, 1311520 +30, 688, 68288, 1292575 +22, 612, 63736, 1317920 +24, 642, 65994, 1304192 +22, 634, 65553, 1321312 +26, 693, 67334, 1314544 +28, 689, 67703, 1302919 +22, 738, 65554, 1338427 +28, 678, 67881, 1333554 +26, 658, 66176, 1306384 +24, 673, 64800, 1307547 +26, 654, 65394, 1344912 +26, 659, 65592, 1308002 +28, 591, 65336, 1321224 +28, 678, 65952, 1326442 +22, 663, 66588, 1321410 +26, 679, 66451, 1343000 +30, 680, 68432, 1308985 +30, 627, 64448, 1294334 +28, 638, 65445, 1294016 +28, 599, 63873, 1344960 +22, 648, 65011, 1274979 +22, 668, 66613, 1297450 +30, 692, 67519, 1312453 +24, 676, 64388, 1292581 +28, 696, 64928, 1319044 +20, 660, 64760, 1289750 +32, 692, 65248, 1324313 +30, 666, 65004, 1310768 +28, 704, 66080, 1307488 +26, 682, 64464, 1288163 +26, 659, 67632, 1326576 +24, 641, 65616, 1315874 +24, 692, 63467, 1293110 +20, 644, 67213, 1292791 +32, 646, 66368, 1296032 +32, 690, 63506, 1295536 +22, 684, 65453, 1310518 +30, 676, 64105, 1322127 +30, 660, 65029, 1318697 +26, 619, 66672, 1327040 +18, 666, 65068, 1295090 +26, 674, 65136, 1288681 +22, 571, 64201, 1311096 +29, 648, 67823, 1329890 +28, 630, 64994, 1293728 +26, 664, 67056, 1303616 +28, 676, 66214, 1331520 +28, 685, 62528, 1293254 +28, 659, 65745, 1321984 +24, 648, 64032, 1316341 +24, 662, 64841, 1314868 +26, 635, 65521, 1314264 +26, 664, 67712, 1285522 +28, 687, 64296, 1320048 +28, 674, 67152, 1336505 +30, 672, 64284, 1285004 +30, 699, 64832, 1319266 +24, 766, 63840, 1329728 +26, 609, 64846, 1300928 +30, 668, 65584, 1303315 +26, 680, 65840, 1303864 +26, 610, 65156, 1356672 +26, 692, 67664, 1293472 +28, 658, 65428, 1336506 +32, 658, 65790, 1313920 +22, 689, 67131, 1307671 +24, 636, 64636, 1323928 +26, 688, 65448, 1328128 +30, 672, 64865, 1332944 +32, 645, 64552, 1265540 +26, 694, 66336, 1309437 +30, 665, 65216, 1301536 +32, 640, 67216, 1271936 +26, 748, 68800, 1304736 +24, 700, 66568, 1339536 +32, 600, 63486, 1289922 +28, 660, 63869, 1319657 +32, 600, 64206, 1312320 +24, 731, 65733, 1284667 +26, 645, 67672, 1319858 +30, 678, 65800, 1310448 +22, 684, 65479, 1336964 +26, 698, 65126, 1323408 +28, 684, 62431, 1343008 +28, 697, 64968, 1307178 +24, 633, 65212, 1339296 +24, 676, 65488, 1343436 +20, 666, 63839, 1356512 +28, 618, 64848, 1331499 +28, 724, 66589, 1291508 +30, 666, 64861, 1326369 +28, 644, 65117, 1295992 +26, 626, 67571, 1299104 +30, 675, 65851, 1309641 +24, 609, 66090, 1304263 +28, 670, 66881, 1328919 +28, 711, 64982, 1343584 +26, 652, 63612, 1290011 +24, 645, 66637, 1318981 +30, 689, 68303, 1322891 +28, 712, 66408, 1324200 +26, 687, 66632, 1344258 +29, 640, 64168, 1300352 +28, 604, 65870, 1316292 +28, 659, 66656, 1294938 +22, 692, 65271, 1321728 +30, 616, 63582, 1301568 +24, 622, 63265, 1331920 +26, 652, 65224, 1338573 +26, 678, 64344, 1323704 +28, 612, 67800, 1316632 +32, 694, 65102, 1331856 +23, 668, 63826, 1335776 +30, 690, 66033, 1280382 +28, 704, 63907, 1342492 +28, 653, 66904, 1309408 +26, 704, 67776, 1313984 +22, 698, 67312, 1343616 +26, 675, 69111, 1342470 +28, 653, 66206, 1285752 +30, 676, 66492, 1305407 +28, 656, 65113, 1336872 +26, 626, 66128, 1270154 +24, 602, 67501, 1289828 +28, 661, 63556, 1315933 +32, 724, 66540, 1318980 +30, 646, 64672, 1326709 +30, 695, 65030, 1288736 +28, 706, 65424, 1319025 +26, 670, 68272, 1313197 +28, 656, 65828, 1331924 +28, 632, 64882, 1309412 +24, 620, 63895, 1289301 +28, 686, 63593, 1341440 +28, 721, 67437, 1340280 +28, 647, 64728, 1330000 +20, 667, 67815, 1323808 +24, 637, 63721, 1322752 +28, 639, 67184, 1301240 +30, 680, 66348, 1314400 +30, 659, 65576, 1303364 +26, 661, 66393, 1315720 +27, 650, 64477, 1295265 +28, 676, 64928, 1284141 +28, 697, 64064, 1293926 +24, 658, 67328, 1286464 +26, 682, 66914, 1294903 +28, 637, 66960, 1299233 +24, 646, 64911, 1320163 +32, 688, 66722, 1313078 +26, 629, 66771, 1314592 +24, 695, 66424, 1347008 +30, 672, 64906, 1319513 +30, 664, 66093, 1310208 +24, 660, 64758, 1325580 +28, 660, 64009, 1323136 +30, 697, 66808, 1291358 +30, 611, 65965, 1342136 +26, 679, 64583, 1312688 +30, 674, 63756, 1305553 +30, 675, 63856, 1307584 +28, 654, 66538, 1282384 +28, 716, 66208, 1312684 +22, 660, 64408, 1308704 +22, 640, 66040, 1330413 +28, 652, 66258, 1341698 +24, 669, 66786, 1324864 +24, 624, 66388, 1334623 +26, 654, 66960, 1333764 +26, 676, 66960, 1312836 +28, 647, 65265, 1295777 +28, 656, 65848, 1334656 +26, 670, 62988, 1311660 +30, 664, 64426, 1263358 +22, 615, 65099, 1291614 +30, 704, 63831, 1324561 +28, 700, 65486, 1280555 +28, 640, 67165, 1330931 +26, 632, 66384, 1336424 +26, 674, 68704, 1262880 +26, 670, 65682, 1300528 +28, 646, 65900, 1323216 +24, 704, 68276, 1309440 +32, 688, 63864, 1300105 +30, 603, 67845, 1312448 +22, 691, 67784, 1312890 +28, 695, 67376, 1342194 +28, 663, 67947, 1314626 +28, 661, 65156, 1349873 +24, 616, 68400, 1319200 +26, 709, 66567, 1329648 +26, 644, 67178, 1306449 +28, 664, 66024, 1334950 +26, 576, 67266, 1288800 +28, 678, 67128, 1338591 +30, 629, 64296, 1319756 +28, 656, 66566, 1294832 +30, 661, 67960, 1314055 +30, 686, 66136, 1353366 +28, 666, 63736, 1304115 +28, 690, 67303, 1302304 +30, 678, 67203, 1336498 +24, 714, 64956, 1340960 +30, 614, 63114, 1328608 +20, 648, 66513, 1338688 +24, 680, 65517, 1306593 +26, 637, 65552, 1299681 +28, 612, 64674, 1319344 +28, 663, 64052, 1335804 +24, 630, 64725, 1326008 +22, 638, 63560, 1321440 +30, 776, 66600, 1338912 +30, 648, 65526, 1292499 +26, 656, 65344, 1324352 +22, 716, 64996, 1310304 +26, 722, 65772, 1296656 +26, 676, 64209, 1280113 +24, 649, 66099, 1300992 +22, 634, 63664, 1290171 +27, 663, 64771, 1306633 +30, 654, 63994, 1306640 +30, 665, 66820, 1311527 +26, 674, 65879, 1298284 +26, 684, 66704, 1271339 +28, 694, 66193, 1313280 +28, 703, 68338, 1314261 +26, 676, 64084, 1311862 +22, 670, 66016, 1310839 +24, 658, 64172, 1335899 +28, 665, 64945, 1305368 +26, 638, 62842, 1337386 +32, 668, 66368, 1325857 +24, 692, 67432, 1313216 +24, 676, 62900, 1316352 +26, 606, 66110, 1299170 +30, 691, 64741, 1318601 +28, 674, 66626, 1313098 +26, 628, 65827, 1315298 +22, 704, 63802, 1302169 +30, 706, 65984, 1314381 +30, 669, 62273, 1329536 +30, 640, 64160, 1323680 +28, 661, 64298, 1307174 +26, 656, 62519, 1306480 +28, 663, 66196, 1340976 +30, 620, 63257, 1356356 +26, 666, 66071, 1282464 +30, 658, 65549, 1349434 +30, 661, 64825, 1333864 +28, 676, 65572, 1315805 +24, 672, 63623, 1322944 +28, 650, 66389, 1318779 +30, 612, 68792, 1302896 +26, 651, 66223, 1334736 +26, 638, 68032, 1349556 +28, 687, 66377, 1332413 +28, 710, 67824, 1302086 +26, 652, 66646, 1346056 +22, 590, 66963, 1299591 +26, 626, 64822, 1303484 +28, 620, 67448, 1285794 +26, 654, 66527, 1315888 +22, 594, 65728, 1346240 +30, 640, 65523, 1291260 +28, 669, 64373, 1314405 +28, 632, 67424, 1311164 +24, 660, 66871, 1328520 +30, 640, 65574, 1322270 +28, 667, 64208, 1349696 +28, 644, 63648, 1288331 +26, 640, 67288, 1330288 +30, 674, 67010, 1327868 +26, 667, 67264, 1312033 +24, 685, 66722, 1324189 +28, 688, 65998, 1287840 +30, 675, 66406, 1307854 +20, 642, 66603, 1292024 +24, 655, 64995, 1287256 +30, 633, 64219, 1291112 +22, 682, 65742, 1296304 +28, 644, 67037, 1309712 +30, 669, 67069, 1305271 +24, 684, 66564, 1337940 +31, 629, 64249, 1295616 +26, 680, 67558, 1325696 +26, 694, 64764, 1293365 +28, 644, 67045, 1341968 +24, 646, 64668, 1285095 +28, 699, 66239, 1323614 +26, 691, 64505, 1316897 +31, 626, 66184, 1340416 +20, 708, 66232, 1293824 +26, 687, 64528, 1306272 +24, 688, 65024, 1292816 +28, 701, 66142, 1297247 +24, 636, 66352, 1333184 +26, 680, 65736, 1333976 +26, 689, 65090, 1316534 +28, 688, 65168, 1299604 +26, 677, 65307, 1331200 +22, 648, 64789, 1337888 +26, 680, 66255, 1314560 +28, 697, 65396, 1317616 +26, 613, 65721, 1331509 +30, 674, 66330, 1310897 +20, 707, 64533, 1322478 +24, 690, 67712, 1318775 +20, 666, 63713, 1302036 +26, 622, 64281, 1347008 +26, 628, 65226, 1317776 +22, 698, 65700, 1318871 +30, 608, 65283, 1305890 +24, 632, 68386, 1316864 +30, 650, 65120, 1292549 +26, 626, 67504, 1293766 +28, 736, 67336, 1339577 +25, 674, 63728, 1283847 +25, 649, 66556, 1293120 +24, 655, 66340, 1311834 +28, 696, 66551, 1327120 +30, 684, 65692, 1321872 +32, 666, 65308, 1315246 +28, 704, 64348, 1319760 +26, 580, 66826, 1336192 +28, 620, 66496, 1302991 +20, 692, 68848, 1316104 +28, 678, 66198, 1329913 +30, 672, 63229, 1302704 +28, 717, 64559, 1305463 +32, 672, 62947, 1313504 +28, 696, 64544, 1325664 +32, 672, 66328, 1318528 +28, 668, 66819, 1294272 +30, 676, 65664, 1325904 +32, 642, 67364, 1289720 +24, 646, 69888, 1318544 +28, 664, 66064, 1308451 +30, 660, 66544, 1308736 +26, 662, 64084, 1291424 +28, 698, 67981, 1342329 +26, 667, 66495, 1339215 +22, 688, 67393, 1333040 +26, 642, 69280, 1316860 +30, 604, 67635, 1327232 +30, 695, 62056, 1264616 +22, 663, 68057, 1330156 +26, 631, 65378, 1297264 +30, 600, 66960, 1282624 +22, 724, 67432, 1332056 +26, 634, 68461, 1271384 +26, 648, 65953, 1306934 +28, 639, 66560, 1296321 +32, 646, 65104, 1297640 +30, 646, 66410, 1320860 +28, 634, 63407, 1318080 +28, 654, 65583, 1289244 +28, 650, 65788, 1280174 +28, 673, 63868, 1307620 +26, 606, 65429, 1346864 +26, 646, 67584, 1331400 +22, 654, 69320, 1312047 +30, 617, 67604, 1303504 +26, 636, 65637, 1298266 +30, 632, 68398, 1323375 +30, 634, 64232, 1291456 +32, 662, 65406, 1314321 +26, 740, 65230, 1302288 +24, 616, 66430, 1310715 +26, 672, 64849, 1332331 +32, 647, 64453, 1307591 +20, 659, 66089, 1271593 +26, 681, 67904, 1326286 +28, 605, 65160, 1328408 +28, 760, 65919, 1304211 +22, 624, 62823, 1268006 +26, 691, 67477, 1304094 +28, 727, 65678, 1318919 +26, 740, 66480, 1349213 +30, 697, 64031, 1339328 +30, 694, 66359, 1287558 +22, 656, 67253, 1286386 +30, 648, 65487, 1303649 +30, 683, 66360, 1344771 +20, 680, 67137, 1327023 +28, 620, 68771, 1306665 +32, 668, 66623, 1319938 +32, 664, 64759, 1340612 +26, 654, 66560, 1286118 +28, 649, 65942, 1335552 +24, 664, 66616, 1281240 +24, 692, 65362, 1316818 +30, 618, 62688, 1301296 +24, 654, 67711, 1329440 +28, 685, 66445, 1298216 +28, 716, 65931, 1275161 +24, 680, 62635, 1303767 +28, 696, 66816, 1353541 +28, 708, 66323, 1300584 +28, 661, 66659, 1337272 +32, 660, 64658, 1353216 +22, 672, 65418, 1301728 +30, 693, 62000, 1321863 +28, 650, 65284, 1314853 +28, 670, 64908, 1354000 +30, 707, 66211, 1314094 +26, 684, 66768, 1308377 +24, 692, 64384, 1301584 +18, 690, 68914, 1338334 +28, 654, 66384, 1251521 +26, 686, 66268, 1310187 +26, 644, 63788, 1308304 +22, 634, 62820, 1336096 +30, 682, 66518, 1312719 +22, 689, 65311, 1328448 +26, 664, 65381, 1317760 +28, 711, 66714, 1326618 +22, 698, 63200, 1327552 +26, 631, 65174, 1309352 +26, 674, 66498, 1306976 +26, 666, 66093, 1290827 +30, 599, 67392, 1326418 +24, 652, 65782, 1306560 +24, 613, 65318, 1314762 +28, 678, 66192, 1326584 +28, 648, 68480, 1297708 +28, 660, 65235, 1352640 +26, 704, 63960, 1305806 +28, 679, 66553, 1322578 +30, 648, 66098, 1311366 +28, 650, 69089, 1330112 +24, 608, 66944, 1352512 +28, 662, 65128, 1298820 +24, 650, 66805, 1328303 +28, 672, 64331, 1333184 +26, 748, 68568, 1310408 +28, 576, 66351, 1307740 +26, 686, 66182, 1299796 +30, 668, 66168, 1281887 +28, 727, 66388, 1288356 +24, 642, 65826, 1319256 +22, 672, 64475, 1310609 +22, 672, 66274, 1323536 +28, 618, 65521, 1302739 +22, 640, 69275, 1288653 +22, 682, 65302, 1315069 +22, 630, 66104, 1330832 +26, 625, 66040, 1304934 +30, 672, 66124, 1314641 +26, 662, 64697, 1300209 +28, 656, 64051, 1317649 +28, 678, 66717, 1349147 +30, 710, 65760, 1313709 +28, 703, 65728, 1273418 +26, 694, 67888, 1300448 +26, 666, 67638, 1313657 +24, 651, 63271, 1271767 +28, 670, 64633, 1329348 +26, 624, 67424, 1338506 +28, 660, 65232, 1315772 +30, 702, 64715, 1342976 +28, 708, 67072, 1325637 +26, 670, 64216, 1294687 +24, 693, 67307, 1287024 +22, 685, 65374, 1325833 +24, 702, 65553, 1290374 +20, 676, 64678, 1343720 +30, 692, 65453, 1343300 +28, 698, 67507, 1327040 +28, 666, 63376, 1319392 +24, 644, 64792, 1316668 +28, 672, 65496, 1315511 +20, 690, 67076, 1303640 +32, 692, 68348, 1306087 +24, 664, 63661, 1321846 +30, 642, 62988, 1314257 +24, 692, 65448, 1292841 +24, 676, 69405, 1326592 +22, 624, 67688, 1340985 +22, 608, 65272, 1298976 +30, 578, 67468, 1311040 +28, 600, 63862, 1313830 +30, 684, 64188, 1286719 +30, 664, 65013, 1321722 +29, 680, 67584, 1312332 +28, 676, 64170, 1286696 +28, 681, 65472, 1316663 +32, 666, 65816, 1352249 +28, 676, 67284, 1332001 +26, 672, 63222, 1288224 +28, 688, 62114, 1292348 +30, 704, 68096, 1323209 +30, 680, 66156, 1306624 +28, 664, 64022, 1325292 +28, 605, 64795, 1302732 +30, 636, 64744, 1295386 +28, 626, 65042, 1312352 +28, 702, 66884, 1327649 +26, 628, 64991, 1342144 +32, 676, 65572, 1281384 +24, 660, 65296, 1327063 +26, 674, 65844, 1326836 +28, 686, 63189, 1294764 +30, 674, 64173, 1321309 +28, 678, 66648, 1278308 +30, 638, 69232, 1311256 +30, 723, 63743, 1322544 +26, 650, 66607, 1346367 +28, 658, 65027, 1294768 +30, 632, 66456, 1338336 +26, 653, 68126, 1302325 +30, 719, 67119, 1265909 +26, 670, 64099, 1308208 +26, 640, 66160, 1331840 +22, 635, 67936, 1318792 +29, 660, 65161, 1333607 +26, 684, 64290, 1307072 +28, 663, 66356, 1316507 +24, 664, 66912, 1304832 +24, 652, 65444, 1291098 +24, 688, 67296, 1299377 +22, 671, 67609, 1306990 +32, 672, 64557, 1341099 +28, 679, 66079, 1329352 +28, 672, 67110, 1289236 +22, 634, 65672, 1304028 +26, 708, 66808, 1342079 +28, 714, 65876, 1330848 +32, 686, 67164, 1309656 +24, 709, 66404, 1329808 +22, 674, 67309, 1326240 +28, 715, 67181, 1285238 +28, 714, 66934, 1341351 +26, 697, 65912, 1305800 +30, 652, 64728, 1348032 +20, 698, 67065, 1280378 +30, 681, 64970, 1290124 +30, 712, 64818, 1296336 +32, 639, 66459, 1317366 +26, 620, 66916, 1353351 +28, 700, 66757, 1326812 +26, 704, 68961, 1337664 +28, 662, 65304, 1300089 +21, 692, 65296, 1319392 +30, 700, 64015, 1339328 +28, 694, 68640, 1294491 +26, 653, 65352, 1299075 +30, 646, 65744, 1322232 +30, 664, 68144, 1317376 +24, 650, 64647, 1311066 +24, 646, 65878, 1341320 +24, 663, 64390, 1324455 +28, 626, 65064, 1320640 +26, 620, 65872, 1334928 +28, 693, 66473, 1305664 +26, 628, 67200, 1293216 +30, 667, 66572, 1296400 +30, 674, 67362, 1327235 +26, 697, 65808, 1325768 +32, 680, 62112, 1344960 +30, 632, 67315, 1295665 +26, 685, 65689, 1310048 +28, 621, 65180, 1344951 +26, 668, 68928, 1315316 +24, 636, 65046, 1333804 +28, 616, 65536, 1333376 +28, 692, 66088, 1315072 +26, 696, 64992, 1285901 +26, 704, 64602, 1290424 +30, 724, 65728, 1350546 +23, 664, 66732, 1315376 +26, 688, 65099, 1270464 +28, 647, 64656, 1325259 +24, 647, 66664, 1287502 +20, 712, 67080, 1348608 +26, 650, 66807, 1365376 +24, 646, 65414, 1308049 +24, 645, 63111, 1324702 +28, 700, 66410, 1280960 +28, 674, 66415, 1330872 +20, 704, 65536, 1292065 +26, 684, 64560, 1298346 +27, 684, 67707, 1322756 +22, 691, 64613, 1292844 +30, 647, 66716, 1258181 +26, 672, 66756, 1318086 +26, 692, 66673, 1332342 +22, 694, 65623, 1320553 +28, 691, 66595, 1302226 +30, 632, 67685, 1289583 +22, 650, 64852, 1296906 +28, 627, 63819, 1291399 +32, 643, 64076, 1305598 +28, 650, 67461, 1338880 +25, 734, 66487, 1309223 +30, 677, 65915, 1332695 +28, 673, 65340, 1309269 +30, 614, 67870, 1334193 +24, 668, 65568, 1324434 +32, 629, 65517, 1335292 +28, 679, 66816, 1258780 +30, 690, 68018, 1331661 +30, 665, 64583, 1328110 +26, 703, 65366, 1284483 +28, 664, 67072, 1342368 +22, 655, 64601, 1328639 +32, 676, 67910, 1346684 +26, 648, 64302, 1335128 +28, 630, 65835, 1310994 +26, 654, 66997, 1318680 +26, 720, 65660, 1325616 +24, 597, 67014, 1342725 diff --git a/test/data/scores_hyperidentity.csv b/test/data/scores_hyperidentity.csv new file mode 100644 index 000000000..c4df5ffda --- /dev/null +++ b/test/data/scores_hyperidentity.csv @@ -0,0 +1,1025 @@ +64-64-50-64-178-50-36, 256-256-120-256-612-100-171, 512-512-150-512-1174-150-300, 1024-1024-200-1024-3000-200-600 +38, 145, 277, 553 +42, 144, 281, 556 +39, 140, 279, 544 +40, 138, 285, 541 +39, 148, 276, 542 +41, 149, 282, 539 +37, 147, 282, 546 +40, 147, 278, 549 +39, 145, 285, 541 +45, 142, 274, 538 +38, 141, 280, 541 +44, 157, 275, 544 +42, 141, 282, 540 +42, 143, 279, 543 +37, 143, 290, 548 +41, 147, 279, 550 +38, 145, 279, 541 +41, 145, 287, 555 +37, 149, 275, 543 +41, 147, 278, 544 +40, 139, 279, 558 +43, 143, 279, 548 +41, 142, 281, 542 +40, 140, 278, 544 +39, 139, 270, 544 +43, 147, 281, 553 +41, 142, 277, 563 +37, 143, 287, 541 +41, 143, 276, 548 +37, 145, 280, 546 +39, 145, 283, 550 +36, 142, 294, 538 +40, 144, 281, 549 +42, 144, 274, 545 +39, 146, 276, 547 +36, 144, 272, 555 +36, 140, 273, 544 +41, 142, 279, 548 +36, 146, 276, 548 +42, 143, 274, 554 +38, 139, 278, 546 +37, 155, 279, 549 +40, 144, 275, 555 +40, 147, 277, 547 +37, 144, 276, 554 +39, 142, 273, 538 +38, 141, 295, 545 +41, 144, 276, 545 +37, 143, 279, 545 +41, 143, 272, 550 +42, 140, 294, 547 +40, 145, 286, 580 +40, 142, 280, 542 +38, 136, 284, 546 +40, 144, 276, 550 +42, 140, 287, 546 +44, 143, 282, 534 +38, 147, 284, 553 +38, 141, 276, 549 +39, 142, 283, 553 +39, 144, 280, 545 +40, 149, 279, 560 +41, 144, 278, 544 +36, 144, 276, 538 +38, 142, 275, 537 +42, 142, 279, 543 +38, 137, 278, 538 +41, 144, 286, 545 +40, 146, 283, 549 +40, 143, 281, 552 +41, 142, 289, 534 +43, 139, 279, 552 +40, 144, 280, 548 +40, 139, 274, 546 +39, 143, 271, 540 +41, 144, 281, 541 +40, 143, 274, 546 +40, 141, 272, 557 +40, 144, 280, 538 +37, 146, 282, 549 +41, 145, 286, 547 +39, 140, 280, 550 +39, 149, 289, 559 +43, 147, 278, 546 +43, 143, 283, 544 +40, 142, 285, 558 +42, 141, 278, 540 +43, 142, 281, 559 +42, 147, 279, 549 +37, 143, 289, 546 +39, 138, 283, 549 +40, 147, 280, 561 +39, 144, 278, 543 +37, 142, 276, 546 +43, 142, 275, 545 +37, 145, 274, 539 +40, 140, 279, 541 +38, 150, 284, 546 +39, 145, 290, 540 +41, 141, 281, 538 +37, 139, 283, 547 +42, 140, 279, 540 +40, 149, 280, 552 +42, 143, 269, 542 +40, 143, 280, 541 +38, 139, 295, 542 +39, 140, 276, 543 +39, 150, 283, 542 +39, 138, 274, 537 +37, 140, 272, 546 +40, 143, 280, 543 +42, 139, 289, 549 +36, 140, 285, 538 +41, 141, 281, 555 +40, 140, 287, 554 +39, 140, 274, 544 +39, 141, 274, 547 +37, 144, 282, 558 +37, 149, 282, 545 +37, 140, 279, 574 +40, 140, 282, 561 +38, 142, 280, 551 +39, 139, 279, 538 +46, 144, 283, 538 +40, 146, 282, 555 +41, 147, 280, 541 +40, 146, 286, 540 +38, 142, 283, 544 +35, 149, 275, 550 +38, 143, 272, 548 +41, 146, 280, 555 +40, 152, 279, 551 +37, 143, 294, 544 +40, 147, 278, 560 +44, 137, 273, 550 +41, 143, 274, 543 +38, 150, 283, 545 +40, 142, 275, 554 +38, 141, 281, 544 +38, 141, 276, 549 +37, 138, 277, 568 +40, 149, 279, 560 +39, 145, 283, 545 +37, 139, 277, 548 +42, 147, 280, 539 +43, 159, 278, 545 +38, 143, 278, 548 +39, 142, 273, 551 +38, 146, 276, 544 +42, 143, 279, 543 +37, 140, 271, 554 +38, 146, 274, 543 +38, 146, 278, 542 +38, 143, 282, 545 +39, 146, 277, 551 +39, 139, 278, 543 +39, 143, 286, 553 +40, 143, 279, 544 +41, 147, 281, 548 +43, 141, 281, 544 +39, 143, 284, 544 +38, 141, 284, 548 +40, 141, 281, 550 +43, 144, 281, 547 +38, 144, 286, 551 +41, 144, 280, 545 +42, 144, 270, 553 +39, 141, 280, 545 +37, 147, 290, 555 +39, 145, 279, 542 +44, 142, 279, 546 +38, 143, 278, 553 +42, 140, 287, 548 +38, 148, 280, 546 +39, 148, 272, 538 +37, 140, 274, 553 +41, 143, 278, 547 +39, 145, 274, 542 +40, 139, 282, 539 +38, 143, 275, 542 +40, 141, 279, 550 +42, 144, 282, 549 +38, 153, 286, 541 +37, 140, 278, 545 +38, 143, 279, 555 +36, 142, 282, 547 +41, 140, 275, 547 +38, 142, 274, 545 +37, 148, 274, 550 +39, 146, 279, 548 +38, 145, 274, 549 +39, 143, 282, 539 +40, 144, 280, 551 +40, 148, 280, 565 +39, 146, 281, 549 +40, 142, 275, 569 +40, 144, 279, 565 +40, 147, 275, 545 +39, 148, 276, 542 +44, 139, 281, 547 +39, 143, 286, 543 +39, 142, 284, 550 +42, 151, 276, 535 +37, 139, 285, 545 +42, 141, 284, 543 +40, 141, 279, 546 +38, 141, 285, 536 +37, 143, 278, 542 +40, 150, 284, 556 +45, 141, 285, 541 +39, 144, 282, 548 +37, 142, 280, 554 +41, 141, 284, 545 +39, 138, 280, 544 +38, 147, 286, 543 +39, 141, 279, 544 +39, 145, 281, 547 +41, 145, 280, 547 +37, 140, 290, 545 +36, 139, 280, 540 +38, 142, 276, 544 +41, 144, 279, 545 +41, 142, 282, 548 +42, 141, 279, 547 +42, 144, 278, 548 +42, 144, 276, 535 +40, 137, 274, 549 +39, 142, 279, 543 +43, 143, 279, 538 +39, 140, 280, 563 +38, 143, 273, 552 +39, 146, 285, 555 +39, 139, 278, 542 +38, 140, 283, 541 +37, 143, 275, 537 +38, 147, 268, 549 +40, 146, 274, 542 +40, 141, 277, 552 +45, 146, 276, 542 +44, 144, 285, 542 +40, 153, 276, 540 +39, 146, 281, 542 +37, 141, 276, 561 +42, 148, 283, 550 +39, 144, 280, 547 +41, 139, 273, 537 +40, 145, 272, 540 +39, 146, 276, 538 +36, 144, 283, 548 +35, 154, 282, 555 +42, 140, 276, 547 +40, 137, 276, 537 +42, 141, 281, 548 +40, 149, 279, 551 +40, 143, 280, 547 +40, 144, 276, 536 +39, 147, 281, 549 +39, 142, 274, 545 +42, 149, 280, 547 +41, 143, 281, 542 +40, 141, 275, 552 +37, 140, 275, 542 +39, 137, 278, 543 +42, 140, 280, 538 +40, 143, 278, 537 +41, 141, 278, 551 +38, 146, 277, 554 +37, 140, 286, 545 +41, 144, 272, 544 +40, 141, 276, 546 +40, 146, 279, 544 +37, 147, 282, 549 +41, 141, 281, 544 +38, 149, 278, 546 +38, 145, 275, 542 +41, 148, 272, 549 +39, 144, 282, 543 +39, 141, 290, 551 +42, 144, 284, 546 +39, 140, 295, 544 +38, 140, 279, 543 +40, 149, 278, 551 +38, 144, 279, 542 +40, 145, 282, 553 +38, 140, 286, 549 +37, 147, 286, 541 +39, 140, 280, 546 +39, 144, 277, 540 +42, 141, 280, 557 +41, 144, 279, 541 +39, 142, 289, 551 +39, 138, 276, 541 +39, 145, 276, 553 +37, 144, 281, 557 +38, 144, 285, 544 +40, 146, 280, 546 +39, 141, 276, 547 +39, 141, 278, 551 +42, 145, 285, 546 +40, 142, 285, 548 +36, 148, 275, 541 +41, 142, 282, 543 +40, 143, 285, 552 +40, 143, 274, 554 +40, 139, 284, 552 +39, 138, 280, 541 +41, 150, 287, 545 +43, 145, 276, 536 +38, 141, 285, 552 +42, 144, 276, 552 +38, 146, 292, 548 +41, 151, 279, 554 +39, 141, 281, 549 +38, 148, 278, 546 +40, 143, 277, 539 +40, 138, 281, 541 +40, 145, 288, 545 +36, 143, 283, 545 +40, 146, 277, 540 +41, 144, 282, 550 +39, 145, 283, 543 +42, 145, 278, 543 +38, 142, 273, 552 +42, 140, 281, 543 +39, 150, 283, 560 +37, 137, 277, 545 +39, 142, 276, 544 +38, 148, 285, 556 +39, 148, 273, 537 +44, 147, 273, 551 +38, 143, 288, 538 +38, 144, 288, 542 +41, 140, 276, 554 +40, 141, 275, 550 +40, 141, 274, 542 +39, 139, 279, 546 +41, 151, 276, 541 +40, 142, 281, 548 +42, 140, 289, 558 +41, 148, 279, 552 +42, 142, 280, 564 +40, 145, 278, 550 +38, 147, 282, 571 +39, 145, 279, 558 +37, 142, 288, 551 +40, 144, 277, 551 +37, 148, 283, 545 +37, 138, 276, 551 +38, 147, 288, 550 +38, 144, 280, 550 +41, 139, 281, 544 +40, 145, 277, 538 +38, 149, 276, 556 +43, 140, 268, 548 +40, 142, 275, 556 +40, 139, 282, 548 +38, 141, 280, 562 +38, 145, 274, 547 +40, 145, 279, 547 +39, 142, 276, 553 +39, 138, 280, 558 +43, 146, 279, 546 +42, 141, 276, 541 +39, 147, 281, 545 +42, 143, 274, 545 +38, 143, 281, 542 +39, 143, 282, 544 +44, 139, 276, 551 +41, 151, 285, 540 +39, 144, 281, 543 +41, 147, 283, 554 +40, 147, 276, 562 +39, 143, 278, 552 +40, 145, 285, 559 +38, 143, 285, 554 +41, 138, 281, 557 +40, 138, 281, 554 +40, 143, 282, 539 +39, 142, 282, 552 +41, 139, 283, 537 +40, 143, 278, 543 +40, 142, 276, 541 +37, 139, 282, 550 +38, 147, 275, 550 +39, 147, 284, 545 +39, 143, 279, 539 +43, 143, 271, 547 +38, 144, 273, 543 +35, 142, 282, 574 +39, 140, 274, 540 +42, 146, 275, 555 +36, 144, 295, 539 +45, 142, 271, 541 +41, 151, 279, 541 +38, 140, 280, 560 +36, 140, 282, 549 +40, 147, 279, 543 +39, 147, 290, 553 +39, 145, 285, 548 +40, 143, 276, 560 +37, 143, 276, 548 +42, 144, 274, 544 +38, 142, 295, 540 +40, 147, 281, 549 +41, 143, 280, 556 +40, 146, 283, 553 +38, 139, 284, 551 +39, 145, 282, 534 +40, 145, 277, 540 +38, 145, 280, 556 +39, 145, 282, 551 +38, 144, 282, 551 +40, 143, 276, 544 +36, 143, 288, 542 +38, 139, 281, 559 +38, 142, 279, 543 +40, 144, 299, 542 +43, 139, 279, 542 +38, 142, 280, 538 +39, 142, 276, 547 +35, 149, 283, 554 +41, 139, 278, 543 +38, 139, 277, 540 +39, 145, 279, 554 +38, 145, 283, 546 +39, 143, 275, 544 +40, 139, 278, 540 +38, 150, 282, 551 +38, 151, 280, 545 +41, 143, 279, 551 +38, 152, 274, 545 +41, 146, 278, 538 +36, 146, 282, 554 +37, 144, 282, 543 +36, 145, 282, 542 +40, 146, 281, 552 +39, 140, 279, 541 +40, 145, 275, 546 +39, 147, 276, 547 +40, 145, 281, 548 +39, 139, 286, 548 +42, 145, 278, 547 +38, 144, 274, 540 +38, 142, 277, 548 +39, 156, 274, 542 +42, 140, 277, 536 +40, 139, 282, 544 +41, 144, 274, 542 +37, 142, 282, 551 +41, 145, 290, 553 +39, 146, 281, 541 +41, 146, 280, 553 +41, 143, 284, 542 +37, 146, 277, 566 +38, 139, 280, 545 +39, 142, 281, 548 +39, 139, 275, 544 +39, 144, 272, 549 +38, 143, 285, 541 +42, 144, 281, 546 +38, 157, 276, 552 +40, 141, 277, 545 +38, 141, 275, 548 +39, 142, 278, 539 +39, 146, 277, 542 +39, 147, 283, 551 +38, 151, 276, 549 +39, 146, 273, 544 +38, 139, 280, 549 +42, 149, 278, 550 +39, 144, 282, 543 +38, 143, 285, 549 +42, 145, 280, 563 +39, 146, 279, 548 +44, 145, 288, 539 +39, 139, 291, 546 +39, 145, 282, 550 +36, 145, 279, 553 +40, 140, 276, 546 +41, 143, 289, 550 +39, 145, 283, 553 +38, 142, 280, 546 +41, 145, 276, 542 +41, 145, 276, 550 +40, 141, 278, 543 +41, 141, 284, 554 +45, 153, 276, 539 +37, 145, 292, 539 +40, 143, 286, 551 +37, 140, 276, 545 +39, 141, 280, 561 +41, 145, 284, 571 +43, 146, 278, 559 +38, 141, 276, 561 +40, 145, 280, 547 +42, 150, 283, 541 +39, 142, 282, 551 +42, 143, 283, 551 +41, 142, 283, 551 +40, 144, 275, 541 +39, 147, 276, 544 +40, 154, 280, 555 +39, 142, 283, 560 +37, 144, 275, 532 +40, 147, 293, 550 +42, 138, 281, 541 +39, 141, 280, 538 +40, 138, 282, 543 +40, 146, 286, 550 +43, 147, 278, 544 +39, 147, 281, 546 +39, 150, 277, 561 +38, 143, 282, 540 +37, 143, 277, 544 +43, 149, 275, 565 +40, 143, 275, 551 +42, 139, 278, 550 +43, 149, 282, 567 +36, 145, 286, 552 +38, 144, 283, 553 +40, 144, 274, 552 +40, 145, 281, 538 +38, 145, 278, 546 +40, 143, 285, 551 +41, 139, 279, 559 +39, 146, 277, 546 +38, 140, 278, 545 +43, 142, 281, 545 +40, 139, 284, 552 +36, 138, 274, 554 +39, 144, 292, 550 +39, 143, 276, 555 +38, 143, 280, 549 +40, 142, 283, 539 +37, 146, 280, 540 +41, 146, 280, 553 +40, 152, 290, 552 +40, 145, 275, 546 +40, 145, 279, 543 +39, 144, 289, 545 +43, 149, 271, 547 +38, 141, 285, 537 +39, 139, 282, 552 +43, 145, 280, 546 +38, 145, 286, 539 +41, 148, 279, 553 +40, 137, 281, 548 +38, 139, 285, 538 +38, 144, 286, 541 +40, 143, 278, 540 +38, 138, 294, 544 +39, 145, 281, 541 +38, 143, 272, 546 +38, 146, 288, 560 +39, 146, 293, 542 +39, 146, 286, 542 +43, 148, 279, 552 +39, 141, 277, 540 +38, 141, 286, 548 +38, 147, 277, 548 +42, 138, 280, 550 +43, 148, 277, 543 +41, 148, 281, 550 +38, 152, 283, 550 +39, 139, 285, 553 +37, 141, 275, 556 +37, 141, 301, 542 +38, 143, 279, 540 +42, 142, 274, 553 +38, 146, 283, 545 +40, 142, 276, 540 +37, 144, 278, 546 +36, 144, 286, 547 +39, 145, 278, 559 +41, 144, 282, 559 +39, 144, 280, 541 +43, 147, 275, 556 +37, 139, 282, 542 +38, 147, 291, 545 +41, 140, 283, 540 +39, 142, 280, 547 +37, 142, 276, 545 +41, 143, 278, 546 +40, 145, 274, 543 +37, 146, 282, 547 +41, 146, 273, 546 +38, 145, 277, 542 +39, 142, 274, 548 +39, 143, 279, 536 +38, 144, 281, 543 +42, 149, 278, 549 +40, 143, 284, 540 +45, 136, 278, 552 +40, 147, 278, 540 +40, 147, 277, 549 +40, 145, 279, 554 +41, 145, 277, 548 +36, 144, 277, 544 +37, 141, 278, 544 +39, 144, 285, 544 +38, 140, 277, 542 +36, 148, 278, 549 +41, 137, 275, 551 +43, 144, 275, 541 +40, 143, 279, 542 +44, 144, 278, 547 +42, 146, 285, 548 +40, 143, 275, 549 +39, 149, 278, 548 +40, 149, 280, 546 +47, 142, 282, 556 +41, 140, 285, 546 +37, 139, 278, 547 +38, 143, 276, 549 +39, 144, 279, 551 +40, 139, 280, 539 +41, 138, 282, 540 +42, 142, 276, 549 +40, 144, 278, 555 +50, 142, 276, 540 +41, 140, 280, 546 +40, 142, 282, 569 +39, 144, 276, 544 +39, 148, 281, 548 +40, 146, 283, 551 +38, 152, 284, 543 +37, 151, 276, 541 +43, 149, 279, 549 +38, 140, 280, 552 +41, 147, 280, 551 +38, 141, 285, 543 +44, 141, 277, 553 +41, 143, 277, 540 +37, 142, 272, 547 +43, 138, 283, 545 +40, 146, 280, 542 +42, 142, 276, 550 +40, 143, 277, 551 +41, 143, 273, 553 +39, 144, 274, 535 +38, 144, 286, 537 +39, 139, 276, 555 +41, 140, 280, 542 +41, 143, 282, 547 +39, 140, 273, 555 +37, 148, 283, 552 +42, 160, 276, 543 +41, 147, 276, 552 +37, 140, 287, 555 +37, 140, 275, 544 +40, 143, 275, 542 +39, 149, 284, 558 +42, 144, 280, 550 +39, 139, 281, 554 +37, 146, 278, 545 +42, 144, 276, 541 +41, 137, 282, 546 +37, 143, 279, 539 +38, 139, 279, 542 +41, 140, 275, 544 +38, 146, 278, 545 +43, 139, 281, 538 +38, 142, 287, 545 +45, 138, 274, 548 +36, 146, 283, 559 +41, 138, 277, 539 +39, 146, 275, 548 +43, 143, 277, 562 +37, 143, 276, 544 +42, 151, 276, 543 +40, 143, 276, 540 +40, 144, 280, 543 +36, 141, 287, 547 +42, 144, 280, 536 +39, 145, 279, 546 +37, 150, 286, 551 +37, 141, 279, 551 +40, 147, 287, 550 +39, 144, 273, 542 +42, 154, 286, 538 +43, 143, 278, 541 +39, 146, 281, 543 +38, 140, 287, 540 +40, 144, 280, 555 +41, 142, 279, 544 +37, 151, 275, 553 +39, 141, 274, 550 +39, 139, 277, 552 +39, 143, 274, 547 +41, 140, 284, 557 +41, 143, 279, 545 +37, 143, 286, 550 +38, 140, 278, 550 +39, 151, 277, 547 +39, 148, 274, 544 +42, 144, 281, 547 +37, 146, 288, 543 +38, 143, 279, 555 +40, 153, 279, 552 +37, 139, 283, 549 +37, 148, 278, 551 +39, 144, 285, 543 +41, 148, 275, 536 +38, 143, 270, 559 +39, 142, 278, 543 +38, 143, 285, 534 +38, 146, 274, 545 +37, 145, 277, 562 +40, 149, 277, 555 +39, 144, 278, 545 +39, 139, 278, 537 +41, 139, 278, 554 +40, 142, 280, 557 +41, 148, 288, 547 +42, 140, 279, 544 +38, 147, 276, 538 +41, 143, 276, 539 +40, 141, 278, 549 +39, 145, 275, 544 +39, 139, 280, 541 +40, 141, 271, 541 +37, 141, 272, 545 +42, 140, 278, 551 +40, 141, 284, 539 +38, 148, 277, 549 +38, 148, 273, 543 +38, 142, 273, 539 +40, 143, 279, 534 +41, 144, 277, 541 +43, 143, 283, 546 +38, 148, 273, 542 +40, 147, 273, 549 +37, 147, 284, 538 +42, 144, 282, 545 +41, 142, 284, 552 +38, 147, 284, 543 +44, 141, 285, 554 +40, 139, 281, 538 +42, 144, 278, 543 +41, 140, 273, 548 +40, 144, 284, 539 +39, 140, 278, 540 +43, 143, 279, 554 +37, 144, 293, 541 +40, 149, 290, 547 +38, 151, 276, 551 +39, 151, 285, 549 +38, 146, 272, 540 +40, 141, 281, 557 +40, 145, 286, 547 +37, 142, 286, 551 +40, 143, 283, 551 +39, 143, 277, 543 +39, 142, 288, 542 +40, 146, 282, 546 +37, 145, 280, 557 +38, 144, 279, 549 +41, 144, 282, 541 +39, 145, 280, 546 +41, 146, 281, 545 +39, 146, 284, 541 +43, 144, 277, 551 +39, 150, 285, 567 +42, 145, 289, 541 +39, 147, 279, 538 +42, 144, 282, 556 +41, 144, 277, 539 +41, 143, 278, 539 +41, 141, 277, 541 +40, 151, 278, 547 +37, 140, 280, 555 +38, 144, 278, 550 +42, 142, 300, 545 +43, 143, 275, 547 +37, 144, 278, 548 +44, 144, 295, 541 +40, 146, 286, 558 +40, 144, 273, 547 +41, 146, 282, 568 +40, 144, 273, 545 +37, 141, 277, 552 +38, 140, 277, 548 +37, 141, 279, 538 +38, 152, 273, 540 +37, 146, 277, 545 +39, 142, 279, 548 +39, 142, 270, 540 +38, 145, 273, 546 +40, 147, 280, 541 +38, 140, 283, 539 +38, 140, 288, 553 +40, 150, 274, 543 +41, 144, 287, 541 +38, 139, 283, 545 +40, 140, 282, 554 +38, 143, 281, 540 +41, 146, 283, 554 +41, 148, 277, 548 +41, 142, 277, 542 +38, 150, 278, 556 +38, 144, 283, 545 +39, 142, 291, 552 +37, 143, 284, 542 +40, 147, 270, 569 +39, 139, 289, 547 +41, 150, 278, 561 +41, 140, 272, 556 +40, 140, 274, 546 +40, 144, 275, 550 +40, 140, 275, 538 +41, 148, 283, 539 +42, 139, 273, 538 +38, 145, 276, 551 +38, 146, 270, 564 +40, 138, 282, 548 +44, 143, 289, 535 +40, 146, 281, 552 +41, 141, 276, 550 +36, 147, 282, 556 +38, 146, 278, 545 +41, 147, 293, 544 +37, 138, 273, 547 +41, 143, 279, 557 +37, 142, 273, 547 +38, 142, 288, 550 +41, 141, 279, 541 +47, 146, 277, 547 +36, 145, 276, 539 +39, 140, 284, 539 +38, 149, 280, 546 +45, 141, 271, 545 +43, 144, 276, 558 +42, 140, 277, 548 +38, 150, 281, 545 +40, 140, 276, 550 +39, 146, 287, 557 +39, 143, 282, 543 +40, 147, 281, 547 +39, 141, 294, 540 +43, 146, 278, 546 +39, 139, 275, 541 +42, 148, 289, 547 +40, 142, 271, 543 +36, 143, 279, 548 +38, 147, 275, 548 +38, 139, 280, 544 +43, 140, 274, 560 +43, 154, 280, 541 +37, 141, 281, 546 +42, 143, 286, 546 +38, 142, 278, 545 +41, 148, 275, 550 +41, 141, 279, 550 +40, 142, 278, 545 +41, 143, 277, 546 +41, 147, 278, 539 +38, 142, 278, 551 +40, 137, 274, 547 +40, 141, 280, 537 +38, 141, 285, 555 +37, 143, 294, 547 +43, 143, 280, 551 +35, 143, 280, 547 +37, 137, 272, 544 +40, 139, 277, 561 +43, 146, 279, 549 +41, 149, 275, 552 +35, 142, 273, 547 +38, 143, 281, 552 +40, 139, 280, 562 +37, 150, 282, 543 +41, 146, 280, 547 +38, 144, 280, 537 +40, 140, 272, 557 +40, 145, 288, 540 +38, 145, 281, 550 +40, 144, 277, 546 +39, 147, 279, 548 +36, 141, 289, 558 +41, 141, 284, 551 +39, 140, 277, 547 +42, 145, 273, 552 +40, 142, 274, 545 +37, 144, 279, 538 +40, 140, 281, 552 +39, 148, 277, 541 +40, 152, 287, 548 +43, 155, 277, 543 +37, 153, 284, 548 +43, 143, 288, 545 +41, 140, 282, 546 +38, 145, 280, 536 +37, 145, 275, 548 +38, 143, 279, 547 +39, 138, 281, 547 +37, 140, 275, 554 +36, 148, 279, 539 +39, 139, 279, 541 +41, 142, 284, 535 +38, 145, 279, 540 +38, 145, 278, 554 +39, 148, 277, 540 +40, 139, 273, 540 +39, 139, 283, 549 +39, 139, 284, 546 +40, 143, 277, 557 +39, 138, 281, 543 +36, 139, 277, 552 +40, 142, 277, 551 +41, 144, 280, 541 +39, 147, 279, 564 +43, 144, 282, 564 +40, 142, 287, 539 +38, 143, 290, 542 +41, 144, 274, 541 +41, 145, 283, 547 +41, 143, 273, 560 +39, 140, 280, 540 +37, 143, 276, 540 +37, 148, 282, 539 +39, 150, 279, 558 +40, 141, 280, 548 +41, 142, 274, 539 +41, 145, 276, 544 +43, 149, 271, 542 +38, 143, 276, 552 +37, 148, 276, 541 +38, 147, 287, 534 +40, 145, 276, 560 +41, 146, 275, 551 +36, 143, 275, 536 +41, 144, 281, 550 +39, 142, 285, 548 +38, 148, 279, 542 +40, 140, 278, 556 +38, 146, 283, 549 +40, 142, 281, 540 +40, 144, 275, 544 +38, 141, 282, 548 +40, 142, 283, 546 +38, 140, 286, 548 +40, 142, 279, 545 +35, 142, 271, 535 +40, 142, 273, 549 +39, 145, 277, 549 +39, 141, 278, 543 +37, 143, 279, 537 +39, 143, 277, 548 +40, 143, 278, 549 +38, 144, 276, 542 +41, 142, 283, 544 +38, 145, 277, 550 +41, 142, 280, 554 +40, 141, 281, 537 +39, 145, 284, 541 +41, 142, 276, 539 +37, 145, 275, 546 +39, 145, 285, 544 +41, 143, 276, 545 +39, 142, 275, 548 +43, 140, 277, 544 +42, 142, 284, 538 +38, 144, 284, 551 +40, 143, 281, 553 +37, 144, 277, 545 +40, 139, 277, 538 +42, 142, 276, 558 +38, 141, 282, 540 +37, 144, 282, 540 +42, 143, 278, 540 +40, 143, 278, 552 +36, 147, 282, 550 +38, 141, 281, 559 +39, 142, 280, 537 +37, 148, 284, 553 +40, 141, 281, 550 +40, 144, 274, 547 +39, 140, 279, 546 +38, 142, 277, 542 +41, 148, 279, 544 +39, 140, 277, 544 +40, 145, 282, 550 +37, 142, 280, 537 +42, 144, 273, 545 +40, 144, 272, 544 +40, 142, 280, 554 +40, 139, 284, 558 +36, 146, 281, 541 +40, 148, 295, 539 +41, 139, 275, 548 +45, 145, 289, 551 +41, 142, 279, 544 +40, 141, 274, 541 +42, 144, 277, 545 +44, 143, 275, 540 +38, 150, 275, 547 +41, 150, 282, 534 +40, 140, 281, 545 +37, 143, 277, 550 +40, 143, 277, 547 +39, 146, 280, 547 +38, 147, 275, 536 +37, 148, 284, 539 +41, 142, 281, 535 +37, 142, 279, 562 +40, 142, 275, 547 +43, 146, 283, 552 +45, 138, 285, 553 +42, 141, 282, 541 +39, 146, 272, 537 +43, 139, 274, 541 +38, 142, 278, 549 +42, 142, 278, 552 +38, 147, 285, 539 +39, 140, 273, 540 +40, 142, 275, 546 +38, 149, 289, 548 +43, 139, 283, 541 +41, 145, 287, 546 +39, 143, 277, 547 +42, 142, 273, 544 +41, 144, 285, 560 +38, 142, 278, 550 +37, 145, 273, 560 diff --git a/test/execution_fees.cpp b/test/execution_fees.cpp new file mode 100644 index 000000000..6fdf2cbd1 --- /dev/null +++ b/test/execution_fees.cpp @@ -0,0 +1,375 @@ +#define NO_UEFI + +#include "contract_testing.h" +#include "../src/ticking/execution_fee_report_collector.h" +#include "../src/contract_core/execution_time_accumulator.h" + +// Helper to create a valid baseline test transaction with given entries +static Transaction* createTestTransaction(unsigned char* buffer, size_t bufferSize, + unsigned int numEntries, + const unsigned int* contractIndices, + const long long* executionFees) +{ + unsigned int alignmentPadding = (numEntries % 2 == 1) ? sizeof(unsigned int) : 0; + const unsigned int inputSize = sizeof(unsigned int) + sizeof(unsigned int) + + (numEntries * sizeof(unsigned int)) + alignmentPadding + + (numEntries * sizeof(long long)) + + sizeof(m256i); + + if (sizeof(Transaction) + inputSize > bufferSize) + { + return nullptr; + } + + Transaction* tx = (Transaction*)buffer; + tx->sourcePublicKey = m256i::zero(); + tx->destinationPublicKey = m256i::zero(); + tx->amount = 0; + tx->tick = 1000; + tx->inputType = EXECUTION_FEE_REPORT_INPUT_TYPE; + tx->inputSize = inputSize; + + unsigned char* inputPtr = tx->inputPtr(); + *(unsigned int*)inputPtr = 5; // phaseNumber + *(unsigned int*)(inputPtr + 4) = numEntries; + + unsigned int* txIndices = (unsigned int*)(inputPtr + 8); + for (unsigned int i = 0; i < numEntries; i++) + { + txIndices[i] = contractIndices[i]; + } + + long long* txFees = (long long*)(inputPtr + 8 + (numEntries * sizeof(unsigned int)) + alignmentPadding); + for (unsigned int i = 0; i < numEntries; i++) + { + txFees[i] = executionFees[i]; + } + + m256i* dataLock = (m256i*)(inputPtr + 8 + (numEntries * sizeof(unsigned int)) + alignmentPadding + (numEntries * sizeof(long long))); + *dataLock = m256i::zero(); + + return tx; +} + +TEST(ExecutionFeeReportCollector, InitAndStore) +{ + ExecutionFeeReportCollector collector; + collector.init(); + + collector.storeReport(2, 0, 1000); + collector.storeReport(2, 1, 2000); + collector.storeReport(1, 0, 5000); + collector.storeReport(1, 500, 6000); + + const unsigned long long* reports = collector.getReportsForContract(2); + ASSERT_NE(reports, nullptr); + EXPECT_EQ(reports[0], 1000); + EXPECT_EQ(reports[1], 2000); + + reports = collector.getReportsForContract(1); + ASSERT_NE(reports, nullptr); + EXPECT_EQ(reports[0], 5000); + EXPECT_EQ(reports[500], 6000); +} + +TEST(ExecutionFeeReportCollector, Reset) +{ + ExecutionFeeReportCollector collector; + collector.init(); + + for (unsigned int i = 0; i < 10; i++) { + collector.storeReport(1, i, i * 1000); + } + + const unsigned long long* reports = collector.getReportsForContract(1); + EXPECT_EQ(reports[5], 5000); + + collector.reset(); + + reports = collector.getReportsForContract(1); + EXPECT_EQ(reports[5], 0); +} + +TEST(ExecutionFeeReportCollector, BoundaryValidation) +{ + ExecutionFeeReportCollector collector; + collector.init(); + + collector.storeReport(1, 0, 100); + collector.storeReport(contractCount - 1, NUMBER_OF_COMPUTORS - 1, 200); + + collector.storeReport(contractCount, 0, 100); + collector.storeReport(1, NUMBER_OF_COMPUTORS, 100); + + const unsigned long long* reports = collector.getReportsForContract(1); + EXPECT_EQ(reports[0], 100); + + reports = collector.getReportsForContract(contractCount - 1); + EXPECT_EQ(reports[NUMBER_OF_COMPUTORS - 1], 200); +} + +TEST(ExecutionFeeReportTransaction, ParseValidTransaction) +{ + unsigned char buffer[512]; + unsigned int contractIndices[2] = {2, 1}; + long long executionFees[2] = {1000, 2000}; + + Transaction* tx = createTestTransaction(buffer, sizeof(buffer), 2, contractIndices, executionFees); + ASSERT_NE(tx, nullptr); + + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidExecutionFeeReport(tx)); + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); + EXPECT_EQ(ExecutionFeeReportTransactionPrefix::getNumEntries(tx), 2u); + + const unsigned int* parsedIndices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); + const unsigned long long* parsedFees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); + EXPECT_EQ(parsedIndices[0], 2u); + EXPECT_EQ(parsedFees[0], 1000); + EXPECT_EQ(parsedIndices[1], 1u); + EXPECT_EQ(parsedFees[1], 2000); +} + +TEST(ExecutionFeeReportTransaction, RejectNonZeroAmount) { + unsigned char buffer[512]; + unsigned int contractIndices[1] = {1}; + long long executionFees[1] = {1000}; + + Transaction* tx = createTestTransaction(buffer, sizeof(buffer), 1, contractIndices, executionFees); + ASSERT_NE(tx, nullptr); + + // Valid initially + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidExecutionFeeReport(tx)); + + // Make amount non-zero (execution fee reports must have amount = 0) + tx->amount = 100; + + // Should now be invalid + EXPECT_FALSE(ExecutionFeeReportTransactionPrefix::isValidExecutionFeeReport(tx)); +} + +TEST(ExecutionFeeReportTransaction, RejectMisalignedEntries) { + unsigned char buffer[512]; + unsigned int contractIndices[1] = {1}; + long long executionFees[1] = {1000}; + + Transaction* tx = createTestTransaction(buffer, sizeof(buffer), 1, contractIndices, executionFees); + ASSERT_NE(tx, nullptr); + + // Valid initially + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); + + // Break alignment by adding 1 byte to inputSize + // Payload size will no longer match expected size for numEntries + tx->inputSize += 1; + + // Should now have invalid alignment + EXPECT_FALSE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); +} + +TEST(ExecutionFeeReportCollector, ValidateReportEntries) { + ExecutionFeeReportCollector collector; + collector.init(); + + // Valid entries + unsigned int validIndices[2] = {1, 2}; + unsigned long long validFees[2] = {1000, 2000}; + EXPECT_TRUE(collector.validateReportEntries(validIndices, validFees, 2)); + + // Invalid: contractIndex >= contractCount + unsigned int invalidContractIndices[1] = {contractCount}; + unsigned long long invalidContractFees[1] = {1000}; + EXPECT_FALSE(collector.validateReportEntries(invalidContractIndices, invalidContractFees, 1)); + + // Invalid: executionFee <= 0 + unsigned int zeroFeeIndices[1] = {1}; + unsigned long long zeroFees[1] = {0}; + EXPECT_FALSE(collector.validateReportEntries(zeroFeeIndices, zeroFees, 1)); + + // Invalid: one good entry, one bad + unsigned int mixedIndices[2] = {1, contractCount + 5}; + unsigned long long mixedFees[2] = {1000, 2000}; + EXPECT_FALSE(collector.validateReportEntries(mixedIndices, mixedFees, 2)); +} + +TEST(ExecutionFeeReportCollector, StoreReportEntries) { + ExecutionFeeReportCollector collector; + collector.init(); + + unsigned int contractIndices[3] = {1, 2, 5}; + unsigned long long executionFees[3] = {1000, 3000, 7000}; + + unsigned int computorIndex = 10; + collector.storeReportEntries(contractIndices, executionFees, 3, computorIndex); + + // Verify entries were stored at correct positions + const unsigned long long* reports1 = collector.getReportsForContract(1); + EXPECT_EQ(reports1[computorIndex], 1000); + + const unsigned long long* reports2 = collector.getReportsForContract(2); + EXPECT_EQ(reports2[computorIndex], 3000); + + const unsigned long long* reports5 = collector.getReportsForContract(5); + EXPECT_EQ(reports5[computorIndex], 7000); + + // Verify other positions remain zero + EXPECT_EQ(reports1[0], 0); + EXPECT_EQ(reports1[computorIndex + 1], 0); +} + +TEST(ExecutionFeeReportCollector, MultipleComputorsReporting) { + ExecutionFeeReportCollector collector; + collector.init(); + + // Computor 0 reports for contracts 3 and 1 + unsigned int comp0Indices[2] = {3, 1}; + unsigned long long comp0Fees[2] = {1000, 2000}; + collector.storeReportEntries(comp0Indices, comp0Fees, /*numEntries=*/2, /*computorIndex=*/0); + + // Computor 5 reports for contracts 3 and 2 (different fee for contract 3) + unsigned int comp5Indices[2] = {3, 2}; + unsigned long long comp5Fees[2] = {1500, 3000}; + collector.storeReportEntries(comp5Indices, comp5Fees, /*numEntries=*/2, /*computorIndex=*/5); + + // Computor 10 reports for contract 1 (different fee than computor 0) + unsigned int comp10Indices[1] = {1}; + unsigned long long comp10Fees[1] = {2500}; + collector.storeReportEntries(comp10Indices, comp10Fees, /*numEntries=*/1, /*computorIndex=*/10); + + // Verify contract 3 has reports from computors 0 and 5 + const unsigned long long* reports3 = collector.getReportsForContract(3); + EXPECT_EQ(reports3[0], 1000); + EXPECT_EQ(reports3[5], 1500); + EXPECT_EQ(reports3[10], 0); // Computor 10 didn't report for contract 3 + + // Verify contract 1 has reports from computors 0 and 10 + const unsigned long long* reports1 = collector.getReportsForContract(1); + EXPECT_EQ(reports1[0], 2000); + EXPECT_EQ(reports1[5], 0); // Computor 5 didn't report for contract 1 + EXPECT_EQ(reports1[10], 2500); + + // Verify contract 2 has report only from computor 5 + const unsigned long long* reports2 = collector.getReportsForContract(2); + EXPECT_EQ(reports2[0], 0); + EXPECT_EQ(reports2[5], 3000); + EXPECT_EQ(reports2[10], 0); +} + +TEST(ExecutionFeeReportBuilder, BuildAndParseEvenEntries) { + ExecutionFeeReportPayload payload; + unsigned long long contractTimes[contractCount] = {0}; + contractTimes[1] = 200; + contractTimes[3] = 100; + + unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 5, 1, 1); + EXPECT_EQ(entryCount, 2u); + + // Verify transaction is valid and parseable + Transaction* tx = (Transaction*)&payload; + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); + EXPECT_EQ(ExecutionFeeReportTransactionPrefix::getNumEntries(tx), 2u); + + const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); + const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); + EXPECT_EQ(indices[0], 1u); + EXPECT_EQ(fees[0], 200); // (200 * 1) / 1 + EXPECT_EQ(indices[1], 3u); + EXPECT_EQ(fees[1], 100); // (100 * 1) / 1 +} + +TEST(ExecutionFeeReportBuilder, BuildAndParseOddEntries) { + ExecutionFeeReportPayload payload; + unsigned long long contractTimes[contractCount] = {0}; + contractTimes[1] = 100; + contractTimes[2] = 300; + contractTimes[5] = 600; + + unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 10, 1, 1); + EXPECT_EQ(entryCount, 3u); + + // Verify transaction is valid and parseable (with alignment padding) + Transaction* tx = (Transaction*)&payload; + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); + EXPECT_EQ(ExecutionFeeReportTransactionPrefix::getNumEntries(tx), 3u); + + const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); + const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); + EXPECT_EQ(indices[0], 1u); + EXPECT_EQ(fees[0], 100); // (100 * 1) / 1 + EXPECT_EQ(indices[1], 2u); + EXPECT_EQ(fees[1], 300); // (300 * 1) / 1 + EXPECT_EQ(indices[2], 5u); + EXPECT_EQ(fees[2], 600); // (600 * 1) / 1 +} + +TEST(ExecutionFeeReportBuilder, NoEntriesReturnsZero) { + ExecutionFeeReportPayload payload; + unsigned long long contractTimes[contractCount] = {0}; + + unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 7, 1, 1); + EXPECT_EQ(entryCount, 0u); +} + +TEST(ExecutionFeeReportBuilder, BuildWithDivisionMultiplier) { + ExecutionFeeReportPayload payload; + unsigned long long contractTimes[contractCount] = {0}; + contractTimes[1] = 5; // Will become 0 after (5 * 1) / 10 - should be excluded + contractTimes[2] = 25; // Will become 2 after (25 * 1) / 10 + contractTimes[3] = 100; // Will become 10 after (100 * 1) / 10 + + unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 5, 1, 10); + + // Only contracts with non-zero fees after division should be included + EXPECT_EQ(entryCount, 2u); // contracts 0 and 2 (contract 1 becomes 0) + + Transaction* tx = (Transaction*)&payload; + EXPECT_TRUE(ExecutionFeeReportTransactionPrefix::isValidEntryAlignment(tx)); + + const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices(tx); + const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees(tx); + EXPECT_EQ(indices[0], 2u); + EXPECT_EQ(fees[0], 2); // (100 * 1) / 10 + EXPECT_EQ(indices[1], 3u); + EXPECT_EQ(fees[1], 10); // (25 * 1) / 10 +} + +TEST(ExecutionFeeReportBuilder, BuildWithMultiplicationMultiplier) { + ExecutionFeeReportPayload payload; + unsigned long long contractTimes[contractCount] = {0}; + contractTimes[1] = 10; + contractTimes[3] = 25; + + unsigned int entryCount = buildExecutionFeeReportPayload(payload, contractTimes, 8, 100, 1); + EXPECT_EQ(entryCount, 2u); + + const unsigned int* indices = ExecutionFeeReportTransactionPrefix::getContractIndices((Transaction*)&payload); + const unsigned long long* fees = ExecutionFeeReportTransactionPrefix::getExecutionFees((Transaction*)&payload); + EXPECT_EQ(indices[0], 1u); + EXPECT_EQ(fees[0], 1000); // (10 * 100) / 1 + EXPECT_EQ(indices[1], 3u); + EXPECT_EQ(fees[1], 2500); // (25 * 100) / 1 +} + +TEST(ExecutionTimeAccumulatorTest, AddingAndPhaseSwitching) +{ + ExecutionTimeAccumulator accum; + + accum.init(); + frequency = 0; // Bypass microseconds conversion + + accum.addTime(/*contractIndex=*/0, /*time=*/52784); + accum.addTime(/*contractIndex=*/contractCount/2, /*time=*/8795); + + accum.startNewAccumulation(); + + const unsigned long long* prevPhaseTimes = accum.getPrevPhaseAccumulatedTimes(); + + for (unsigned int c = 0; c < contractCount; ++c) + { + if (c == 0) + EXPECT_EQ(prevPhaseTimes[c], 52784); + else if (c == contractCount / 2) + EXPECT_EQ(prevPhaseTimes[c], 8795); + else + EXPECT_EQ(prevPhaseTimes[c], 0); + } +} diff --git a/test/file_io.cpp b/test/file_io.cpp new file mode 100644 index 000000000..316d132e1 --- /dev/null +++ b/test/file_io.cpp @@ -0,0 +1,684 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + + +#include +#include +#include +#include +#include + +#include "../src/platform/file_io.h" + +static constexpr unsigned long long THREAD_COUNT = 4; +static constexpr unsigned long long MEM_BUFFER_SIZE = 52ULL * 1024ULL * 1024ULL; +std::mutex gMessageLock; + +// For testing the scheduler save file +struct FragmentData +{ + // Reserve memories + unsigned int memBuffer[MEM_BUFFER_SIZE]; + + // Randomly parition of data for writing + unsigned long long dataPos[4][2]; +}; + +static std::vector threadData; +static FragmentData buffer; +static unsigned char threadFinish[THREAD_COUNT]; + +inline static unsigned int random(const unsigned int range) +{ + unsigned int value; + _rdrand32_step(&value); + + return value % range; +} + +inline static unsigned long long random64(const unsigned long long range) +{ + unsigned long long value; + _rdrand64_step(&value); + + return (value % range); +} + +class FileSystemWrapper +{ +public: + FileSystemWrapper() + { + initFilesystem(); + registerAsynFileIO(NULL); + } + ~FileSystemWrapper() + { + deInitFileSystem(); + } + + bool initTestData() + { + memset(threadFinish, 1, THREAD_COUNT); + + threadData.resize(THREAD_COUNT); + for (int id = 0; id < THREAD_COUNT; id++) + { + // randomly generate a chunk of data + for (unsigned long long i = 0; i < MEM_BUFFER_SIZE; i++) + { + threadData[id].memBuffer[i] = random(4096) * ((int)i + 1); + } + // Randomly pick some part of data for writing out + unsigned long long remainedData = MEM_BUFFER_SIZE; + for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) + { + // Start of the data + threadData[id].dataPos[i][0] = random64(MEM_BUFFER_SIZE - 1); + + // Size of the data. Make sure we limit all small files in size of total MEM_BUFFER_SIZE + unsigned long long dataSize = random64(MEM_BUFFER_SIZE - threadData[id].dataPos[i][0]); + dataSize = dataSize > remainedData ? remainedData : dataSize; + remainedData = remainedData - dataSize; + + if (dataSize == 0) + { + dataSize = 1; + } + + threadData[id].dataPos[i][1] = dataSize; + } + } + return true; + } +}; + +static FileSystemWrapper fileSystem; + +long long loadFile(CHAR16* fileName, unsigned long long totalSize, char* buffer) +{ + FILE* file = nullptr; + if (_wfopen_s(&file, fileName, L"rb") != 0 || !file) + { + wprintf(L"Error opening file %s!\n", fileName); + return -1; + } + if (fread(buffer, 1, totalSize, file) != totalSize) + { + wprintf(L"Error reading %llu bytes from %s!\n", totalSize, fileName); + return -1; + } + fclose(file); + return totalSize; +} + +long long saveFile(CHAR16* fileName, unsigned long long totalSize, const char* buffer) +{ + FILE* file = nullptr; + if (_wfopen_s(&file, fileName, L"wb") != 0 || !file) + { + wprintf(L"Error opening file %s!\n", fileName); + return -1; + } + if (fwrite(buffer, 1, totalSize, file) != totalSize) + { + wprintf(L"Error saving %llu bytes from %s!\n", totalSize, fileName); + return -1; + } + fclose(file); + return totalSize; +} + +bool runAsyncSaveFile(int id, bool blocking = true, bool largeFile = false) +{ + bool sts = true; + CHAR16 fileName[32]; + setText(fileName, L"tmp_file_"); + appendNumber(fileName, id, false); + + if (largeFile) + { + long long sts = asyncSaveLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[0]), NULL, false, blocking); + if (sts <= 0) + { + std::lock_guard lock(gMessageLock); + std::cout << "runAsyncSaveFile::saveFile failed with size " << MEM_BUFFER_SIZE * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + sts = false; + } + } + else + { + // Try to save the files + for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) + { + unsigned long long dataStart = threadData[id].dataPos[i][0]; + unsigned long long dataCount = threadData[id].dataPos[i][1]; + + CHAR16 partionFileName[256]; + setText(partionFileName, fileName); + appendText(partionFileName, L"."); + appendNumber(partionFileName, i, false); + + long long sts = asyncSave(partionFileName, dataCount * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[dataStart]), NULL, blocking); + if (sts <= 0) + { + std::lock_guard lock(gMessageLock); + std::cout << "runAsyncSaveFile::saveFile failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + sts = false; + break; + } + } + } + + threadFinish[id] = 1; + return sts; +} + +bool prepareAsyncLoadFile(bool largeFile = false) +{ + bool sts = true; + fileSystem.initTestData(); + + for (int id = 0; id < THREAD_COUNT; id++) + { + CHAR16 fileName[32]; + setText(fileName, L"tmp_file_"); + appendNumber(fileName, id, false); + + if (largeFile) + { + long long sts = saveLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[0]), NULL, false); + if (sts <= 0) + { + std::lock_guard lock(gMessageLock); + std::cout << "prepareAsyncLoadFile::saveFile failed with size " << MEM_BUFFER_SIZE * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + sts = false; + } + } + else + { + // Try to save the files + for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) + { + unsigned long long dataStart = threadData[id].dataPos[i][0]; + unsigned long long dataCount = threadData[id].dataPos[i][1]; + + CHAR16 partionFileName[256]; + setText(partionFileName, fileName); + appendText(partionFileName, L"."); + appendNumber(partionFileName, i, false); + + long long sts = save(partionFileName, dataCount * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[dataStart]), NULL); + if (sts <= 0) + { + std::lock_guard lock(gMessageLock); + std::cout << "prepareAsyncLoadFile::saveFile failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + sts = false; + break; + } + } + } + } + return sts; +} + +bool runAsyncLoadFile(int id, bool largeFile = false) +{ + bool sts = true; + CHAR16 fileName[32]; + setText(fileName, L"tmp_file_"); + appendNumber(fileName, id, false); + + if (largeFile) + { + long long sts = asyncLoadLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[0]), NULL); + if (sts <= 0) + { + std::lock_guard lock(gMessageLock); + std::cout << "runAsyncLoadFile::loadFile failed with size " << MEM_BUFFER_SIZE * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + sts = false; + } + } + else + { + // Try to load the files + for (int i = 0; i < sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); i++) + { + unsigned long long dataStart = threadData[id].dataPos[i][0]; + unsigned long long dataCount = threadData[id].dataPos[i][1]; + + CHAR16 partionFileName[256]; + setText(partionFileName, fileName); + appendText(partionFileName, L"."); + appendNumber(partionFileName, i, false); + + long long sts = asyncLoad(partionFileName, dataCount * sizeof(unsigned int), (unsigned char*)&(threadData[id].memBuffer[dataStart]), NULL); + if (sts <= 0) + { + std::lock_guard lock(gMessageLock); + std::cout << "runAsyncLoadFile::loadFile failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + sts = false; + break; + } + } + } + + threadFinish[id] = 1; + return sts; +} + + +bool verifyResult(int id, bool largeFile = false) +{ + bool testPass = false; + CHAR16 fileName[32]; + setText(fileName, L"tmp_file_"); + appendNumber(fileName, id, false); + + if (largeFile) + { + long long sts = loadLargeFile(fileName, MEM_BUFFER_SIZE * sizeof(unsigned int), (unsigned char*)buffer.memBuffer, NULL); + unsigned char* originalData = (unsigned char*)&(threadData[id].memBuffer[0]); + unsigned char* loadedData = (unsigned char*)&(buffer.memBuffer[0]); + int result = memcmp(originalData, loadedData, MEM_BUFFER_SIZE * sizeof(unsigned int)); + testPass = (result == 0); + } + else + { + int matchCount = 0; + int numberOfFiles = sizeof(threadData[id].dataPos) / sizeof(threadData[id].dataPos[0]); + for (int i = 0; i < numberOfFiles; i++) + { + unsigned long long dataStart = threadData[id].dataPos[i][0]; + unsigned long long dataCount = threadData[id].dataPos[i][1]; + + CHAR16 partionFileName[256]; + setText(partionFileName, fileName); + appendText(partionFileName, L"."); + appendNumber(partionFileName, i, false); + + long long sts = loadFile(partionFileName, dataCount * sizeof(unsigned int), (char*)buffer.memBuffer); + + if (sts != dataCount * sizeof(unsigned int)) + { + std::cout << "verifyResult failed with size " << dataCount * sizeof(unsigned int) / 1024 << " KB. Error " << sts << std::endl; + return false; + } + + unsigned char* originalData = (unsigned char*)&(threadData[id].memBuffer[dataStart]); + unsigned char* loadedData = (unsigned char*)&(buffer.memBuffer[0]); + + int result = memcmp(originalData, loadedData, dataCount * sizeof(unsigned int)); + if (result == 0) + { + matchCount++; + } + } + testPass = (matchCount == numberOfFiles); + } + + return testPass; +} + +int runTestAsyncSaveFile(bool blocking, bool largeFile, bool limitItem) +{ + fileSystem.initTestData(); + + // Run the test + std::vector> threadVec(THREAD_COUNT); + for (int i = 0; i < THREAD_COUNT; i++) + { + threadFinish[i] = 0; + threadVec[i].reset(new std::thread(runAsyncSaveFile, i, blocking, largeFile)); + } + + auto startTime = std::chrono::high_resolution_clock::now(); + int readyCount = 0; + while (readyCount < THREAD_COUNT) + { + // Don't flush right away. Wait sometimes for simulate + if (limitItem) + { + flushAsyncFileIOBuffer(2); + } + else + { + unsigned long long waitingTimeInMs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); + if (waitingTimeInMs > 1000) + { + startTime = std::chrono::high_resolution_clock::now(); + flushAsyncFileIOBuffer(); + } + } + + readyCount = 0; + for (int i = 0; i < THREAD_COUNT; i++) + { + char readyFlag = 0; + readyFlag = threadFinish[i]; + if (readyFlag) + { + readyCount++; + } + } + } + + for (int i = 0; i < THREAD_COUNT; i++) + { + if (threadVec[i]->joinable()) + { + threadVec[i]->join(); + } + } + + // Non blocking, need to flush all data + if (!blocking) + { + flushAsyncFileIOBuffer(); + } + + // Verify result + int testPass = 0; + for (int i = 0; i < THREAD_COUNT; i++) + { + if (verifyResult(i, largeFile)) + { + testPass++; + } + } + return testPass; +} + + +int runTestAsyncLoadFile(bool blocking, bool largeFile, bool limitItem) +{ + prepareAsyncLoadFile(largeFile); + + // Run the test + std::vector> threadVec(THREAD_COUNT); + for (int i = 0; i < THREAD_COUNT; i++) + { + threadFinish[i] = 0; + threadVec[i].reset(new std::thread(runAsyncLoadFile, i, largeFile)); + } + auto startTime = std::chrono::high_resolution_clock::now(); + int readyCount = 0; + while (readyCount < THREAD_COUNT) + { + // Don't flush right away. Wait sometimes for simulate + if (limitItem) + { + flushAsyncFileIOBuffer(2); + } + else + { + unsigned long long waitingTimeInMs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime).count(); + if (waitingTimeInMs > 1000) + { + startTime = std::chrono::high_resolution_clock::now(); + flushAsyncFileIOBuffer(); + } + } + + readyCount = 0; + for (int i = 0; i < THREAD_COUNT; i++) + { + char readyFlag = 0; + readyFlag = threadFinish[i]; + if (readyFlag) + { + readyCount++; + } + } + } + + for (int i = 0; i < THREAD_COUNT; i++) + { + if (threadVec[i]->joinable()) + { + threadVec[i]->join(); + } + } + + // Verify result + int testPass = 0; + for (int i = 0; i < THREAD_COUNT; i++) + { + if (verifyResult(i, largeFile)) + { + testPass++; + } + } + return testPass; +} + +TEST(TestAsyncFileIO, AsyncNonBlockingSaveFile) +{ + if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) + { + EXPECT_EQ(runTestAsyncSaveFile(false, false, false), THREAD_COUNT); + } + else + { + EXPECT_TRUE(true); + } +} + +TEST(TestAsyncFileIO, AsyncNonBlockingSaveLargeFile) +{ + if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) + { + EXPECT_EQ(runTestAsyncSaveFile(false, true, false), THREAD_COUNT); + } + else + { + EXPECT_TRUE(true); + } +} + +TEST(TestAsyncFileIO, AsyncNonBlockingSaveFileWithLimitItems) +{ + if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) + { + EXPECT_EQ(runTestAsyncSaveFile(false, false, true), THREAD_COUNT); + } + else + { + EXPECT_TRUE(true); + } +} + +TEST(TestAsyncFileIO, AsyncNonBlockingSaveLargeFileWithLimitItems) +{ + if (ASYNC_FILE_IO_WRITE_QUEUE_BUFFER_SIZE > 0) + { + EXPECT_EQ(runTestAsyncSaveFile(false, true, true), THREAD_COUNT); + } + else + { + EXPECT_TRUE(true); + } +} + + +TEST(TestAsyncFileIO, AsyncSaveFile) +{ + EXPECT_EQ(runTestAsyncSaveFile(true, false, false), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncSaveLargeFile) +{ + EXPECT_EQ(runTestAsyncSaveFile(true, true, false), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncSaveFileWithLimitItems) +{ + EXPECT_EQ(runTestAsyncSaveFile(true, false, true), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncSaveLargeFileWithLimitItems) +{ + EXPECT_EQ(runTestAsyncSaveFile(true, true, true), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncLoadFile) +{ + EXPECT_EQ(runTestAsyncLoadFile(true, false, false), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncLoadLargeFile) +{ + EXPECT_EQ(runTestAsyncLoadFile(true, true, false), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncLoadFileWithLimitItems) +{ + EXPECT_EQ(runTestAsyncLoadFile(true, false, true), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, AsyncLoadLargeFileWithLimitItems) +{ + EXPECT_EQ(runTestAsyncLoadFile(true, true, true), THREAD_COUNT); +} + +TEST(TestAsyncFileIO, FindKLargest) +{ + constexpr int NUMBER_OF_ELEMENTS = 2025; + constexpr int K_NUMBER = 245; + + PairStruct priorityArray[NUMBER_OF_ELEMENTS]; + + // Generate the elements + for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) + { + priorityArray[i]._key = i; + priorityArray[i]._value = random(NUMBER_OF_ELEMENTS); + } + + // Find K largest in priorityArray + findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); + + // Comparison. Expect all K left items are greater than remained items + for (int i = 0; i < K_NUMBER; i++) + { + for (int j = K_NUMBER; j < NUMBER_OF_ELEMENTS; j++) + { + EXPECT_GE(priorityArray[i]._value, priorityArray[j]._value); + } + } +} + +TEST(TestAsyncFileIO, FindKLargestOneItemArray) +{ + constexpr int NUMBER_OF_ELEMENTS = 1; + constexpr int K_NUMBER = 1; + + PairStruct priorityArray[NUMBER_OF_ELEMENTS]; + priorityArray[0]._value = random(1000); + + // Find K largest in priorityArray + findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); + + // Comparison. Expect all K left items are greater than remained items + for (int i = 0; i < K_NUMBER; i++) + { + for (int j = K_NUMBER; j < NUMBER_OF_ELEMENTS; j++) + { + EXPECT_GE(priorityArray[i]._value, priorityArray[j]._value); + } + } +} + +TEST(TestAsyncFileIO, FindKLargestDuplicatedItems) +{ + constexpr int NUMBER_OF_ELEMENTS = 1000; + constexpr int K_NUMBER = 100; + + PairStruct priorityArray[NUMBER_OF_ELEMENTS]; + + // Generate the elements + const int value = random(NUMBER_OF_ELEMENTS); + for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) + { + priorityArray[i]._key = i; + priorityArray[i]._value = value; + } + + // Find K largest in priorityArray + findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); + + // Comparison. Expect all K left items are greater than remained items + for (int i = 0; i < K_NUMBER; i++) + { + for (int j = K_NUMBER; j < NUMBER_OF_ELEMENTS; j++) + { + EXPECT_GE(priorityArray[i]._value, priorityArray[j]._value); + } + } +} + +TEST(TestAsyncFileIO, FindKLargestK1) +{ + constexpr int NUMBER_OF_ELEMENTS = 1000; + constexpr int K_NUMBER = 1; + + PairStruct priorityArray[NUMBER_OF_ELEMENTS]; + + // Generate the elements + for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) + { + priorityArray[i]._key = i; + priorityArray[i]._value = random(NUMBER_OF_ELEMENTS); + } + + // Find K largest in priorityArray + findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); + + // Comparison. The first item is the largest one + for (int j = 1; j < NUMBER_OF_ELEMENTS; j++) + { + EXPECT_GE(priorityArray[0]._value, priorityArray[j]._value); + } +} + +TEST(TestAsyncFileIO, FindKLargestKDuplicated) +{ + constexpr int NUMBER_OF_ELEMENTS = 1000; + constexpr int K_NUMBER = 100; + + PairStruct priorityArray[NUMBER_OF_ELEMENTS]; + + // Generate the elements + std::vector numbers(NUMBER_OF_ELEMENTS); + + // Constant value for first K_NUMBER + 1 element + const int value = NUMBER_OF_ELEMENTS + 1; + for (int i =0; i < K_NUMBER + 1; i++) + { + priorityArray[i]._value = value; + } + for (int i = K_NUMBER + 1; i < NUMBER_OF_ELEMENTS; i++) + { + priorityArray[i]._value = random(NUMBER_OF_ELEMENTS); + } + + // Shuffle the array + for (int i = 0; i < NUMBER_OF_ELEMENTS; i++) + { + int newLocation = random(NUMBER_OF_ELEMENTS); + long long tempValue = priorityArray[i]._value; + priorityArray[i]._value = priorityArray[newLocation]._value; + priorityArray[newLocation]._value = tempValue; + } + + // Find K largest in priorityArray + findKLargest(priorityArray, K_NUMBER, NUMBER_OF_ELEMENTS); + + // Comparison. The first item is the largest one + for (int j = 0; j < K_NUMBER; j++) + { + EXPECT_EQ(priorityArray[j]._value, value); + } +} + diff --git a/test/fourq.cpp b/test/fourq.cpp new file mode 100644 index 000000000..1f606ef9d --- /dev/null +++ b/test/fourq.cpp @@ -0,0 +1,264 @@ +#define NO_UEFI + +#include "../src/platform/memory.h" +#include "../src/four_q.h" +#include "utils.h" + +#include +#include "gtest/gtest.h" + +#include +#include + +static constexpr int ID_SIZE = 61; +static inline void getIDChar(const unsigned char* key, char* identity, bool isLowerCase) +{ + CHAR16 computorID[61]; + getIdentity(key, computorID, true); + for (int k = 0; k < 60; ++k) + { + identity[k] = computorID[k] - L'a' + 'a'; + } + identity[60] = 0; +} + +TEST(TestFourQ, TestMultiply) +{ + // 8 test cases for 256-bit multiplication + unsigned long long a[8][4] = { + {9951791076627133056ULL, 8515301911953011018ULL, 10503917255838740547ULL, 9403542041099946340ULL}, + {9634782769625085733ULL, 3923345248364070851ULL, 12874006609097115757ULL, 9445681298461330583ULL}, + {9314926113594160360ULL, 9012577733633554087ULL, 15853326627100346762ULL, 3353532907889994600ULL}, + {11822735244239455150ULL, 14860878323222532373ULL, 839169842161576273ULL, 8384082473945502970ULL}, + {6391904870724534887ULL, 7752608459014781040ULL, 8834893383869603648ULL, 14432583643443481392ULL}, + {9034457083341789982ULL, 15550692794033658766ULL, 18370398459251091929ULL, 161212377777301450ULL}, + {12066041174979511630ULL, 6197228902632247602ULL, 15544684064627230784ULL, 8662358800126738212ULL}, + {2997608593061094407ULL, 10746661492960439270ULL, 13066743968851273858ULL, 901611315508727516ULL} + }; + + unsigned long long b[8][4] = { + {14556080569315562443ULL, 4784279743451576405ULL, 16952050128007612055ULL, 17448141405813274955ULL}, + {16953856751996506377ULL, 5957469746201176117ULL, 413985909494190460ULL, 5019301766552018644ULL}, + {8337584125020700765ULL, 9891896711220896307ULL, 3688562803407556063ULL, 15879907979249125147ULL}, + {5253913930687524613ULL, 14356908424098313115ULL, 7294083945257658276ULL, 11357758627518780620ULL}, + {6604082675214113798ULL, 8102242472442817269ULL, 4231600794557460268ULL, 9254306641367892880ULL}, + {15307070962626904180ULL, 14565308158529607085ULL, 7804612167412830134ULL, 11197002641182899202ULL}, + {5681082236069360781ULL, 11354469612480482261ULL, 10740484893427922886ULL, 4093428096946105430ULL}, + {16936346349005670285ULL, 16111331879026478134ULL, 281576863978497861ULL, 4843225515675739317ULL} + }; + + unsigned long long expectedMultiplicationResults[8][8] = { + {13505937776277228416ULL, 10691581058996783029ULL, 15857294677093499275ULL, 10551077288120234079ULL, + 10488747005868148888ULL, 3163167577502768305ULL, 12011108917152358447ULL, 8894487319443104894ULL}, + + {11258722506082215245ULL, 3752109657065586715ULL, 9754007644313481322ULL, 10650543212606486248ULL, + 14000725040689989368ULL, 6868107242688413590ULL, 12132679480588703742ULL, 2570140542862762927ULL}, + + {7980800202961401928ULL, 18091087109835938886ULL, 11937230724836153237ULL, 18437285308724511498ULL, + 9256451621004954121ULL, 2817287347866660760ULL, 7356871972350029435ULL, 2886893956455686033ULL}, + + {14821519003237893222ULL, 11951435221854993875ULL, 5649570579164725909ULL, 16529503750125471729ULL, + 5712698943065886767ULL, 10417044944053178538ULL, 10215165497768617151ULL, 5162124257364100363ULL}, + + {10299854845140439658ULL, 5620198573463725080ULL, 18403939479767000599ULL, 3997239017815343129ULL, + 17558583433366224073ULL, 16662387952814143598ULL, 16240400534467578973ULL, 7240494806558978920ULL}, + + {15852260790186789272ULL, 6843720495231156925ULL, 18209245341578934878ULL, 3229051715759855960ULL, + 6553675393672969791ULL, 11442787882881602486ULL, 5043402961965006398ULL, 97854418782578342ULL}, + + {16919816915648955382ULL, 15728350531867604818ULL, 262149976282468082ULL, 16822220236393767682ULL, + 17482650320082366559ULL, 4634282717190265856ULL, 3892072508178358212ULL, 1922222304195309433ULL}, + + {3674093358531938523ULL, 797775358977430453ULL, 6686355987721165902ULL, 16831290265741585642ULL, + 11378657779800286238ULL, 14872963278680745844ULL, 15850192255010623436ULL, 236719656924026199ULL} + }; + + long long averageProcessingTime = 0; + for (int i = 0; i < 8; i++) + { + unsigned long long result[8] = { 0 }; + + multiply(a[i], b[i], result); + + for (int k = 0; k < 8; k++) + { + EXPECT_EQ(result[k], expectedMultiplicationResults[i][k]) << " at [" << k << "]"; + } + } +} + +TEST(TestFourQ, TestMontgomeryMultiplyModOrder) +{ + + // 8 test cases for 256-bit MontgomeryMultiplyMod + unsigned long long a[8][4] = { + {9951791076627133056ULL, 8515301911953011018ULL, 10503917255838740547ULL, 9403542041099946340ULL}, + {9634782769625085733ULL, 3923345248364070851ULL, 12874006609097115757ULL, 9445681298461330583ULL}, + {9314926113594160360ULL, 9012577733633554087ULL, 15853326627100346762ULL, 3353532907889994600ULL}, + {11822735244239455150ULL, 14860878323222532373ULL, 839169842161576273ULL, 8384082473945502970ULL}, + {6391904870724534887ULL, 7752608459014781040ULL, 8834893383869603648ULL, 14432583643443481392ULL}, + {9034457083341789982ULL, 15550692794033658766ULL, 18370398459251091929ULL, 161212377777301450ULL}, + {12066041174979511630ULL, 6197228902632247602ULL, 15544684064627230784ULL, 8662358800126738212ULL}, + {2997608593061094407ULL, 10746661492960439270ULL, 13066743968851273858ULL, 901611315508727516ULL} + }; + + unsigned long long b[8][4] = { + {14556080569315562443ULL, 4784279743451576405ULL, 16952050128007612055ULL, 17448141405813274955ULL}, + {16953856751996506377ULL, 5957469746201176117ULL, 413985909494190460ULL, 5019301766552018644ULL}, + {8337584125020700765ULL, 9891896711220896307ULL, 3688562803407556063ULL, 15879907979249125147ULL}, + {5253913930687524613ULL, 14356908424098313115ULL, 7294083945257658276ULL, 11357758627518780620ULL}, + {6604082675214113798ULL, 8102242472442817269ULL, 4231600794557460268ULL, 9254306641367892880ULL}, + {15307070962626904180ULL, 14565308158529607085ULL, 7804612167412830134ULL, 11197002641182899202ULL}, + {5681082236069360781ULL, 11354469612480482261ULL, 10740484893427922886ULL, 4093428096946105430ULL}, + {16936346349005670285ULL, 16111331879026478134ULL, 281576863978497861ULL, 4843225515675739317ULL} + }; + + unsigned long long expectedMontgomeryMultiplyModOrderResults[8][4] = { + {1178600784049730938ULL,13475129099769568773ULL,8171380610981515619ULL,8889798462048389782ULL,}, + {9346893806433251032ULL,3783576366952291632ULL,9006661425833189295ULL,2561156787602149305ULL,}, + {15012255874803770290ULL,11566062810664104635ULL,4497422501535458145ULL,2875434161571900946ULL,}, + {12004931297526373125ULL,10222857380028780508ULL,17154413062382081055ULL,5158706721726943589ULL,}, + {9724623868153589743ULL,17410218506619807138ULL,7496133043478274651ULL,7229774243864893754ULL,}, + {10156145653863087884ULL,16403847498912163678ULL,18326820829694769537ULL,90612319098335675ULL,}, + {2160566501146101694ULL,16888000406840707060ULL,8270191582668357443ULL,1911769260568884212ULL,}, + {6774298701428010308ULL,11825708701777781499ULL,11766967071579472107ULL,229574109642630470ULL,}, + }; + + for (int i = 0; i < 8; i++) + { + unsigned long long result[4] = { 0 }; + // (a * b) mod n + Montgomery_multiply_mod_order(a[i], b[i], result); + + for (int k = 0; k < 4; k++) + { + EXPECT_EQ(result[k], expectedMontgomeryMultiplyModOrderResults[i][k]) << " at [" << k << "]"; + } + } +} + +TEST(TestFourQ, TestGenerateKeys) +{ + // Data generate from clang-compiled qubic-cli + unsigned char computorSeeds[][56] = { + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "nhtighfbfdvgxnxrwxnbfmisknawppoewsycodciozvqpeqegttwofg", + "fcvgljppwwwjhrawxeywxqdgssttiihcmikxbnnunugldvcitkhcrfl", + "eijytgswxqzfkotmqvwulivpbximuhmgydvnaozwszguqflpfvltqge", + }; + unsigned char expectedPrivateIDs[][ID_SIZE] = { + "cctwbaulwuyhybijykxrmxnyrvzbalwryiiahltfwanuafhyfhepcjjgvaec", + "qfldkxspcgsnbgnccmsjftuhxvlfrmarkqrvqjvjaebwoasbytasvcffdfwd", + "mqlgaugwpdaphhqsacmqdcioomybxkaisrwyefyisayrikqjlckwkpdhuqlc", + "qwqmcjhdlzphgabvnjbedsgwrgpbvplcipmxkzuogglbhzfjiytunaeactsn", + }; + unsigned char expectedPublicIDs[][ID_SIZE] = { + "bzbqfllbncxemglobhuvftluplvcpquassilfaboffbcadqssupnwlzbqexk", + "lsgscfhdoahhlbdmlyzrfkvsfrqbbuznganescizcetyxkcdhljhemofxcwb", + "zsvpltnzfdyjzetanimltroldybdzoctvfguybpbvdxbndsrhyreppgccspo", + "xcfqbuwxxtufpfwyteglgchgnqyanubfbkpwtivfobxybgaqcgiqmzlgscwe" + }; + + unsigned char computorSubseeds[32]; + unsigned char computorPrivateKeys[32]; + unsigned char computorPublicKeys[32]; + char privakeyKeyId[ID_SIZE]; + char publicKeyId[ID_SIZE]; + + int numberOfTests = sizeof(computorSeeds) / sizeof(computorSeeds[0]); + + for (int i = 0; i < numberOfTests; ++i) + { + getSubseed(computorSeeds[i], computorSubseeds); + getPrivateKey(computorSubseeds, computorPrivateKeys); + getPublicKey(computorPrivateKeys, computorPublicKeys); + + getIDChar(computorPrivateKeys, privakeyKeyId, true); + getIDChar(computorPublicKeys, publicKeyId, true); + // Verification + for (int k = 0; k < ID_SIZE; ++k) + { + EXPECT_EQ(expectedPrivateIDs[i][k], privakeyKeyId[k]) << " at [" << i << "][" << k << "]"; + EXPECT_EQ(expectedPublicIDs[i][k], publicKeyId[k]) << " at [" << i << "][" << k << "]"; + } + } +} + +// sign(const unsigned char* subseed, const unsigned char* publicKey, const unsigned char* messageDigest, unsigned char* signature) +TEST(TestFourQ, TestSign) +{ + // For sign and verification, some constants need to be set +#ifdef __AVX512F__ + initAVX512FourQConstants(); +#endif + + const std::string subSeedsStr[] = { + "4ac19e2bf0d3776519aabe31924f7dc2589b3d0e7411a65f84c9b16df72c038e", + "e8217c5b40aa91df662803ce4dbf18722e35f1097ac68fb5da10643a825799e3", + "6d02f48bcb53ac397fc71a9028e4165df9b87044c53e116a0192d7fa83254bb0", + "3cfa1097be482f6e5ce132c2aa657d0fb9d84121048de6f05b90a2cc136bf73a", + "5208dd447a21f3c9911cae547637c580e74fa40d2a9c5e1fb26dcbfa408539d1", + "987b163dc6fd492573b4e18af7016c52a027ced5345fb8904e69037ed2ac1b81", + "d462ab0c83f931c4a87f12953e20bd576be849da5a8f1402c3b176ef92486d05", + "713fc86e289f124bdb2a65f6a37c01e0559a148ebf430f6729c184de76b23a90", + "15cd5b0700000000b168de3a00000000743af15000000000efcdab0000000000" + }; + + const std::string messageDigestsStr[] = { + "94e120a4d3f58c217a53eb9046d9f2c5b11288a9fe340d6ce5a771cf04b82e63", + "77f493b58ea40162dc33f9a718e2543b05f629884d7ca0e31598c45f021ae7c0", + "5cc82fa973101da5bfb3e2448196f0a7d7d3324c86fbbe42907613d5c8c2f1a4", + "c01ae5f2879d11439b30ddae5f4c7b22689f023e17b4955c3b2f05e8d9089af6", + "2f893e70ad52c9186eb4b60dfe137288c4a9e0fb6d34a51897e2365a01b0d443", + "ec09a3f415c2dda5f8419026678ab03524f67ed9817ba230cd24513750e01bc6", + "48b59f32a61dff0e13528e4c7937bdf080c3efa7d221364be87f01d5c60f882a", + "b31e704c8a3f1d02692f05a7d8e5f6c911d370f4a68b2ec3fa4c51d7289003de", + "89d5f92a895987457400219e121e8730f6b248a1fd28bfee017611ef079105b5" + }; + + const std::string expectedSignaturesStr[] = + { + "357d47b1366f33eed311a4458ec7326d35728e9292328a9b7ff8d4ec0f7b0df9323f5d1cd01bd5a380a1a8e4f29ad3ae9c5d94e84f4181a61ca73030d6d11600", + "7d2479a15746839c4c5e1fdf0aadb167974c292ceee80593e18b5135763db63163d8eee5bd309c506f47b16cd1242ddfe985887b19d3943c14ec6ab79a9c1900", + "1b8ed83af3dc12deb1554f48df46bf5bc5e4654f62f97ef20656fae4e965ac87762c9fe6189dfe89192a619bca4a6c390f4e97bb1f926041263f2ba4206b0c00", + "470ad247ff6b2e55d44e9f2a79ce402bfc5e8c5322ed297f71939a9c5398b6fcb5058c05e614d10d90d6bdec8ee4ecc6462cdd54e0ea830fde6be465de3f2900", + "0851db3d4021bdc8cf3816b4672aba2f5f7cd0bf19e779e28ee60241bc4246dabe7442a11953703a44ed1cadd0af9fce683c5a312326341ac7a3e55a18c40100", + "54466ae5ecad45c83798e4e3e02ab40e834bf8d3f4f1628b300601ab87894599a43278efd48be7e9615cd569e656356a9e2307ae257b85a3f1f0f333f2302200", + "6d67294ccd03dc51fdb3bca649b7e060d3cf06c417e7053472ca617b93e5926928a7a48b1791c2487c7e83eeb4919046493709508c0541d1c02e9545401b2100", + "20764a88943fb4e796f81a560bde5e652c82ffb203c00b4846102a268ae68f64cdee6c7a3edbf4de48dd25fd423a4b40e79d97a2a47fd11030b6f30a09130b00", + "9f71d3138ff8a72db3b39883e056ce7f5bfe40de6387e64eff0c17e72bd1862ccd848000be1841725f1da87654235329b685e1c81c939cb0154bbc8d30a20c00", + }; + + constexpr size_t numberOfTests = sizeof(subSeedsStr) / sizeof(subSeedsStr[0]); + m256i subseeds[numberOfTests]; + m256i messageDigests[numberOfTests]; + unsigned char expectedSignatures[numberOfTests][64]; + + for (unsigned long long i = 0; i < numberOfTests; ++i) + { + subseeds[i] = test_utils::hexTo32Bytes(subSeedsStr[i], 32); + messageDigests[i] = test_utils::hexTo32Bytes(messageDigestsStr[i], 32); + test_utils::hexToByte(expectedSignaturesStr[i], 64, expectedSignatures[i]); + } + + for (unsigned long long i = 0; i < numberOfTests; ++i) + { + unsigned char publicKey[32]; + unsigned char privateKey[32]; + getPrivateKey(subseeds[i].m256i_u8, privateKey); + getPublicKey(privateKey, publicKey); + + unsigned char signature[64]; + sign(subseeds[i].m256i_u8, publicKey, messageDigests[i].m256i_u8, signature); + + // Verify functions + bool verifyStatus = verify(publicKey, messageDigests[i].m256i_u8, signature); + + EXPECT_TRUE(verifyStatus); + + for (int k = 0; k < 64; ++k) + { + EXPECT_EQ(expectedSignatures[i][k], signature[k]) << " at [" << i << "][" << k << "]"; + } + } +} diff --git a/test/kangaroo_twelve.cpp b/test/kangaroo_twelve.cpp new file mode 100644 index 000000000..231317c8a --- /dev/null +++ b/test/kangaroo_twelve.cpp @@ -0,0 +1,92 @@ +#define NO_UEFI + +#include "../src/K12/kangaroo_twelve_xkcp.h" +#include "../src/kangaroo_twelve.h" +#include "../src/platform/memory.h" +#include +#include "gtest/gtest.h" + +#include +#include + + +TEST(TestCoreK12, PerformanceDigest32Of1GB) +{ + constexpr size_t bytesPerGigaByte = 1024 * 1024 * 1024; + constexpr size_t repN = 1; + constexpr size_t inputN = bytesPerGigaByte; + constexpr size_t outputN = 32; + + char* inputPtr = new char[inputN]; + for (size_t i = 0; i < 100; ++i) + { + unsigned int pos, val; + _rdrand32_step(&pos); + _rdrand32_step(&val); + inputPtr[pos % inputN] = val & 0xff; + } + char outputArray[outputN]; + + auto startTime = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < repN; ++i) + XKCP::KangarooTwelve((unsigned char *) inputPtr, inputN, (unsigned char*) outputArray, outputN); + auto durationMilliSec = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime); + + double bytePerMilliSec = double(repN * inputN) / double(durationMilliSec.count()); + double gigaBytePerSec = bytePerMilliSec * (1000.0 / bytesPerGigaByte); + std::cout << "K12 of 1 GB to 32 Byte digest: " << gigaBytePerSec << " GB/sec = " << 1.0 / gigaBytePerSec << " sec/GB" << std::endl; + + delete [] inputPtr; +} + +TEST(TestCoreK12, CompareK12Implementations) +{ + constexpr size_t bytesPerGigaByte = 1024 * 1024 * 1024; + constexpr size_t repN = 1; + constexpr size_t inputN = bytesPerGigaByte; + constexpr size_t outputN = 32; + + char* inputPtr = new char[inputN]; + for (size_t i = 0; i < 100; ++i) + { + unsigned int pos, val; + _rdrand32_step(&pos); + _rdrand32_step(&val); + inputPtr[pos % inputN] = val & 0xff; + } + char outputArrayXKCP[outputN]; + + auto startTime = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < repN; ++i) + XKCP::KangarooTwelve((unsigned char *) inputPtr, inputN, (unsigned char*) outputArrayXKCP, outputN); + auto durationMilliSec = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime); + + double bytePerMilliSec = double(repN * inputN) / double(durationMilliSec.count()); + double gigaBytePerSec = bytePerMilliSec * (1000.0 / bytesPerGigaByte); + std::cout << "K12 of 1 GB to 32 Byte digest: " << gigaBytePerSec << " GB/sec = " << 1.0 / gigaBytePerSec << " sec/GB" << std::endl; + std::cout << "Digest of xkcp implementation: "; + for (int i = 0; i < sizeof(outputArrayXKCP); i++){ + std::cout << std::hex << std::setfill('0') << std::setw(2) + << (static_cast(outputArrayXKCP[i]) & 0xff); + } + std::cout << std::endl; + + char outputArray[outputN]; + startTime = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < repN; ++i) + KangarooTwelve((unsigned char *) inputPtr, inputN, (unsigned char*) outputArray, outputN); + durationMilliSec = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime); + + bytePerMilliSec = double(repN * inputN) / double(durationMilliSec.count()); + gigaBytePerSec = bytePerMilliSec * (1000.0 / bytesPerGigaByte); + std::cout << "K12 of 1 GB to 32 Byte digest: " << gigaBytePerSec << " GB/sec = " << 1.0 / gigaBytePerSec << " sec/GB" << std::endl; + std::cout << "Digest of native implementation: "; + + for (int i = 0; i < sizeof(outputArray); i++){ + std::cout << std::hex << std::setfill('0') << std::setw(2) + << (static_cast(outputArray[i]) & 0xff); + } + std::cout << std::endl; + ASSERT_EQ(memcmp(outputArrayXKCP, outputArray, outputN), 0); + delete [] inputPtr; +} diff --git a/test/logging_test.h b/test/logging_test.h new file mode 100644 index 000000000..cefbacf46 --- /dev/null +++ b/test/logging_test.h @@ -0,0 +1,42 @@ +#pragma once + +#include "gtest/gtest.h" + +// workaround for name clash with stdlib +#define system qubicSystemStruct + +// enable all logging that is used in tests +#include "private_settings.h" +#undef LOG_SPECTRUM +#define LOG_SPECTRUM 1 + +// also reduce size of logging tx index by reducing maximum number of ticks per epoch +#include "public_settings.h" +#undef MAX_NUMBER_OF_TICKS_PER_EPOCH +#define MAX_NUMBER_OF_TICKS_PER_EPOCH 3000 + +// Reduce virtual memory size for testing +#undef LOG_BUFFER_PAGE_SIZE +#undef PMAP_LOG_PAGE_SIZE +#undef IMAP_LOG_PAGE_SIZE +#undef VM_NUM_CACHE_PAGE +#define LOG_BUFFER_PAGE_SIZE 10000000ULL +#define PMAP_LOG_PAGE_SIZE 1000000ULL +#define IMAP_LOG_PAGE_SIZE 300ULL +#define VM_NUM_CACHE_PAGE 1 + +#include "logging/logging.h" + +class LoggingTest +{ +public: + LoggingTest() + { + EXPECT_TRUE(qLogger::initLogging()); + } + + ~LoggingTest() + { + qLogger::deinitLogging(); + } +}; diff --git a/test/m256.cpp b/test/m256.cpp new file mode 100644 index 000000000..8ddae7baf --- /dev/null +++ b/test/m256.cpp @@ -0,0 +1,274 @@ +#define NO_UEFI + +#include "gtest/gtest.h" +#include "../src/platform/m256.h" + +#include + + +TEST(TestCore256BitClass, ConstructAssignCompare) { + // Basic construction, comparison, and assignment + unsigned char buffer_u8[32]; + for (int8_t i = 0; i < 32; ++i) + buffer_u8[i] = 3 + 2 * i; + + m256i v1(buffer_u8); + for (int8_t i = 0; i < 32; ++i) + EXPECT_EQ(v1.m256i_u8[i], 3 + 2 * i); + EXPECT_TRUE(v1 == buffer_u8); + EXPECT_FALSE(v1 != buffer_u8); + EXPECT_FALSE(isZero(v1)); + + for (int8_t i = 0; i < 32; ++i) + buffer_u8[i] = 250 - 3 * i; + + for (int8_t i = 0; i < 32; ++i) + EXPECT_EQ(v1.m256i_u8[i], 3 + 2 * i); + EXPECT_TRUE(v1 != buffer_u8); + EXPECT_FALSE(v1 == buffer_u8); + EXPECT_FALSE(isZero(v1)); + + m256i v2(buffer_u8); + for (int8_t i = 0; i < 32; ++i) + EXPECT_EQ(v2.m256i_u8[i], 250 - 3 * i); + EXPECT_TRUE(v1 != v2); + EXPECT_FALSE(v1 == v2); + EXPECT_FALSE(isZero(v2)); + + m256i v3(v1); + for (int8_t i = 0; i < 32; ++i) + EXPECT_EQ(v3.m256i_u8[i], 3 + 2 * i); + EXPECT_TRUE(v1 == v3); + EXPECT_FALSE(v1 != v3); + EXPECT_FALSE(isZero(v3)); + + __m256i buffer_intr; + unsigned char* bytes_of_buffer_intr = reinterpret_cast(&buffer_intr); + for (uint8_t i = 0; i < 32; ++i) { + bytes_of_buffer_intr[i] = 90 + i; + } + + m256i v4(buffer_intr); + for (int8_t i = 0; i < 32; ++i) + EXPECT_EQ(v4.m256i_u8[i], 90 + i); + EXPECT_TRUE(v4 == buffer_intr); + EXPECT_FALSE(v4 != buffer_intr); + EXPECT_TRUE(v4 != v3); + EXPECT_FALSE(v4 == v3); + EXPECT_TRUE(v4 != v2); + EXPECT_FALSE(v4 == v2); + EXPECT_TRUE(v4 != v1); + EXPECT_FALSE(v4 == v1); + EXPECT_FALSE(isZero(v4)); + + // Construction, comparison, and assignment involving volatile + volatile m256i v5(v4); + EXPECT_TRUE(v4 == v5); + EXPECT_FALSE(v4 != v5); + EXPECT_FALSE(isZero(v5)); + + m256i v6(v5); + EXPECT_TRUE(v6 == v5); + EXPECT_FALSE(v6 != v5); + EXPECT_FALSE(isZero(v6)); + + EXPECT_TRUE(v1 != v5); + EXPECT_FALSE(v1 == v5); + v1 = v5; + EXPECT_TRUE(v1 == v5); + EXPECT_FALSE(v1 != v5); + EXPECT_FALSE(isZero(v1)); + + EXPECT_TRUE(v2 != v5); + EXPECT_FALSE(v2 == v5); + v5 = v2; + EXPECT_TRUE(v2 == v5); + EXPECT_FALSE(v2 != v5); + EXPECT_FALSE(isZero(v2)); + + v5.m256i_i64[0] = 0; + v5.m256i_i64[1] = 0; + v5.m256i_i64[2] = 0; + v5.m256i_i64[3] = 0; + EXPECT_TRUE(isZero(v5)); + EXPECT_TRUE(v2 != v5); + EXPECT_FALSE(v2 == v5); + v2 = v5; + EXPECT_TRUE(v2 == v5); + EXPECT_FALSE(v2 != v5); + EXPECT_TRUE(isZero(v2)); + + // single-bit difference test + for (int i = 0; i < 32; ++i) + { + for (int j = 0; j < 8; ++j) + { + v2 = v5; + EXPECT_TRUE(isZero(v2)); + EXPECT_TRUE(v2 == v5); + EXPECT_FALSE(v2 != v5); + v2.m256i_u8[i] |= (1 << j); + EXPECT_FALSE(isZero(v2)); + EXPECT_TRUE(v2 != v5); + EXPECT_FALSE(v2 == v5); + } + } + + // self-assignment and comparison + m256i v1_original_state = v1; // Capture original state for later comparison + m256i v5_original_state = v5; + + SUPPRESS_WARNINGS_BEGIN + IGNORE_SELFASSIGNMENT_WARNING + v1 = v1; // This line is intentionally testing self-assignment + SUPPRESS_WARNINGS_END + + EXPECT_TRUE(v1 == v1_original_state); + EXPECT_TRUE(v1 == v1); + EXPECT_FALSE(v1 != v1); + v5 = v5; + EXPECT_TRUE(v5 == v5_original_state); + EXPECT_TRUE(v5 == v5); + EXPECT_FALSE(v5 != v5); + + // Non-aligned assignment and comparison + unsigned char buffer_u8_64[64]; + for (int8_t i = 0; i < 64; ++i) + buffer_u8_64[i] = 7 + i; + for (int8_t i = 0; i < 32; ++i) + { + v1 = buffer_u8_64 + i; + EXPECT_TRUE(v1 == buffer_u8_64 + i); + EXPECT_FALSE(v1 != buffer_u8_64 + i); + EXPECT_FALSE(isZero(v1)); + for (int j = 0; j < 32; ++j) + EXPECT_EQ(v1.m256i_u8[j], 7 + i + j); + } + + // m256i::zero() + EXPECT_FALSE(isZero(v1)); + EXPECT_FALSE(v1 == m256i::zero()); + EXPECT_TRUE(v1 != m256i::zero()); + v1 = m256i::zero(); + EXPECT_TRUE(isZero(v1)); + EXPECT_TRUE(v1 == m256i::zero()); + EXPECT_FALSE(v1 != m256i::zero()); + + // 4 x u64 constructor + m256i v7(1234, 5678, 9012, 3456); + EXPECT_EQ(v7.u64._0, 1234); + EXPECT_EQ(v7.u64._1, 5678); + EXPECT_EQ(v7.u64._2, 9012); + EXPECT_EQ(v7.u64._3, 3456); + EXPECT_EQ(v7.m256i_u64[0], 1234); + EXPECT_EQ(v7.m256i_u64[1], 5678); + EXPECT_EQ(v7.m256i_u64[2], 9012); + EXPECT_EQ(v7.m256i_u64[3], 3456); +} + +TEST(TestCore256BitFunctionsIntrinsicType, isZero) { + EXPECT_TRUE(isZero(m256i(0, 0, 0, 0).getIntrinsicValue())); + EXPECT_FALSE(isZero(m256i(1, 0, 0, 0).getIntrinsicValue())); + EXPECT_FALSE(isZero(m256i(0, 1, 0, 0).getIntrinsicValue())); + EXPECT_FALSE(isZero(m256i(0, 0, 1, 0).getIntrinsicValue())); + EXPECT_FALSE(isZero(m256i(0, 0, 0, 1).getIntrinsicValue())); + EXPECT_FALSE(isZero(m256i(0xffffffffffffffff,0xffffffffffffffff,0xffffffffffffffff,0xffffffffffffffff).getIntrinsicValue())); +} + +TEST(TestCore256BitFunctionsIntrinsicType, isZeroPerformance) +{ + constexpr int N = 50000000; + volatile m256i optimizeBarrierValue(0, 0, 0, 0); + m256i value; + [[maybe_unused]] volatile bool optimizeBarrierResult; + + // measure isZero + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N; ++i) + { + optimizeBarrierValue.i64._0 = i; + value = optimizeBarrierValue; + optimizeBarrierResult = isZero(value); + } + auto t1 = std::chrono::high_resolution_clock::now(); + auto ms = std::chrono::duration_cast(t1 - t0).count(); + std::cout << N << " x isZero: " << ms << " milliseconds" << std::endl; + + // measure comparison with existing zero m256i + t0 = std::chrono::high_resolution_clock::now(); + m256i zero = m256i::zero(); + for (int i = 0; i < N; ++i) + { + optimizeBarrierValue.i64._0 = i; + value = optimizeBarrierValue; + optimizeBarrierResult = (value == zero); + } + t1 = std::chrono::high_resolution_clock::now(); + ms = std::chrono::duration_cast(t1 - t0).count(); + std::cout << N << " x compare with existing zero instance: " << ms << " milliseconds" << std::endl; + + // measure comparison with new zero m256i + t0 = std::chrono::high_resolution_clock::now(); + volatile m256i zeroVol; + for (int i = 0; i < N; ++i) + { + optimizeBarrierValue.i64._0 = i; + value = optimizeBarrierValue; + zeroVol = m256i::zero(); + optimizeBarrierResult = (value == zeroVol); + } + t1 = std::chrono::high_resolution_clock::now(); + ms = std::chrono::duration_cast(t1 - t0).count(); + std::cout << N << " x compare with new zero instance: " << ms << " milliseconds" << std::endl; +} + + +TEST(TestCore256BitFunctions, operatorEqual) { + EXPECT_TRUE(m256i(0, 0, 0, 0) == m256i(0, 0, 0, 0)); + EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(0, 0, 0, 1)); + EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(0, 0, 1, 0)); + EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(0, 1, 0, 0)); + EXPECT_FALSE(m256i(0, 0, 0, 0) == m256i(1, 0, 0, 0)); + + EXPECT_TRUE(m256i(42, 42, 42, 42) == m256i(42, 42, 42, 42)); + EXPECT_FALSE(m256i(0, 42, 42, 42) == m256i(42, 42, 42, 42)); + EXPECT_FALSE(m256i(42, 0, 42, 42) == m256i(42, 42, 42, 42)); + EXPECT_FALSE(m256i(42, 42, 0, 42) == m256i(42, 42, 42, 42)); + EXPECT_FALSE(m256i(42, 42, 42, 0) == m256i(42, 42, 42, 42)); +} + +TEST(TestCore256BitFunctions, operatorNotEqual) { + EXPECT_FALSE(m256i(0, 0, 0, 0) != m256i(0, 0, 0, 0)); + EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(0, 0, 0, 1)); + EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(0, 0, 1, 0)); + EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(0, 1, 0, 0)); + EXPECT_TRUE(m256i(0, 0, 0, 0) != m256i(1, 0, 0, 0)); + + EXPECT_FALSE(m256i(42, 42, 42, 42) != m256i(42, 42, 42, 42)); + EXPECT_TRUE(m256i(0, 42, 42, 42) != m256i(42, 42, 42, 42)); + EXPECT_TRUE(m256i(42, 0, 42, 42) != m256i(42, 42, 42, 42)); + EXPECT_TRUE(m256i(42, 42, 0, 42) != m256i(42, 42, 42, 42)); + EXPECT_TRUE(m256i(42, 42, 42, 0) != m256i(42, 42, 42, 42)); +} + +TEST(TestCore256BitFunctions, operatorLessThan) { + EXPECT_FALSE(m256i(0, 0, 0, 0) < m256i(0, 0, 0, 0)); + EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(0, 0, 0, 1)); + EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(0, 0, 1, 0)); + EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(0, 1, 0, 0)); + EXPECT_TRUE(m256i(0, 0, 0, 0) < m256i(1, 0, 0, 0)); + EXPECT_FALSE(m256i(0, 0, 0, 1) < m256i(0, 0, 0, 0)); + EXPECT_FALSE(m256i(0, 0, 1, 0) < m256i(0, 0, 0, 0)); + EXPECT_FALSE(m256i(0, 1, 0, 0) < m256i(0, 0, 0, 0)); + EXPECT_FALSE(m256i(1, 0, 0, 0) < m256i(0, 0, 0, 0)); + + EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 100, 100, 100)); + EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 100, 100, 1)); + EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 100, 1, 100)); + EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(100, 1, 100, 100)); + EXPECT_FALSE(m256i(100, 100, 100, 100) < m256i(1, 100, 100, 100)); + EXPECT_TRUE(m256i(100, 100, 100, 1) < m256i(100, 100, 100, 100)); + EXPECT_TRUE(m256i(100, 100, 1, 100) < m256i(100, 100, 100, 100)); + EXPECT_TRUE(m256i(100, 1, 100, 100) < m256i(100, 100, 100, 100)); + EXPECT_TRUE(m256i(1, 100, 100, 100) < m256i(100, 100, 100, 100)); +} diff --git a/test/math_lib.cpp b/test/math_lib.cpp new file mode 100644 index 000000000..6769b830e --- /dev/null +++ b/test/math_lib.cpp @@ -0,0 +1,88 @@ +#include "gtest/gtest.h" + +#include +#include "../src/contracts/math_lib.h" + +TEST(TestCoreMathLib, Max) { + EXPECT_EQ(math_lib::max(0, 0), 0); + EXPECT_EQ(math_lib::max(10, 0), 10); + EXPECT_EQ(math_lib::max(0, 20), 20); + EXPECT_EQ(math_lib::max(-10, 0), 0); + EXPECT_EQ(math_lib::max(0, -20), 0); + + EXPECT_EQ(math_lib::max(0.0, 0.0), 0.0); + EXPECT_EQ(math_lib::max(10.0, 0.0), 10.0); + EXPECT_EQ(math_lib::max(0.0, 20.0), 20.0); + EXPECT_EQ(math_lib::max(-10.0, 0.0), 0.0); + EXPECT_EQ(math_lib::max(0.0, -20.0), 0.0); +} + +TEST(TestCoreMathLib, Min) { + EXPECT_EQ(math_lib::min(0, 0), 0); + EXPECT_EQ(math_lib::min(10, 0), 0); + EXPECT_EQ(math_lib::min(0, 20), 0); + EXPECT_EQ(math_lib::min(-10, 0), -10); + EXPECT_EQ(math_lib::min(0, -20), -20); + + EXPECT_EQ(math_lib::min(0.0, 0.0), 0.0); + EXPECT_EQ(math_lib::min(10.0, 0.0), 0.0); + EXPECT_EQ(math_lib::min(0.0, 20.0), 0.0); + EXPECT_EQ(math_lib::min(-10.0, 0.0), -10.0); + EXPECT_EQ(math_lib::min(0.0, -20.0), -20.0); +} + +TEST(TestCoreMathLib, Abs) { + EXPECT_EQ(math_lib::abs(0), 0); + EXPECT_EQ(math_lib::abs(10), 10); + EXPECT_EQ(math_lib::abs(-1110), 1110); + EXPECT_EQ(math_lib::abs(-987654ll), 987654llu); + + EXPECT_EQ(math_lib::abs(0.0), 0.0); + EXPECT_EQ(math_lib::abs(-0.0), 0.0); + EXPECT_EQ(math_lib::abs(-1230.0f), 1230.0f); + EXPECT_EQ(math_lib::abs(INFINITY), INFINITY); + EXPECT_EQ(math_lib::abs(-INFINITY), INFINITY); + EXPECT_EQ(math_lib::abs(FP_NAN), FP_NAN); +} + +// div() and mod() are defined in qpi.h + +template +void testDivUp() +{ + EXPECT_EQ(int(math_lib::divUp(T(0), T(0))), 0); + EXPECT_EQ(int(math_lib::divUp(T(1), T(0))), 0); + EXPECT_EQ(int(math_lib::divUp(T(0), T(1))), 0); + EXPECT_EQ(int(math_lib::divUp(T(1), T(1))), 1); + EXPECT_EQ(int(math_lib::divUp(T(2), T(1))), 2); + EXPECT_EQ(int(math_lib::divUp(T(3), T(1))), 3); + EXPECT_EQ(int(math_lib::divUp(T(0), T(2))), 0); + EXPECT_EQ(int(math_lib::divUp(T(1), T(2))), 1); + EXPECT_EQ(int(math_lib::divUp(T(2), T(2))), 1); + EXPECT_EQ(int(math_lib::divUp(T(3), T(2))), 2); + EXPECT_EQ(int(math_lib::divUp(T(4), T(2))), 2); + EXPECT_EQ(int(math_lib::divUp(T(5), T(2))), 3); + EXPECT_EQ(int(math_lib::divUp(T(0), T(3))), 0); + EXPECT_EQ(int(math_lib::divUp(T(1), T(3))), 1); + EXPECT_EQ(int(math_lib::divUp(T(2), T(3))), 1); + EXPECT_EQ(int(math_lib::divUp(T(3), T(3))), 1); + EXPECT_EQ(int(math_lib::divUp(T(4), T(3))), 2); + EXPECT_EQ(int(math_lib::divUp(T(5), T(3))), 2); + EXPECT_EQ(int(math_lib::divUp(T(6), T(3))), 2); + EXPECT_EQ(int(math_lib::divUp(T(7), T(3))), 3); + EXPECT_EQ(int(math_lib::divUp(T(20), T(19))), 2); + EXPECT_EQ(int(math_lib::divUp(T(20), T(20))), 1); + EXPECT_EQ(int(math_lib::divUp(T(20), T(21))), 1); + EXPECT_EQ(int(math_lib::divUp(T(20), T(22))), 1); + EXPECT_EQ(int(math_lib::divUp(T(50), T(24))), 3); + EXPECT_EQ(int(math_lib::divUp(T(50), T(25))), 2); + EXPECT_EQ(int(math_lib::divUp(T(50), T(26))), 2); + EXPECT_EQ(int(math_lib::divUp(T(50), T(27))), 2); +} + +TEST(TestCoreMathLib, DivUp) { + testDivUp(); + testDivUp(); + testDivUp(); + testDivUp(); +} diff --git a/test/network_messages.cpp b/test/network_messages.cpp new file mode 100644 index 000000000..7994e4b0b --- /dev/null +++ b/test/network_messages.cpp @@ -0,0 +1,68 @@ +#include "gtest/gtest.h" + +#define NETWORK_MESSAGES_WITHOUT_CORE_DEPENDENCIES +#include "../src/network_messages/all.h" + + +TEST(TestCoreRequestResponseHeader, TestSize) { + RequestResponseHeader hdr; + memset(& hdr, 0, sizeof(hdr)); + EXPECT_EQ(0, hdr.size()); + EXPECT_EQ(0, hdr.type()); + EXPECT_EQ(0, hdr.dejavu()); + + EXPECT_TRUE(hdr.checkAndSetSize(1)); + EXPECT_EQ(1, hdr.size()); + + hdr.setSize<10>(); + EXPECT_EQ(10, hdr.size()); + + EXPECT_TRUE(hdr.checkAndSetSize(13579864)); + EXPECT_EQ(13579864, hdr.size()); + + hdr.setSize<9876543>(); + EXPECT_EQ(9876543, hdr.size()); + + EXPECT_TRUE(hdr.checkAndSetSize(0xffffff)); + EXPECT_EQ(0xffffff, hdr.size()); + + // maximum size is 0xffffff = 16777215 + EXPECT_FALSE(hdr.checkAndSetSize(RequestResponseHeader::max_size + 1)); + EXPECT_FALSE(hdr.checkAndSetSize(RequestResponseHeader::max_size * 2)); +} + +TEST(TestCoreRequestResponseHeader, TestPayload) { + RequestResponseHeader hdr; + memset(&hdr, 0, sizeof(hdr)); + EXPECT_EQ(hdr.getPayload(), ((char*)&hdr) + sizeof(RequestResponseHeader)); + + EXPECT_TRUE(hdr.checkAndSetSize(1234 + sizeof(RequestResponseHeader))); + EXPECT_TRUE(hdr.checkPayloadSize(1234)); + EXPECT_FALSE(hdr.checkPayloadSize(1235)); + EXPECT_TRUE(hdr.checkPayloadSizeMinMax(1234, 1234)); + EXPECT_TRUE(hdr.checkPayloadSizeMinMax(123, 1234)); + EXPECT_TRUE(hdr.checkPayloadSizeMinMax(1234, 12346)); + EXPECT_TRUE(hdr.checkPayloadSizeMinMax(123, 12346)); + EXPECT_FALSE(hdr.checkPayloadSizeMinMax(1235, 1235)); + EXPECT_FALSE(hdr.checkPayloadSizeMinMax(12, 13)); + EXPECT_FALSE(hdr.checkPayloadSizeMinMax(13, 12)); + EXPECT_EQ(hdr.getPayloadSize(), 1234); +} + +TEST(TestCoreCommonDef, IPv4Address) { + const unsigned char ip_char_array[4] = { 1, 2, 3, 4 }; + const IPv4Address& ip_ref = *reinterpret_cast(ip_char_array); + EXPECT_EQ(ip_ref.u8[0], 1); + EXPECT_EQ(ip_ref.u8[1], 2); + EXPECT_EQ(ip_ref.u8[2], 3); + EXPECT_EQ(ip_ref.u8[3], 4); + EXPECT_EQ(ip_ref.u32, 0x04030201); + + IPv4Address ip2 {{100, 200, 32, 4}}; + EXPECT_TRUE(ip_ref != ip2); + EXPECT_FALSE(ip_ref == ip2); + + ip2 = ip_ref; + EXPECT_TRUE(ip_ref == ip2); + EXPECT_FALSE(ip_ref != ip2); +} diff --git a/test/oracle_engine.cpp b/test/oracle_engine.cpp new file mode 100644 index 000000000..c8f8e083d --- /dev/null +++ b/test/oracle_engine.cpp @@ -0,0 +1,1325 @@ +#define NO_UEFI + +#include "oracle_testing.h" + +#include "platform/random.h" + + +struct OracleEngineTest : public LoggingTest +{ + OracleEngineTest() + { + EXPECT_TRUE(initSpectrum()); + EXPECT_TRUE(commonBuffers.init(1, 1024 * 1024)); + EXPECT_TRUE(initSpecialEntities()); + EXPECT_TRUE(initContractExec()); + EXPECT_TRUE(ts.init()); + EXPECT_TRUE(OI::initOracleInterfaces()); + + // init computors + for (int computorIndex = 0; computorIndex < NUMBER_OF_COMPUTORS; computorIndex++) + { + broadcastedComputors.computors.publicKeys[computorIndex] = m256i(computorIndex * 2, 42, 13, 1337); + } + + // setup tick and time + system.tick = 1000; + etalonTick.year = 25; + etalonTick.month = 12; + etalonTick.day = 15; + etalonTick.hour = 16; + etalonTick.minute = 51; + etalonTick.second = 12; + ts.beginEpoch(system.tick); + } + + ~OracleEngineTest() + { + deinitSpectrum(); + commonBuffers.deinit(); + deinitContractExec(); + ts.deinit(); + } +}; + +struct OracleEngineWithInitAndDeinit : public OracleEngine +{ + uint16_t ownComputorIdsBegin; + uint16_t ownComputorIdsEnd; + + OracleEngineWithInitAndDeinit(const m256i* ownComputorPublicKeys, uint16_t ownComputorIdsBegin, uint16_t ownComputorIdsEnd) + { + this->init(ownComputorPublicKeys); + this->ownComputorIdsBegin = ownComputorIdsBegin; + this->ownComputorIdsEnd = ownComputorIdsEnd; + } + + ~OracleEngineWithInitAndDeinit() + { + this->deinit(); + } + + uint32_t getReplyCommitTransaction( + void* txBuffer, uint16_t computorIdx, + uint32_t txScheduleTick, uint32_t startIdx = 0) + { + ASSERT(computorIdx >= ownComputorIdsBegin); + ASSERT(computorIdx < ownComputorIdsEnd); + return OracleEngine::getReplyCommitTransaction(txBuffer, computorIdx, txScheduleTick, startIdx); + } + + void checkPendingState(int64_t queryId, uint16_t totalCommitTxExecuted, uint16_t ownCommitTxExecuted, uint8_t expectedStatus) const + { + uint32_t queryIndex; + EXPECT_TRUE(this->queryIdToIndex->get(queryId, queryIndex)); + EXPECT_LT(queryIndex, this->oracleQueryCount); + const OracleQueryMetadata& oqm = this->queries[queryIndex]; + EXPECT_EQ(oqm.status, expectedStatus); + EXPECT_TRUE(oqm.status == ORACLE_QUERY_STATUS_PENDING || oqm.status == ORACLE_QUERY_STATUS_COMMITTED); + const OracleReplyState& replyState = this->replyStates[oqm.statusVar.pending.replyStateIndex]; + EXPECT_EQ((int)totalCommitTxExecuted, (int)replyState.totalCommits); + int executed = 0; + for (int i = ownComputorIdsBegin; i < ownComputorIdsEnd; ++i) + { + if (replyState.replyCommitTicks[i]) + ++executed; + } + EXPECT_EQ((int)ownCommitTxExecuted, (int)executed); + EXPECT_EQ(this->getOracleQueryStatus(queryId), expectedStatus); + } + + void checkStatus(int64_t queryId, uint8_t expectedStatus) const + { + uint32_t queryIndex; + EXPECT_TRUE(this->queryIdToIndex->get(queryId, queryIndex)); + EXPECT_LT(queryIndex, this->oracleQueryCount); + const OracleQueryMetadata& oqm = this->queries[queryIndex]; + EXPECT_EQ(oqm.status, expectedStatus); + } + + // Test findFirstQueryIndexOfTick(). Ticks must be sorted! + void testFindFirstQueryIndexOfTick(const std::vector& ticks) + { + // setup pseudo-queries + this->oracleQueryCount = 0; + for (auto tick : ticks) + this->queries[this->oracleQueryCount++].queryTick = tick; + + // test function with all values in range + uint32_t minValue = (ticks.empty()) ? 0 : ticks.front() - 1; + uint32_t maxValue = (ticks.empty()) ? 2 : ticks.back() + 1; + for (uint32 value = minValue; value <= maxValue; ++value) + { + auto it = std::find(ticks.begin(), ticks.end(), value); + uint32_t expectedIndex = (it == ticks.end()) ? UINT32_MAX : uint32(it - ticks.begin()); + EXPECT_EQ(this->findFirstQueryIndexOfTick(value), expectedIndex); + } + + this->reset(); + } +}; + +static void dummyNotificationProc(const QPI::QpiContextProcedureCall&, void* state, void* input, void* output, void* locals) +{ +} + +TEST(OracleEngine, ContractQuerySuccess) +{ + OracleEngineTest test; + + // simulate three nodes: one with 400 computor IDs, one with 200, and one with 76 + const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; + OracleEngineWithInitAndDeinit oracleEngine1(allCompPubKeys, 0, 400); + OracleEngineWithInitAndDeinit oracleEngine2(allCompPubKeys, 400, 600); + OracleEngineWithInitAndDeinit oracleEngine3(allCompPubKeys, 600, 676); + + OI::Price::OracleQuery priceQuery; + priceQuery.oracle = m256i(1, 2, 3, 4); + priceQuery.currency1 = m256i(2, 3, 4, 5); + priceQuery.currency2 = m256i(3, 4, 5, 6); + priceQuery.timestamp = QPI::DateAndTime::now(); + QPI::uint32 interfaceIndex = 0; + QPI::uint16 contractIndex = 1; + QPI::uint32 timeout = 30000; + const QPI::uint32 notificationProcId = 12345; + EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 128, 128, 1 })); + + //------------------------------------------------------------------------- + // start contract query / check message to OM node + QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); + EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, 0)); + checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); + EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); + EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); + + //------------------------------------------------------------------------- + // get query contract data + OI::Price::OracleQuery priceQueryReturned; + EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); + EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); + + //------------------------------------------------------------------------- + // process simulated reply from OM node + struct + { + OracleMachineReply metadata; + OI::Price::OracleReply data; + } priceOracleMachineReply; + + priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; + priceOracleMachineReply.metadata.oracleQueryId = queryId; + priceOracleMachineReply.data.numerator = 1234; + priceOracleMachineReply.data.denominator = 1; + + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + oracleEngine3.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + // duplicate from other node + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + // other value from other node + priceOracleMachineReply.data.numerator = 1233; + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + //------------------------------------------------------------------------- + // create reply commit tx (with computor index 0) + uint8_t txBuffer[MAX_TRANSACTION_SIZE]; + auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, system.tick + 3, 0), UINT32_MAX); + { + EXPECT_EQ((int)replyCommitTx->inputType, (int)OracleReplyCommitTransactionPrefix::transactionType()); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[0]); + EXPECT_TRUE(isZero(replyCommitTx->destinationPublicKey)); + EXPECT_EQ(replyCommitTx->tick, system.tick + 3); + EXPECT_EQ((int)replyCommitTx->inputSize, (int)sizeof(OracleReplyCommitTransactionItem)); + } + + // second call in the same tick: no commits for tx + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + // process commit tx + system.tick += 3; + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + + // no reveal yet + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + // no notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + + //------------------------------------------------------------------------- + // create and process enough reply commit tx to trigger reveal tx + + // create tx of node 3 computers and process in all nodes + for (int i = 600; i < 676; ++i) + { + EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, i, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); + const int txFromNode3 = i - 600; + oracleEngine1.checkPendingState(queryId, txFromNode3 + 1, 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, txFromNode3 + 2, 1, ORACLE_QUERY_STATUS_PENDING); + oracleEngine2.checkPendingState(queryId, txFromNode3 + 1, 0, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, txFromNode3 + 2, 0, ORACLE_QUERY_STATUS_PENDING); + oracleEngine3.checkPendingState(queryId, txFromNode3 + 1, txFromNode3 + 0, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, txFromNode3 + 2, txFromNode3 + 1, ORACLE_QUERY_STATUS_PENDING); + } + + // create tx of node 2 computers and process in all nodes + for (int i = 400; i < 600; ++i) + { + EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, i, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); + const int txFromNode2 = i - 400; + oracleEngine1.checkPendingState(queryId, txFromNode2 + 77, 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, txFromNode2 + 78, 1, ORACLE_QUERY_STATUS_PENDING); + oracleEngine2.checkPendingState(queryId, txFromNode2 + 77, txFromNode2 + 0, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, txFromNode2 + 78, txFromNode2 + 1, ORACLE_QUERY_STATUS_PENDING); + oracleEngine3.checkPendingState(queryId, txFromNode2 + 77, 76, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, txFromNode2 + 78, 76, ORACLE_QUERY_STATUS_PENDING); + } + + // create tx of node 1 computers and process in all nodes + for (int i = 1; i < 400; ++i) + { + bool expectStatusCommitted = (i + 276) >= 451; + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, i, system.tick + 3, 0), ((expectStatusCommitted) ? 0 : UINT32_MAX)); + if (!expectStatusCommitted) + { + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); + const int txFromNode1 = i; + uint8_t newStatus = (txFromNode1 + 276 < 450) ? ORACLE_QUERY_STATUS_PENDING : ORACLE_QUERY_STATUS_COMMITTED; + oracleEngine1.checkPendingState(queryId, txFromNode1 + 276, txFromNode1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, txFromNode1 + 277, txFromNode1 + 1, newStatus); + oracleEngine2.checkPendingState(queryId, txFromNode1 + 276, 200, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, txFromNode1 + 277, 200, newStatus); + oracleEngine3.checkPendingState(queryId, txFromNode1 + 276, 76, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, txFromNode1 + 277, 76, newStatus); + } + else + { + oracleEngine1.checkPendingState(queryId, 451, 175, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine2.checkPendingState(queryId, 451, 200, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine3.checkPendingState(queryId, 451, 76, ORACLE_QUERY_STATUS_COMMITTED); + } + } + + //------------------------------------------------------------------------- + // reply reveal tx + + // success for one tx + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); + + // second call does not provide the same tx again + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + system.tick += 3; + auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; + const unsigned int txIndex = 10; + addOracleTransactionToTickStorage(replyRevealTx, txIndex); + oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndex); + + //------------------------------------------------------------------------- + // notifications + const OracleNotificationData* notification = oracleEngine1.getNotification(); + EXPECT_NE(notification, nullptr); + EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); + EXPECT_EQ(notification->procedureId, notificationProcId); + EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); + const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; + EXPECT_EQ(notificationInput->queryId, replyRevealTx->queryId); + EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_SUCCESS); + EXPECT_EQ(notificationInput->subscriptionId, 0); + EXPECT_EQ(notificationInput->reply.numerator, 1234); + EXPECT_EQ(notificationInput->reply.denominator, 1); + + // no additional notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); + + OI::Price::OracleReply reply; + EXPECT_TRUE(oracleEngine1.getOracleReply(queryId, &reply, sizeof(reply))); + EXPECT_TRUE(compareMem(&reply, ¬ificationInput->reply, sizeof(reply)) == 0); + + // oracleEngine2 did not process reveal -> no success / reply + EXPECT_EQ(oracleEngine2.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_COMMITTED); + EXPECT_FALSE(oracleEngine2.getOracleReply(queryId, &reply, sizeof(reply))); + + //------------------------------------------------------------------------- + // revenue + OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); + OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); + OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + // first 451 commit messages got through and are all correct: + // - all of node 3 (computor 600-675) + // - all of node 2 (computor 400-599) + // - first 451-276 of node 1 (computor 0-174) + bool gotCommit = (i >= 400) || (i <= 174); + EXPECT_EQ(rev1.computorRevPoints[i], (gotCommit) ? 1 : 0); + + // no reveal processed in node 2 and 3 + EXPECT_EQ(rev2.computorRevPoints[i], 0); + EXPECT_EQ(rev3.computorRevPoints[i], 0); + } + + // check that oracle engine is in consistent state + oracleEngine1.checkStateConsistencyWithAssert(); + oracleEngine2.checkStateConsistencyWithAssert(); + oracleEngine3.checkStateConsistencyWithAssert(); +} + +TEST(OracleEngine, ContractQueryUnresolvable) +{ + // 2 nodes send 200 commits each with agreeing digest + // 1 node sends 276 commits with different digest + // -> no quroum can be reached and status changes from pending to unresolvable directly + // -> no reveal / nobody gets revenue + + OracleEngineTest test; + + // simulate three nodes: two with 200 computor IDs each, one with 276 IDs + const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; + OracleEngineWithInitAndDeinit oracleEngine1(allCompPubKeys, 0, 200); + OracleEngineWithInitAndDeinit oracleEngine2(allCompPubKeys, 200, 400); + OracleEngineWithInitAndDeinit oracleEngine3(allCompPubKeys, 400, 676); + + + OI::Price::OracleQuery priceQuery; + priceQuery.oracle = m256i(10, 20, 30, 40); + priceQuery.currency1 = m256i(20, 30, 40, 50); + priceQuery.currency2 = m256i(30, 40, 50, 60); + priceQuery.timestamp = QPI::DateAndTime::now(); + QPI::uint32 interfaceIndex = 0; + QPI::uint16 contractIndex = 2; + QPI::uint32 timeout = 120000; + const QPI::uint32 notificationProcId = 12345; + EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 1024, 128, 1 })); + + //------------------------------------------------------------------------- + // start contract query / check message to OM node + QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); + EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, 0)); + EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); + EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); + checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); + + //------------------------------------------------------------------------- + // get query contract data + OI::Price::OracleQuery priceQueryReturned; + EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); + EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); + + //------------------------------------------------------------------------- + // process simulated reply from OM nodes + struct + { + OracleMachineReply metadata; + OI::Price::OracleReply data; + } priceOracleMachineReply; + + // reply received/committed by node 1 and 2 + priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; + priceOracleMachineReply.metadata.oracleQueryId = queryId; + priceOracleMachineReply.data.numerator = 1234; + priceOracleMachineReply.data.denominator = 1; + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + // reply received/committed by node 3 + priceOracleMachineReply.data.numerator = 1233; + priceOracleMachineReply.data.denominator = 1; + oracleEngine3.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + + //------------------------------------------------------------------------- + // create and process reply commits of node 3 computers and process in all nodes + uint8_t txBuffer[MAX_TRANSACTION_SIZE]; + auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; + for (int ownCompIdx = 0; ownCompIdx < 200; ++ownCompIdx) + { + int allCompIdx = ownCompIdx; + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); + + allCompIdx = ownCompIdx + 200; + EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); + + allCompIdx = ownCompIdx + 400; + EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + } + + // create/process transactions that contradict with majority digest and turn status into unresolvable + for (int allCompIdx = 600; allCompIdx < 676; ++allCompIdx) + { + int ownCompIdx = allCompIdx - 400; + int unknownVotes = 676 - allCompIdx; + bool moreTxExpected = (unknownVotes > 450 - 400); + EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), moreTxExpected ? UINT32_MAX : 0); + if (moreTxExpected) + { + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + } + + if (unknownVotes > 451 - 400) + { + oracleEngine1.checkPendingState(queryId, allCompIdx + 1, 200, ORACLE_QUERY_STATUS_PENDING); + oracleEngine2.checkPendingState(queryId, allCompIdx + 1, 200, ORACLE_QUERY_STATUS_PENDING); + oracleEngine3.checkPendingState(queryId, allCompIdx + 1, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + } + else + { + oracleEngine1.checkStatus(queryId, ORACLE_QUERY_STATUS_UNRESOLVABLE); + oracleEngine2.checkStatus(queryId, ORACLE_QUERY_STATUS_UNRESOLVABLE); + oracleEngine3.checkStatus(queryId, ORACLE_QUERY_STATUS_UNRESOLVABLE); + } + } + + //------------------------------------------------------------------------- + // notifications + const OracleNotificationData* notification = oracleEngine1.getNotification(); + EXPECT_NE(notification, nullptr); + EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); + EXPECT_EQ(notification->procedureId, notificationProcId); + EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); + const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; + EXPECT_EQ(notificationInput->queryId, queryId); + EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_UNRESOLVABLE); + EXPECT_EQ(notificationInput->subscriptionId, 0); + EXPECT_EQ(notificationInput->reply.numerator, 0); + EXPECT_EQ(notificationInput->reply.denominator, 0); + + // no additional notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_UNRESOLVABLE); + + //------------------------------------------------------------------------- + // revenue + OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); + OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); + OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + // no reveal + EXPECT_EQ(rev1.computorRevPoints[i], 0); + EXPECT_EQ(rev2.computorRevPoints[i], 0); + EXPECT_EQ(rev3.computorRevPoints[i], 0); + } + + // check that oracle engine is in consistent state + oracleEngine1.checkStateConsistencyWithAssert(); + oracleEngine2.checkStateConsistencyWithAssert(); + oracleEngine3.checkStateConsistencyWithAssert(); +} + +TEST(OracleEngine, ContractQueryWrongKnowledgeProof) +{ + // 3 nodes send 451 commits with agreeing digest, but 150 of them have + // a wrong knowledge proof -> status gets unresolvable, but the computors + // with correct knowledge proof get a revenue point + + OracleEngineTest test; + + // simulate three nodes: two with 200 computor IDs each, one with 276 IDs + const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; + OracleEngineWithInitAndDeinit oracleEngine1(allCompPubKeys, 0, 200); + OracleEngineWithInitAndDeinit oracleEngine2(allCompPubKeys, 200, 400); + OracleEngineWithInitAndDeinit oracleEngine3(allCompPubKeys, 400, 676); + + + OI::Price::OracleQuery priceQuery; + priceQuery.oracle = m256i(10, 20, 30, 40); + priceQuery.currency1 = m256i(20, 30, 40, 50); + priceQuery.currency2 = m256i(30, 40, 50, 60); + priceQuery.timestamp = QPI::DateAndTime::now(); + QPI::uint32 interfaceIndex = 0; + QPI::uint16 contractIndex = 2; + QPI::uint32 timeout = 120000; + const QPI::uint32 notificationProcId = 12345; + EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 1024, 128, 1 })); + + //------------------------------------------------------------------------- + // start contract query / check message to OM node + QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); + EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, 0)); + EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); + EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId)); + checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); + + //------------------------------------------------------------------------- + // get query contract data + OI::Price::OracleQuery priceQueryReturned; + EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); + EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); + + //------------------------------------------------------------------------- + // process simulated reply from OM nodes + struct + { + OracleMachineReply metadata; + OI::Price::OracleReply data; + } priceOracleMachineReply; + + // reply received/committed + priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; + priceOracleMachineReply.metadata.oracleQueryId = queryId; + priceOracleMachineReply.data.numerator = 1234; + priceOracleMachineReply.data.denominator = 1; + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + oracleEngine3.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + //------------------------------------------------------------------------- + // create and process reply commits of node 3 computers and process in all nodes + uint8_t txBuffer[MAX_TRANSACTION_SIZE]; + auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; + for (int ownCompIdx = 0; ownCompIdx < 200; ++ownCompIdx) + { + int allCompIdx = ownCompIdx; + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + uint8_t expectedStatus = (3 * ownCompIdx + 1 < 451) ? ORACLE_QUERY_STATUS_PENDING : ORACLE_QUERY_STATUS_COMMITTED; + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx + 1, expectedStatus); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, expectedStatus); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 1, ownCompIdx, expectedStatus); + + if (3 * ownCompIdx + 1 == 451) + { + // After status switched to committed, getReplyCommitTransaction() won't return more tx because these + // would be to late to get revenue anyway. + allCompIdx = ownCompIdx + 200; + EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), 0); + allCompIdx = ownCompIdx + 400; + EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), 0); + break; + } + + allCompIdx = ownCompIdx + 200; + EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 2, ownCompIdx, ORACLE_QUERY_STATUS_PENDING); + + allCompIdx = ownCompIdx + 400; + EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, allCompIdx, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[allCompIdx]); + + // manipulate knowledge proof of computor 400-600 to simulate that computors just echo the commit of + // other computors without actually having the oracle reply + auto* commit = reinterpret_cast(replyCommitTx->inputPtr()); + commit->replyKnowledgeProof.u64._3 += 2; + + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, 3 * ownCompIdx + 3, ownCompIdx + 1, ORACLE_QUERY_STATUS_PENDING); + } + + //------------------------------------------------------------------------- + // reply reveal tx + + // success for one tx + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); + + // second call does not provide the same tx again + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_COMMITTED); + + system.tick += 3; + auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; + const unsigned int txIndex = 10; + addOracleTransactionToTickStorage(replyRevealTx, txIndex); + oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndex); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_UNRESOLVABLE); + + //------------------------------------------------------------------------- + // notifications + const OracleNotificationData* notification = oracleEngine1.getNotification(); + EXPECT_NE(notification, nullptr); + EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); + EXPECT_EQ(notification->procedureId, notificationProcId); + EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); + const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; + EXPECT_EQ(notificationInput->queryId, queryId); + EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_UNRESOLVABLE); + EXPECT_EQ(notificationInput->subscriptionId, 0); + EXPECT_EQ(notificationInput->reply.numerator, 0); + EXPECT_EQ(notificationInput->reply.denominator, 0); + + // no additional notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_UNRESOLVABLE); + + //------------------------------------------------------------------------- + // revenue + OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); + OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); + OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + // In reveal, computors with correct knowledge proof get a point and those + // with wrong knowledge proof don't get one: + // - first 151 of node 1 got through and get rev (0-150) + // - first 150 of node 2 got through and get rev (200-349) + // - first 150 of node 3 get no rev due to wrong knowledge proof + // - other don't get rev, because no commit got through + bool node1rev = i <= 150; + bool node2rev = i >= 200 && i <= 349; + uint64_t expectedRevPoints = (node1rev || node2rev) ? 1 : 0; + EXPECT_EQ(rev1.computorRevPoints[i], expectedRevPoints); + + // no reveal + EXPECT_EQ(rev2.computorRevPoints[i], 0); + EXPECT_EQ(rev3.computorRevPoints[i], 0); + } + + // check that oracle engine is in consistent state + oracleEngine1.checkStateConsistencyWithAssert(); + oracleEngine2.checkStateConsistencyWithAssert(); + oracleEngine3.checkStateConsistencyWithAssert(); +} + +TEST(OracleEngine, ContractQueryTimeout) +{ + // no response from OM node + // -> pending to timeout directly + // -> no reveal / nobody gets revenue + + OracleEngineTest test; + + // simulate one node + const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; + OracleEngineWithInitAndDeinit oracleEngine1(allCompPubKeys, 0, 676); + + OI::Price::OracleQuery priceQuery; + priceQuery.oracle = m256i(10, 20, 30, 40); + priceQuery.currency1 = m256i(20, 30, 40, 50); + priceQuery.currency2 = m256i(30, 40, 50, 60); + priceQuery.timestamp = QPI::DateAndTime::now(); + QPI::uint32 interfaceIndex = 0; + QPI::uint16 contractIndex = 2; + QPI::uint32 timeout = 10000; + const QPI::uint32 notificationProcId = 12345; + EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 1024, 128, 1 })); + + //------------------------------------------------------------------------- + // start contract query / check message to OM node + QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &priceQuery, sizeof(priceQuery), timeout, notificationProcId); + checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); + + //------------------------------------------------------------------------- + // get query contract data + OI::Price::OracleQuery priceQueryReturned; + EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); + EXPECT_EQ(memcmp(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); + + //------------------------------------------------------------------------- + // timeout: no response from OM node + ++system.tick; + ++etalonTick.hour; + oracleEngine1.processTimeouts(); + + //------------------------------------------------------------------------- + // notifications + const OracleNotificationData* notification = oracleEngine1.getNotification(); + EXPECT_NE(notification, nullptr); + EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); + EXPECT_EQ(notification->procedureId, notificationProcId); + EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); + const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; + EXPECT_EQ(notificationInput->queryId, queryId); + EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_TIMEOUT); + EXPECT_EQ(notificationInput->subscriptionId, 0); + EXPECT_EQ(notificationInput->reply.numerator, 0); + EXPECT_EQ(notificationInput->reply.denominator, 0); + + // no additional notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_TIMEOUT); + + //------------------------------------------------------------------------- + // revenue + OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + // no reveal + EXPECT_EQ(rev1.computorRevPoints[i], 0); + } + + // check that oracle engine is in consistent state + oracleEngine1.checkStateConsistencyWithAssert(); +} + +template +static void checkReplyCommitTransactions( + OracleEngine& oracleEngine, int globalCompIdxBegin, int globalCompIdxEnd, + const std::vector& queryIds, QPI::sint64 queryIdWithoutReply) +{ + uint8_t txBuffer[MAX_TRANSACTION_SIZE]; + auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; + + // create tx of node 3 computers and process in all nodes + for (int globalCompIdx = globalCompIdxBegin; globalCompIdx < globalCompIdxEnd; ++globalCompIdx) + { + std::set pendingCommitQueryIds; + pendingCommitQueryIds.insert(queryIds.begin(), queryIds.end()); + + unsigned int retCode = 0; + do + { + retCode = oracleEngine.getReplyCommitTransaction(txBuffer, globalCompIdx, system.tick + 3, retCode); + if (!retCode) + break; + unsigned short commitCount = replyCommitTx->inputSize / sizeof(OracleReplyCommitTransactionItem); + auto* commits = reinterpret_cast(replyCommitTx->inputPtr()); + for (unsigned short i = 0; i < commitCount; ++i) + { + pendingCommitQueryIds.erase(commits[i].queryId); + } + } while (retCode != UINT32_MAX); + + // only the query without OM reply is not returned + EXPECT_EQ(pendingCommitQueryIds.size(), 1); + EXPECT_TRUE(pendingCommitQueryIds.contains(queryIdWithoutReply)); + } +} + +TEST(OracleEngine, MultiContractQuerySuccess) +{ + OracleEngineTest test; + + // simulate three nodes: one with 350 computor IDs, one with 250, and one with 76 + const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; + OracleEngineWithInitAndDeinit oracleEngine1(allCompPubKeys, 0, 350); + OracleEngineWithInitAndDeinit oracleEngine2(allCompPubKeys, 350, 600); + OracleEngineWithInitAndDeinit oracleEngine3(allCompPubKeys, 600, 676); + + QPI::uint16 contractIndex = 4; + QPI::uint32 timeout = 50000; + const QPI::uint32 notificationProcId = 12345; + EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 128, 128, 1 })); + + constexpr int queryCount = 25; + QPI::uint16 interfaceIndex = OI::Mock::oracleInterfaceIndex; + OI::Mock::OracleQuery mockQuery; + + //------------------------------------------------------------------------- + // start contract query / check message to OM node / check query + std::vector queryIds; + for (int i = 0; i < queryCount; ++i) + { + mockQuery.value = i + 1000; + QPI::sint64 queryId = oracleEngine1.startContractQuery(contractIndex, interfaceIndex, &mockQuery, sizeof(mockQuery), timeout, notificationProcId); + EXPECT_EQ(queryId, getContractOracleQueryId(system.tick, i)); + EXPECT_EQ(queryId, oracleEngine2.startContractQuery(contractIndex, interfaceIndex, &mockQuery, sizeof(mockQuery), timeout, notificationProcId)); + EXPECT_EQ(queryId, oracleEngine3.startContractQuery(contractIndex, interfaceIndex, &mockQuery, sizeof(mockQuery), timeout, notificationProcId)); + + checkNetworkMessageOracleMachineQuery(queryId, timeout, mockQuery); + + OI::Mock::OracleQuery mockQueryReturned; + EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &mockQueryReturned, sizeof(mockQueryReturned))); + EXPECT_EQ(memcmp(&mockQueryReturned, &mockQuery, sizeof(mockQuery)), 0); + + queryIds.push_back(queryId); + } + + //------------------------------------------------------------------------- + // process simulated reply from OM node + struct + { + OracleMachineReply metadata; + OI::Mock::OracleReply data; + } oracleMachineReply; + + for (int i = 0; i < queryCount; ++i) + { + oracleMachineReply.metadata.oracleMachineErrorFlags = 0; + oracleMachineReply.metadata.oracleQueryId = queryIds[i]; + oracleMachineReply.data.echoedValue = 1000 + i; + oracleMachineReply.data.doubledValue = (1000 + i) * 2; + + // the first 3 queries don't get OM reply all oracleEngines + if (i != 0) + oracleEngine1.processOracleMachineReply(&oracleMachineReply.metadata, sizeof(oracleMachineReply)); + if (i != 1) + oracleEngine2.processOracleMachineReply(&oracleMachineReply.metadata, sizeof(oracleMachineReply)); + if (i != 2) + oracleEngine3.processOracleMachineReply(&oracleMachineReply.metadata, sizeof(oracleMachineReply)); + } + + //------------------------------------------------------------------------- + // create and process reply commit transactions + + // get all commit tx for checking that correct set of commits is returned + + checkReplyCommitTransactions(oracleEngine1, 0, 350, queryIds, queryIds[0]); + checkReplyCommitTransactions(oracleEngine2, 350, 600, queryIds, queryIds[1]); + checkReplyCommitTransactions(oracleEngine3, 600, 676, queryIds, queryIds[2]); + + // advance few ticks in order to get commit tx returned again for processing the commits + system.tick += 5; + + uint8_t txBuffer[MAX_TRANSACTION_SIZE]; + auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; + + unsigned int txIndexInTickData = 0; + + int globalCompIdxBeginEnd[] = { 0, 350, 600, 676 }; + for (int oracleEngineIdx = 0; oracleEngineIdx < 3; ++oracleEngineIdx) + { + for (int globalCompIdx = globalCompIdxBeginEnd[oracleEngineIdx]; + globalCompIdx < globalCompIdxBeginEnd[oracleEngineIdx + 1]; + ++globalCompIdx) + { + unsigned int retCode = 0; + do + { + if (oracleEngineIdx == 0) + retCode = oracleEngine1.getReplyCommitTransaction( + txBuffer, globalCompIdx, + system.tick + 3, retCode); + else if (oracleEngineIdx == 1) + retCode = oracleEngine2.getReplyCommitTransaction( + txBuffer, globalCompIdx, + system.tick + 3, retCode); + else + retCode = oracleEngine3.getReplyCommitTransaction( + txBuffer, globalCompIdx, + system.tick + 3, retCode); + if (!retCode) + break; + + // store commit tx in tick storage for processing tx later + addOracleTransactionToTickStorage(replyCommitTx, txIndexInTickData); + txIndexInTickData++; + } while (retCode != UINT32_MAX); + } + + // each engine send its txs in a different tick here + ++system.tick; + txIndexInTickData = 0; + } + + // process commit txs + ts.checkStateConsistencyWithAssert(); + for (int tick = 0; tick < 3; ++tick) + { + const unsigned long long* tsTickTransactionOffsets = ts.tickTransactionOffsets.getByTickInCurrentEpoch(system.tick); + const unsigned long long* tsTickTransactionOffsets2 = ts.tickTransactionOffsets.getByTickInCurrentEpoch(system.tick + 1); + const unsigned long long* tsTickTransactionOffsets3 = ts.tickTransactionOffsets.getByTickInCurrentEpoch(system.tick + 2); + for (txIndexInTickData = 0; txIndexInTickData < NUMBER_OF_TRANSACTIONS_PER_TICK; ++txIndexInTickData) + { + const unsigned long long offset = tsTickTransactionOffsets[txIndexInTickData]; + if (offset) + { + const auto* commitTx = (OracleReplyCommitTransactionPrefix*)ts.tickTransactions.ptr(offset); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(commitTx)); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(commitTx)); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(commitTx)); + } + } + ++system.tick; + } + + // check status + oracleEngine1.checkPendingState(queryIds[0], 326, 0, ORACLE_QUERY_STATUS_PENDING); + oracleEngine2.checkPendingState(queryIds[0], 326, 250, ORACLE_QUERY_STATUS_PENDING); + oracleEngine3.checkPendingState(queryIds[0], 326, 76, ORACLE_QUERY_STATUS_PENDING); + + oracleEngine1.checkPendingState(queryIds[1], 426, 350, ORACLE_QUERY_STATUS_PENDING); + oracleEngine2.checkPendingState(queryIds[1], 426, 0, ORACLE_QUERY_STATUS_PENDING); + oracleEngine3.checkPendingState(queryIds[1], 426, 76, ORACLE_QUERY_STATUS_PENDING); + + oracleEngine1.checkPendingState(queryIds[2], 600, 350, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine2.checkPendingState(queryIds[2], 600, 250, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine3.checkPendingState(queryIds[2], 600, 0, ORACLE_QUERY_STATUS_COMMITTED); + + for (int i = 3; i < queryCount; ++i) + { + oracleEngine1.checkPendingState(queryIds[i], 676, 350, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine2.checkPendingState(queryIds[i], 676, 250, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine3.checkPendingState(queryIds[i], 676, 76, ORACLE_QUERY_STATUS_COMMITTED); + } + + //------------------------------------------------------------------------- + // reply reveal tx + + std::set pendingRevealQueryIds; + pendingRevealQueryIds.insert(queryIds.begin() + 2, queryIds.end()); + + // generate all reveal of oracleEngine3 + auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; + unsigned int retCode = 0; + std::vector revealTxs; + txIndexInTickData = 0; + while ((retCode = oracleEngine3.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, retCode)) != 0) + { + oracleEngine1.announceExpectedRevealTransaction(replyRevealTx); + oracleEngine2.announceExpectedRevealTransaction(replyRevealTx); + oracleEngine3.announceExpectedRevealTransaction(replyRevealTx); + + // save pointer to reveal tx in tick storage for processing tx later + revealTxs.push_back((OracleReplyRevealTransactionPrefix*)addOracleTransactionToTickStorage(replyRevealTx, txIndexInTickData)); + txIndexInTickData++; + } + + // process reveal tx + system.tick += 3; + for (txIndexInTickData = 0; txIndexInTickData < revealTxs.size(); ++txIndexInTickData) + { + const auto* revealTx = revealTxs[txIndexInTickData]; + pendingRevealQueryIds.erase(revealTx->queryId); + + oracleEngine1.processOracleReplyRevealTransaction(revealTx, txIndexInTickData); + oracleEngine2.processOracleReplyRevealTransaction(revealTx, txIndexInTickData); + oracleEngine3.processOracleReplyRevealTransaction(revealTx, txIndexInTickData); + } + + // no reveal possible for oracleEngine3 in query 2, because it didn't get OM reply + EXPECT_EQ(pendingRevealQueryIds.size(), 1); + EXPECT_TRUE(pendingRevealQueryIds.contains(queryIds[2])); + + // reveal query 2 using oracleEngine2 + EXPECT_EQ(oracleEngine2.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); + EXPECT_EQ(replyRevealTx->queryId, queryIds[2]); + EXPECT_EQ(oracleEngine2.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); + addOracleTransactionToTickStorage(replyRevealTx, txIndexInTickData); + system.tick += 3; + oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndexInTickData); + oracleEngine2.processOracleReplyRevealTransaction(replyRevealTx, txIndexInTickData); + oracleEngine3.processOracleReplyRevealTransaction(replyRevealTx, txIndexInTickData); + + // nothing to reveal for oracleEngine1 + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + //------------------------------------------------------------------------- + // notifications + + std::set pendingNotificationQueryIds; + pendingNotificationQueryIds.insert(queryIds.begin() + 2, queryIds.end()); + + for (int i = 2; i < queryCount; ++i) + { + const OracleNotificationData* notification = oracleEngine1.getNotification(); + EXPECT_NE(notification, nullptr); + EXPECT_EQ((int)notification->contractIndex, (int)contractIndex); + EXPECT_EQ(notification->procedureId, notificationProcId); + EXPECT_EQ((int)notification->inputSize, sizeof(OracleNotificationInput)); + const auto* notificationInput = (const OracleNotificationInput*) & notification->inputBuffer; + EXPECT_NE(notificationInput->queryId, 0); + EXPECT_EQ(notificationInput->status, ORACLE_QUERY_STATUS_SUCCESS); + EXPECT_EQ(notificationInput->subscriptionId, 0); + + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(notificationInput->queryId), ORACLE_QUERY_STATUS_SUCCESS); + OI::Mock::OracleQuery query; + EXPECT_TRUE(oracleEngine1.getOracleQuery(notificationInput->queryId, &query, sizeof(query))); + + EXPECT_EQ(notificationInput->reply.echoedValue, query.value); + EXPECT_EQ(notificationInput->reply.doubledValue, query.value * 2); + + OI::Mock::OracleReply reply; + EXPECT_TRUE(oracleEngine1.getOracleReply(notificationInput->queryId, &reply, sizeof(reply))); + EXPECT_TRUE(compareMem(&reply, ¬ificationInput->reply, sizeof(reply)) == 0); + + pendingNotificationQueryIds.erase(notificationInput->queryId); + } + + // no additional notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + EXPECT_EQ(pendingNotificationQueryIds.size(), 0); + + //------------------------------------------------------------------------- + // revenue + OracleRevenuePoints rev1; oracleEngine1.getRevenuePoints(rev1); + OracleRevenuePoints rev2; oracleEngine2.getRevenuePoints(rev2); + OracleRevenuePoints rev3; oracleEngine3.getRevenuePoints(rev3); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + bool hadFastestCommits = (i < 600); + uint64_t expectedRevPoints = (hadFastestCommits) ? queryCount - 2 : 0; + EXPECT_EQ(rev1.computorRevPoints[i], expectedRevPoints); + EXPECT_EQ(rev2.computorRevPoints[i], expectedRevPoints); + EXPECT_EQ(rev3.computorRevPoints[i], expectedRevPoints); + } + + // check that oracle engine is in consistent state + oracleEngine1.checkStateConsistencyWithAssert(); + oracleEngine2.checkStateConsistencyWithAssert(); + oracleEngine3.checkStateConsistencyWithAssert(); +} + +/* +Tests: +- error conditions +*/ + +struct PriceQueryTransaction : public OracleUserQueryTransactionPrefix +{ + OI::Price::OracleQuery query; + uint8_t signature[SIGNATURE_SIZE]; +}; + +static PriceQueryTransaction getPriceQueryTransaction(const OI::Price::OracleQuery& query, const m256i& sourcePublicKey, int64_t fee, uint32_t timeout) +{ + PriceQueryTransaction tx; + tx.amount = fee; + tx.destinationPublicKey = m256i::zero(); + tx.inputSize = OracleUserQueryTransactionPrefix::minInputSize() + sizeof(query); + tx.inputType = OracleUserQueryTransactionPrefix::transactionType(); + tx.oracleInterfaceIndex = OI::Price::oracleInterfaceIndex; + tx.sourcePublicKey = sourcePublicKey; + tx.tick = system.tick; + tx.timeoutMilliseconds = timeout; + tx.query = query; + setMem(tx.signature, SIGNATURE_SIZE, 0); // keep signature uninitialized + return tx; +} + +TEST(OracleEngine, UserQuerySuccess) +{ + OracleEngineTest test; + + const id USER1(123, 456, 789, 876); + increaseEnergy(USER1, 10000000000); + + // simulate three nodes: one with 400 computor IDs, one with 200, and one with 76 + const m256i* allCompPubKeys = broadcastedComputors.computors.publicKeys; + OracleEngineWithInitAndDeinit oracleEngine1(allCompPubKeys, 0, 400); + OracleEngineWithInitAndDeinit oracleEngine2(allCompPubKeys, 400, 600); + OracleEngineWithInitAndDeinit oracleEngine3(allCompPubKeys, 600, 676); + + OI::Price::OracleQuery priceQuery; + priceQuery.oracle = m256i(42, 13, 100, 1000); + priceQuery.currency1 = m256i(20, 30, 40, 50); + priceQuery.currency2 = m256i(300, 400, 500, 600); + priceQuery.timestamp = QPI::DateAndTime::now(); + QPI::uint32 interfaceIndex = 0; + QPI::uint32 timeout = 40000; + QPI::sint64 fee = OI::Price::getQueryFee(priceQuery); + PriceQueryTransaction priceQueryTx = getPriceQueryTransaction(priceQuery, USER1, fee, timeout); + unsigned int priceQueryTxIndex = 15; + addOracleTransactionToTickStorage(&priceQueryTx, priceQueryTxIndex); + + const QPI::uint32 notificationProcId = 12345; + EXPECT_TRUE(userProcedureRegistry->add(notificationProcId, { dummyNotificationProc, 1, 128, 128, 1 })); + + //------------------------------------------------------------------------- + // start user query / check message to OM node + QPI::sint64 queryId = oracleEngine1.startUserQuery(&priceQueryTx, priceQueryTxIndex); + EXPECT_EQ(queryId, getUserOracleQueryId(system.tick, priceQueryTxIndex)); + checkNetworkMessageOracleMachineQuery(queryId, timeout, priceQuery); + EXPECT_EQ(queryId, oracleEngine2.startUserQuery(&priceQueryTx, priceQueryTxIndex)); + EXPECT_EQ(queryId, oracleEngine3.startUserQuery(&priceQueryTx, priceQueryTxIndex)); + + //------------------------------------------------------------------------- + // get query contract data + OI::Price::OracleQuery priceQueryReturned; + EXPECT_TRUE(oracleEngine1.getOracleQuery(queryId, &priceQueryReturned, sizeof(priceQueryReturned))); + EXPECT_EQ(compareMem(&priceQueryReturned, &priceQuery, sizeof(priceQuery)), 0); + + //------------------------------------------------------------------------- + // process simulated reply from OM node + struct + { + OracleMachineReply metadata; + OI::Price::OracleReply data; + } priceOracleMachineReply; + + priceOracleMachineReply.metadata.oracleMachineErrorFlags = 0; + priceOracleMachineReply.metadata.oracleQueryId = queryId; + priceOracleMachineReply.data.numerator = 1234; + priceOracleMachineReply.data.denominator = 1; + + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + oracleEngine2.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + // test: no reply to oracle engine 3! + + // duplicate from other node + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + + // other value from other node + priceOracleMachineReply.data.numerator = 1233; + oracleEngine1.processOracleMachineReply(&priceOracleMachineReply.metadata, sizeof(priceOracleMachineReply)); + priceOracleMachineReply.data.numerator = 1234; + + //------------------------------------------------------------------------- + // create reply commit tx (with computor index 0) + uint8_t txBuffer[MAX_TRANSACTION_SIZE]; + auto* replyCommitTx = (OracleReplyCommitTransactionPrefix*)txBuffer; + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, system.tick + 3, 0), UINT32_MAX); + { + EXPECT_EQ((int)replyCommitTx->inputType, (int)OracleReplyCommitTransactionPrefix::transactionType()); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[0]); + EXPECT_TRUE(isZero(replyCommitTx->destinationPublicKey)); + EXPECT_EQ(replyCommitTx->tick, system.tick + 3); + EXPECT_EQ((int)replyCommitTx->inputSize, (int)sizeof(OracleReplyCommitTransactionItem)); + } + + // second call in the same tick: no commits for tx + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + // process commit tx + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + + // no reveal yet + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + // no notifications + EXPECT_EQ(oracleEngine1.getNotification(), nullptr); + + //------------------------------------------------------------------------- + // create and process enough reply commit tx to trigger reveal tx + + // create tx of node 3 computers? -> no commit tx because reply data is not available in node 3 (no OM reply) + for (int i = 600; i < 676; ++i) + { + EXPECT_EQ(oracleEngine3.getReplyCommitTransaction(txBuffer, i, system.tick + 3, 0), 0); + } + + // create tx of node 2 computers and process in all nodes + for (int i = 400; i < 600; ++i) + { + EXPECT_EQ(oracleEngine2.getReplyCommitTransaction(txBuffer, i, system.tick + 3, 0), UINT32_MAX); + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); + const int txFromNode2 = i - 400; + oracleEngine1.checkPendingState(queryId, txFromNode2 + 1, 1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, txFromNode2 + 2, 1, ORACLE_QUERY_STATUS_PENDING); + oracleEngine2.checkPendingState(queryId, txFromNode2 + 1, txFromNode2 + 0, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, txFromNode2 + 2, txFromNode2 + 1, ORACLE_QUERY_STATUS_PENDING); + oracleEngine3.checkPendingState(queryId, txFromNode2 + 1, 0, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, txFromNode2 + 2, 0, ORACLE_QUERY_STATUS_PENDING); + } + + // create tx of node 1 computers and process in all nodes + for (int i = 1; i < 400; ++i) + { + bool expectStatusCommitted = (i + 200) >= 451; + EXPECT_EQ(oracleEngine1.getReplyCommitTransaction(txBuffer, i, system.tick + 3, 0), ((expectStatusCommitted) ? 0 : UINT32_MAX)); + if (!expectStatusCommitted) + { + EXPECT_EQ(replyCommitTx->sourcePublicKey, allCompPubKeys[i]); + const int txFromNode1 = i; + uint8_t newStatus = (txFromNode1 + 200 < 450) ? ORACLE_QUERY_STATUS_PENDING : ORACLE_QUERY_STATUS_COMMITTED; + oracleEngine1.checkPendingState(queryId, txFromNode1 + 200, txFromNode1, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine1.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine1.checkPendingState(queryId, txFromNode1 + 201, txFromNode1 + 1, newStatus); + oracleEngine2.checkPendingState(queryId, txFromNode1 + 200, 200, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine2.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine2.checkPendingState(queryId, txFromNode1 + 201, 200, newStatus); + oracleEngine3.checkPendingState(queryId, txFromNode1 + 200, 0, ORACLE_QUERY_STATUS_PENDING); + EXPECT_TRUE(oracleEngine3.processOracleReplyCommitTransaction(replyCommitTx)); + oracleEngine3.checkPendingState(queryId, txFromNode1 + 201, 0, newStatus); + } + else + { + oracleEngine1.checkPendingState(queryId, 451, 251, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine2.checkPendingState(queryId, 451, 200, ORACLE_QUERY_STATUS_COMMITTED); + oracleEngine3.checkPendingState(queryId, 451, 0, ORACLE_QUERY_STATUS_COMMITTED); + } + } + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_COMMITTED); + + //------------------------------------------------------------------------- + // reply reveal tx + + // success for one tx + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 1); + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 1), 0); + + // second call does not provide the same tx again + EXPECT_EQ(oracleEngine1.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + // node 3 is in committed state but cannot generate reveal tx, because it did not receive OM reply + EXPECT_EQ(oracleEngine3.getReplyRevealTransaction(txBuffer, 0, system.tick + 3, 0), 0); + + system.tick += 3; + auto* replyRevealTx = (OracleReplyRevealTransactionPrefix*)txBuffer; + const unsigned int txIndex = 10; + addOracleTransactionToTickStorage(replyRevealTx, txIndex); + oracleEngine1.processOracleReplyRevealTransaction(replyRevealTx, txIndex); + oracleEngine2.processOracleReplyRevealTransaction(replyRevealTx, txIndex); + oracleEngine3.processOracleReplyRevealTransaction(replyRevealTx, txIndex); + + //------------------------------------------------------------------------- + // status + EXPECT_EQ(oracleEngine1.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); + EXPECT_EQ(oracleEngine2.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); + EXPECT_EQ(oracleEngine3.getOracleQueryStatus(queryId), ORACLE_QUERY_STATUS_SUCCESS); + + OI::Price::OracleReply reply; + EXPECT_TRUE(oracleEngine1.getOracleReply(queryId, &reply, sizeof(reply))); + EXPECT_TRUE(compareMem(&reply, &priceOracleMachineReply.data, sizeof(reply)) == 0); + EXPECT_TRUE(oracleEngine2.getOracleReply(queryId, &reply, sizeof(reply))); + EXPECT_TRUE(compareMem(&reply, &priceOracleMachineReply.data, sizeof(reply)) == 0); + EXPECT_TRUE(oracleEngine3.getOracleReply(queryId, &reply, sizeof(reply))); + EXPECT_TRUE(compareMem(&reply, &priceOracleMachineReply.data, sizeof(reply)) == 0); + + // check that oracle engine is in consistent state + oracleEngine1.checkStateConsistencyWithAssert(); + oracleEngine2.checkStateConsistencyWithAssert(); + oracleEngine3.checkStateConsistencyWithAssert(); +} + +TEST(OracleEngine, FindFirstQueryIndexOfTick) +{ + OracleEngineTest test; + OracleEngineWithInitAndDeinit oracleEngine(broadcastedComputors.computors.publicKeys, 0, 676); + oracleEngine.testFindFirstQueryIndexOfTick({ 1 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 3 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 2 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 3 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 2 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 3 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 3, 3 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 1 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 2 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 3 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 1, 4 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 1, 2, 4 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 3, 4 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 2, 4, 8 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 1, 8, 8, 8 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 108, 109 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 100, 100, 105, 108, 109 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 105, 108, 109 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 108, 108, 109 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 100, 105, 108, 109, 109 }); + oracleEngine.testFindFirstQueryIndexOfTick({ 100, 100, 100, 100, 100, 105, 105, 105, 108, 109, 109 }); + + // random test + std::vector ticks; + uint32_t ticksWithQueries = random(100) + 1; + uint32_t prevTick = 100; + for (uint32_t i = 0; i < ticksWithQueries; ++i) + { + const uint32_t tick = prevTick + random(10); + const uint32_t queriesInTick = random(100) + 1; + for (uint32_t j = 0; j < queriesInTick; ++j) + { + ticks.push_back(tick); + } + prevTick = tick; + } + oracleEngine.testFindFirstQueryIndexOfTick(ticks); +} diff --git a/test/oracle_testing.h b/test/oracle_testing.h new file mode 100644 index 000000000..fd278abe1 --- /dev/null +++ b/test/oracle_testing.h @@ -0,0 +1,84 @@ +#pragma once + +// Include this first, to ensure "logging/logging.h" isn't included before the custom LOG_BUFFER_SIZE has been defined +#include "logging_test.h" + +#undef MAX_NUMBER_OF_TICKS_PER_EPOCH +#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 +#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH +#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 5 +#include "ticking/tick_storage.h" + +#include "gtest/gtest.h" + +#include "oracle_core/oracle_engine.h" +#include "contract_core/qpi_ticking_impl.h" +#include "contract_core/qpi_spectrum_impl.h" + + +union EnqueuedNetworkMessage +{ + RequestResponseHeader header; + + struct + { + RequestResponseHeader header; + OracleMachineQuery queryMetadata; + unsigned char queryData[MAX_ORACLE_QUERY_SIZE]; + } omQuery; +}; + +GLOBAL_VAR_DECL EnqueuedNetworkMessage enqueuedNetworkMessage; + +template +static void checkNetworkMessageOracleMachineQuery(QPI::uint64 expectedOracleQueryId, QPI::uint32 expectedTimeout, const typename OracleInterface::OracleQuery& expectedQuery) +{ + EXPECT_EQ(enqueuedNetworkMessage.header.type(), OracleMachineQuery::type()); + EXPECT_GT(enqueuedNetworkMessage.header.size(), sizeof(RequestResponseHeader) + sizeof(OracleMachineQuery)); + QPI::uint32 queryDataSize = enqueuedNetworkMessage.header.size() - sizeof(RequestResponseHeader) - sizeof(OracleMachineQuery); + EXPECT_LE(queryDataSize, (QPI::uint32)MAX_ORACLE_QUERY_SIZE); + EXPECT_EQ(queryDataSize, sizeof(typename OracleInterface::OracleQuery)); + EXPECT_EQ(enqueuedNetworkMessage.omQuery.queryMetadata.oracleInterfaceIndex, OracleInterface::oracleInterfaceIndex); + EXPECT_EQ(enqueuedNetworkMessage.omQuery.queryMetadata.oracleQueryId, expectedOracleQueryId); + EXPECT_EQ(enqueuedNetworkMessage.omQuery.queryMetadata.timeoutInMilliseconds, expectedTimeout); + const auto* q = (const OracleInterface::OracleQuery*)enqueuedNetworkMessage.omQuery.queryData; + EXPECT_TRUE(compareMem(q, &expectedQuery, sizeof(OracleInterface::OracleQuery)) == 0); +} + +static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data) +{ + EXPECT_EQ(peer, (Peer*)0x1); + EXPECT_LE(dataSize, sizeof(OracleMachineQuery) + MAX_ORACLE_QUERY_SIZE); + EXPECT_TRUE(enqueuedNetworkMessage.header.checkAndSetSize(sizeof(RequestResponseHeader) + dataSize)); + enqueuedNetworkMessage.header.setType(type); + enqueuedNetworkMessage.header.setDejavu(dejavu); + copyMem(&enqueuedNetworkMessage.omQuery.queryMetadata, data, dataSize); +} + +static inline QPI::uint64 getContractOracleQueryId(QPI::uint32 tick, QPI::uint32 indexInTick) +{ + return ((QPI::uint64)tick << 31) | (indexInTick + NUMBER_OF_TRANSACTIONS_PER_TICK); +} + +static inline QPI::uint64 getUserOracleQueryId(QPI::uint32 tick, QPI::uint32 indexInTick) +{ + ASSERT(indexInTick < NUMBER_OF_TRANSACTIONS_PER_TICK); + return ((QPI::uint64)tick << 31) | indexInTick; +} + +static const Transaction* addOracleTransactionToTickStorage(const Transaction* tx, unsigned int txIndex) +{ + ASSERT(txIndex < NUMBER_OF_TRANSACTIONS_PER_TICK); + Transaction* tsTx = nullptr; + const unsigned int txSize = tx->totalSize(); + auto* offsets = ts.tickTransactionOffsets.getByTickInCurrentEpoch(tx->tick); + if (ts.nextTickTransactionOffset + txSize <= ts.tickTransactions.storageSpaceCurrentEpoch) + { + EXPECT_EQ(offsets[txIndex], 0); + offsets[txIndex] = ts.nextTickTransactionOffset; + tsTx = ts.tickTransactions(ts.nextTickTransactionOffset); + copyMem(tsTx, tx, txSize); + ts.nextTickTransactionOffset += txSize; + } + return tsTx; +} diff --git a/test/packages.config b/test/packages.config new file mode 100644 index 000000000..a450605d2 --- /dev/null +++ b/test/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/test/pending_txs_pool.cpp b/test/pending_txs_pool.cpp new file mode 100644 index 000000000..b191a5994 --- /dev/null +++ b/test/pending_txs_pool.cpp @@ -0,0 +1,544 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +// workaround for name clash with stdlib +#define system qubicSystemStruct + +#include "../src/contract_core/contract_def.h" +#include "../src/contract_core/contract_exec.h" +#include "../src/contract_core/qpi_spectrum_impl.h" + +#include "../src/public_settings.h" +#undef PENDING_TXS_POOL_NUM_TICKS +#define PENDING_TXS_POOL_NUM_TICKS 50ULL +#undef NUMBER_OF_TRANSACTIONS_PER_TICK +#define NUMBER_OF_TRANSACTIONS_PER_TICK 128ULL +#include "../src/ticking/pending_txs_pool.h" + +#include +#include + +static constexpr unsigned int NUM_INITIALIZED_ENTITIES = 200U; + +class TestPendingTxsPool : public PendingTxsPool +{ + unsigned char transactionBuffer[MAX_TRANSACTION_SIZE]; +public: + TestPendingTxsPool() + { + // we need the spectrum for tx priority calculation + EXPECT_TRUE(initSpectrum()); + memset(spectrum, 0, spectrumSizeInBytes); + for (unsigned int i = 0; i < NUM_INITIALIZED_ENTITIES; i++) + { + // create NUM_INITIALIZED_ENTITIES entities with balance > 0 to get desired txs priority + spectrum[i].incomingAmount = i + 1; + spectrum[i].outgoingAmount = 0; + spectrum[i].publicKey = m256i{0, 0, 0, i + 1 }; + + // create NUM_INITIALIZED_ENTITIES entities with balance = 0 for testing + spectrum[NUM_INITIALIZED_ENTITIES + i].incomingAmount = 0; + spectrum[NUM_INITIALIZED_ENTITIES + i].outgoingAmount = 0; + spectrum[NUM_INITIALIZED_ENTITIES + i].publicKey = m256i{ 0, 0, 0, NUM_INITIALIZED_ENTITIES + i + 1 }; + } + updateSpectrumInfo(); + commonBuffers.init(1, sizeof(*txsPriorities)); + } + + ~TestPendingTxsPool() + { + deinitSpectrum(); + commonBuffers.deinit(); + } + + static constexpr unsigned int getMaxNumTxsPerTick() + { + return maxNumTxsPerTick; + } + + bool addTransaction(unsigned int tick, long long amount, unsigned int inputSize, const m256i* dest = nullptr, const m256i* src = nullptr) + { + Transaction* transaction = (Transaction*)transactionBuffer; + transaction->amount = amount; + if (dest == nullptr) + transaction->destinationPublicKey.setRandomValue(); + else + transaction->destinationPublicKey.assign(*dest); + if (src == nullptr) + transaction->sourcePublicKey.setRandomValue(); + else + transaction->sourcePublicKey.assign(*src); + transaction->inputSize = inputSize; + transaction->inputType = 0; + transaction->tick = tick; + + return add(transaction); + } + + unsigned int addTickTransactions(unsigned int tick, unsigned long long seed, unsigned int maxTransactions) + { + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + unsigned int numTransactionsAdded = 0; + + // add transactions of tick + unsigned int transactionNum = gen64() % (maxTransactions + 1); + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + unsigned int inputSize = gen64() % MAX_INPUT_SIZE; + long long amount = gen64() % MAX_AMOUNT; + m256i srcPublicKey = m256i{ 0, 0, 0, (gen64() % NUM_INITIALIZED_ENTITIES) + 1 }; + if (addTransaction(tick, amount, inputSize, /*dest=*/nullptr, &srcPublicKey)) + numTransactionsAdded++; + } + checkStateConsistencyWithAssert(); + + return numTransactionsAdded; + } + + void checkTickTransactions(unsigned int tick, unsigned long long seed, unsigned int maxTransactions) + { + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // check transactions of tick + unsigned int transactionNum = gen64() % (maxTransactions + 1); + + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + unsigned int expectedInputSize = gen64() % MAX_INPUT_SIZE; + long long expectedAmount = gen64() % MAX_AMOUNT; + m256i expectedSrcPublicKey = m256i{ 0, 0, 0, (gen64() % NUM_INITIALIZED_ENTITIES) + 1 }; + + Transaction* tp = getTx(tick, transaction); + + ASSERT_NE(tp, nullptr); + + EXPECT_TRUE(tp->checkValidity()); + EXPECT_EQ(tp->tick, tick); + EXPECT_EQ(static_cast(tp->inputSize), expectedInputSize); + EXPECT_EQ(tp->amount, expectedAmount); + EXPECT_TRUE(tp->sourcePublicKey == expectedSrcPublicKey); + + m256i* digest = getDigest(tick, transaction); + + ASSERT_NE(digest, nullptr); + + m256i tpDigest; + KangarooTwelve(tp, tp->totalSize(), &tpDigest, 32); + EXPECT_EQ(*digest, tpDigest); + } + } +}; + + +TEST(TestPendingTxsPool, EpochTransition) +{ + TestPendingTxsPool pendingTxsPool; + + unsigned long long seed = 42; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // 5x test with running 3 epoch transitions + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + // first, test case of having no transactions + unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + constexpr unsigned int firstEpochTicks = PENDING_TXS_POOL_NUM_TICKS; + // second epoch start will reset the pool completely because secondEpochTick0 is not contained + constexpr unsigned int secondEpochTicks = PENDING_TXS_POOL_NUM_TICKS / 2; + // thirdEpochTick0 will be contained with newInitialIndex >= buffersBeginIndex + constexpr unsigned int thirdEpochTicks = PENDING_TXS_POOL_NUM_TICKS / 2 + PENDING_TXS_POOL_NUM_TICKS / 4; + // fourthEpochTick0 will be contained with newInitialIndex < buffersBeginIndex + const unsigned int firstEpochTick0 = gen64() % 10000000; + const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; + const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; + const unsigned int fourthEpochTick0 = thirdEpochTick0 + thirdEpochTicks; + unsigned long long firstEpochSeeds[firstEpochTicks]; + unsigned long long secondEpochSeeds[secondEpochTicks]; + unsigned long long thirdEpochSeeds[thirdEpochTicks]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen64(); + for (int i = 0; i < secondEpochTicks; ++i) + secondEpochSeeds[i] = gen64(); + for (int i = 0; i < thirdEpochTicks; ++i) + thirdEpochSeeds[i] = gen64(); + unsigned int numAdded = 0; + + // first epoch + pendingTxsPool.beginEpoch(firstEpochTick0); + pendingTxsPool.checkStateConsistencyWithAssert(); + + // add ticks transactions + for (int i = 0; i < firstEpochTicks; ++i) + numAdded = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + + // check ticks transactions + for (int i = 0; i < firstEpochTicks; ++i) + pendingTxsPool.checkTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + + pendingTxsPool.checkStateConsistencyWithAssert(); + + // Epoch transistion + pendingTxsPool.beginEpoch(secondEpochTick0); + pendingTxsPool.checkStateConsistencyWithAssert(); + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(secondEpochTick0), 0); + + // add ticks transactions + for (int i = 0; i < secondEpochTicks; ++i) + numAdded = pendingTxsPool.addTickTransactions(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); + + // check ticks transactions + for (int i = 0; i < secondEpochTicks; ++i) + pendingTxsPool.checkTickTransactions(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); + + // add a transaction for the next epoch + numAdded = pendingTxsPool.addTickTransactions(thirdEpochTick0 + 1, thirdEpochSeeds[1], maxTransactions); + + pendingTxsPool.checkStateConsistencyWithAssert(); + + // Epoch transistion + pendingTxsPool.beginEpoch(thirdEpochTick0); + pendingTxsPool.checkStateConsistencyWithAssert(); + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(thirdEpochTick0), numAdded); + + // add ticks transactions + for (int i = 2; i < thirdEpochTicks; ++i) + numAdded = pendingTxsPool.addTickTransactions(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); + + // check ticks transactions + for (int i = 1; i < thirdEpochTicks; ++i) + pendingTxsPool.checkTickTransactions(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); + + // add a transaction for the next epoch + numAdded = pendingTxsPool.addTickTransactions(fourthEpochTick0 + 1, /*seed=*/42, maxTransactions); + + pendingTxsPool.checkStateConsistencyWithAssert(); + + // Epoch transistion + pendingTxsPool.beginEpoch(fourthEpochTick0); + pendingTxsPool.checkStateConsistencyWithAssert(); + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(fourthEpochTick0), numAdded); + + pendingTxsPool.deinit(); + } +} + +TEST(TestPendingTxsPool, TotalNumberOfPendingTxs) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 1337; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // 5x test with running 1 epoch + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + // first, test case of having no transactions + unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + const int firstEpochTicks = (gen64() % (4 * PENDING_TXS_POOL_NUM_TICKS)) + 1; + const unsigned int firstEpochTick0 = gen64() % 10000000; + unsigned long long firstEpochSeeds[4 * PENDING_TXS_POOL_NUM_TICKS]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen64(); + + // first epoch + pendingTxsPool.beginEpoch(firstEpochTick0); + + // add ticks transactions + std::vector numTransactionsAdded(firstEpochTicks); + std::vector numPendingTransactions(firstEpochTicks, 0); + for (int i = firstEpochTicks - 1; i >= 0; --i) + { + numTransactionsAdded[i] = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + if (i > 0) + { + numPendingTransactions[i - 1] = numPendingTransactions[i] + numTransactionsAdded[i]; + } + } + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), (unsigned int)numTransactionsAdded[0] + numPendingTransactions[0]); + for (int i = 0; i < firstEpochTicks; ++i) + { + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 + i), (unsigned int)numPendingTransactions[i]); + } + + pendingTxsPool.deinit(); + } +} + +TEST(TestPendingTxsPool, NumberOfPendingTickTxs) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 67534; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // 5x test with running 1 epoch + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + // first, test case of having no transactions + unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + constexpr unsigned int firstEpochTicks = PENDING_TXS_POOL_NUM_TICKS; + const unsigned int firstEpochTick0 = gen64() % 10000000; + unsigned long long firstEpochSeeds[firstEpochTicks]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen64(); + + // first epoch + pendingTxsPool.beginEpoch(firstEpochTick0); + + // add ticks transactions + std::vector numTransactionsAdded(firstEpochTicks); + for (int i = firstEpochTicks - 1; i >= 0; --i) + { + numTransactionsAdded[i] = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + } + + EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0 - 1), 0); + for (int i = 0; i < firstEpochTicks; ++i) + { + EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0 + i), (unsigned int)numTransactionsAdded[i]); + } + + pendingTxsPool.deinit(); + } +} + +TEST(TestPendingTxsPool, IncrementFirstStoredTick) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 84129; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // 5x test with running 1 epoch + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + // first, test case of having no transactions + unsigned int maxTransactions = (testIdx == 0) ? 0 : pendingTxsPool.getMaxNumTxsPerTick(); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + const int firstEpochTicks = (gen64() % (4 * PENDING_TXS_POOL_NUM_TICKS)) + 1; + const unsigned int firstEpochTick0 = gen64() % 10000000; + unsigned long long firstEpochSeeds[4 * PENDING_TXS_POOL_NUM_TICKS]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen64(); + + // first epoch + pendingTxsPool.beginEpoch(firstEpochTick0); + + // add ticks transactions + std::vector numTransactionsAdded(firstEpochTicks); + std::vector numPendingTransactions(firstEpochTicks, 0); + for (int i = firstEpochTicks - 1; i >= 0; --i) + { + numTransactionsAdded[i] = pendingTxsPool.addTickTransactions(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + if (i > 0) + { + numPendingTransactions[i - 1] = numPendingTransactions[i] + numTransactionsAdded[i]; + } + } + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), (unsigned int)numTransactionsAdded[0] + numPendingTransactions[0]); + for (int i = 0; i < firstEpochTicks; ++i) + { + pendingTxsPool.incrementFirstStoredTick(); + for (int tx = 0; tx < numTransactionsAdded[i]; ++tx) + { + EXPECT_EQ(pendingTxsPool.getTx(firstEpochTick0 + i, 0), nullptr); + EXPECT_EQ(pendingTxsPool.getDigest(firstEpochTick0 + i, 0), nullptr); + } + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 + i), (unsigned int)numPendingTransactions[i]); + } + + pendingTxsPool.deinit(); + } +} + +TEST(TestPendingTxsPool, TxsPrioritizationMoreThanMaxTxs) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 9532; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + const unsigned int firstEpochTick0 = gen64() % 10000000; + unsigned int numAdditionalTxs = 64; + + pendingTxsPool.beginEpoch(firstEpochTick0); + + // add more than `pendingTxsPool.getMaxNumTxsPerTick()` with increasing priority + // (entities were set up in a way that u64._0 of the public key corresponds to their balance) + m256i srcPublicKey = m256i::zero(); + for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick() + numAdditionalTxs; ++t) + { + srcPublicKey.u64._3 = t + 1; + EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, /*amount=*/t + 1, /*inputSize=*/0, /*dest=*/nullptr, &srcPublicKey)); + } + + // adding lower priority tx does not work + srcPublicKey.u64._3 = 1; + EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, /*amount=*/1, /*inputSize=*/0, /*dest=*/nullptr, &srcPublicKey)); + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), pendingTxsPool.getMaxNumTxsPerTick()); + EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0), pendingTxsPool.getMaxNumTxsPerTick()); + + for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick(); ++t) + { + if (t < numAdditionalTxs) + EXPECT_EQ(pendingTxsPool.getTx(firstEpochTick0, t)->amount, pendingTxsPool.getMaxNumTxsPerTick() + t + 1); + else + EXPECT_EQ(pendingTxsPool.getTx(firstEpochTick0, t)->amount, t + 1); + } + + pendingTxsPool.deinit(); +} + +TEST(TestPendingTxsPool, RejectDuplicateTxs) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 9532; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + const unsigned int firstEpochTick0 = gen64() % 10000000; + constexpr unsigned int numTxs = pendingTxsPool.getMaxNumTxsPerTick() / 2; + + pendingTxsPool.beginEpoch(firstEpochTick0); + + // try to add duplicate transactions: same dest, src, amount, tick, input size/type + m256i dest{ 562, 789, 234, 121 }; + m256i src{ 0, 0, 0, NUM_INITIALIZED_ENTITIES / 3 }; + long long amount = 1; + + // first add should succeed, all others should fail + EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, amount, /*inputSize=*/0, &dest, &src)); + for (unsigned int t = 1; t < numTxs; ++t) + EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, amount, /*inputSize=*/0, &dest, &src)); + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), 1); + EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0), 1); + + Transaction* tx = pendingTxsPool.getTx(firstEpochTick0, 0); + EXPECT_TRUE(tx->checkValidity()); + EXPECT_EQ(tx->amount, amount); + EXPECT_EQ(tx->tick, firstEpochTick0); + EXPECT_EQ(static_cast(tx->inputSize), 0U); + EXPECT_TRUE(tx->destinationPublicKey == dest); + EXPECT_TRUE(tx->sourcePublicKey == src); + + pendingTxsPool.deinit(); +} + +TEST(TestPendingTxsPool, ProtocolLevelTxsMaxPriority) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 9532; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + const unsigned int firstEpochTick0 = gen64() % 10000000; + + pendingTxsPool.beginEpoch(firstEpochTick0); + + // fill the PendingTxsPool completely for tick `firstEpochTick0` + m256i srcPublicKey = m256i::zero(); + for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick(); ++t) + { + srcPublicKey.u64._3 = t + 1; + EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); + } + + EXPECT_EQ(pendingTxsPool.getTotalNumberOfPendingTxs(firstEpochTick0 - 1), pendingTxsPool.getMaxNumTxsPerTick()); + EXPECT_EQ(pendingTxsPool.getNumberOfPendingTickTxs(firstEpochTick0), pendingTxsPool.getMaxNumTxsPerTick()); + + Transaction tx { + .sourcePublicKey = m256i{ 0, 0, 0, (gen64() % NUM_INITIALIZED_ENTITIES) + 1 }, + .destinationPublicKey = m256i::zero(), + .amount = 0, .tick = firstEpochTick0, + .inputType = VOTE_COUNTER_INPUT_TYPE, + .inputSize = 0, + }; + + EXPECT_TRUE(pendingTxsPool.add(&tx)); + + tx.inputType = CustomMiningSolutionTransaction::transactionType(); + + EXPECT_TRUE(pendingTxsPool.add(&tx)); + + pendingTxsPool.deinit(); +} + +TEST(TestPendingTxsPool, TxsWithSrcBalance0AreRejected) +{ + TestPendingTxsPool pendingTxsPool; + unsigned long long seed = 3452; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + pendingTxsPool.init(); + pendingTxsPool.checkStateConsistencyWithAssert(); + + const unsigned int firstEpochTick0 = gen64() % 10000000; + + pendingTxsPool.beginEpoch(firstEpochTick0); + + // partially fill the PendingTxsPool for tick `firstEpochTick0` + m256i srcPublicKey = m256i::zero(); + for (unsigned int t = 0; t < pendingTxsPool.getMaxNumTxsPerTick() / 2; ++t) + { + srcPublicKey.u64._3 = t + 1; + EXPECT_TRUE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); + } + + // public key with balance 0 + srcPublicKey.u64._3 = NUM_INITIALIZED_ENTITIES + 1 + (gen64() % NUM_INITIALIZED_ENTITIES); + EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); + + // non-existant public key + srcPublicKey = m256i{ 0, gen64() % MAX_AMOUNT, 0, 0}; + EXPECT_FALSE(pendingTxsPool.addTransaction(firstEpochTick0, gen64() % MAX_AMOUNT, gen64() % MAX_INPUT_SIZE, /*dest=*/nullptr, &srcPublicKey)); + + pendingTxsPool.deinit(); + } +} \ No newline at end of file diff --git a/test/platform.cpp b/test/platform.cpp new file mode 100644 index 000000000..5bc997dd3 --- /dev/null +++ b/test/platform.cpp @@ -0,0 +1,501 @@ +#define NO_UEFI + +#include "gtest/gtest.h" +#include "../src/platform/read_write_lock.h" +#include "../src/platform/stack_size_tracker.h" +#include "../src/platform/custom_stack.h" +#include "../src/platform/profiling.h" + +#include "common_buffers.h" +#include + +TEST(TestCoreReadWriteLock, SimpleSingleThread) +{ + ReadWriteLock l; + l.reset(); + + // Aquire lock for multiple readers + constexpr int numReaders = 10; + for (int i = 0; i < numReaders; ++i) + { + EXPECT_TRUE(l.tryAcquireRead()); + EXPECT_FALSE(l.tryAcquireWrite()); + } + + // Release read locks + for (int i = 0; i < numReaders; ++i) + { + l.releaseRead(); + } + + // Aquire lock for writer + EXPECT_TRUE(l.tryAcquireWrite()); + EXPECT_FALSE(l.tryAcquireWrite()); + EXPECT_FALSE(l.tryAcquireRead()); + + // Release write lock + l.releaseWrite(); + + l.acquireRead(); + l.releaseRead(); + + l.acquireWrite(); + l.releaseWrite(); +} + + +StackSizeTracker stackSizeTracker; + +template +void recursion(int i) +{ + // use volatile and get address to make sure this is not optimized away and kept on stack + volatile unsigned long long data[stackVarSize]; + data[0] = (unsigned long long)(volatile unsigned long long*)data; + + unsigned long long oldSize = stackSizeTracker.maxStackSize(); + stackSizeTracker.update(); + unsigned long long newSize = stackSizeTracker.maxStackSize(); + + EXPECT_NE(newSize, StackSizeTracker::uninitialized); + EXPECT_GT(newSize, oldSize); + + if (i > 0) + recursion(i - 1); +} + +template +unsigned long long testStackSizeTracker(int recursionLevel) +{ + INIT_STACK_SIZE_TRACKER(stackSizeTracker); + long long data[10]; data[0] = 0; + stackSizeTracker.update(); + auto size = stackSizeTracker.maxStackSize(); + { + // this does not change size -> shows that storage of all stack variables of function + // is reserved, independently of scope + long long data2[10]; data2[0] = 0; + stackSizeTracker.update(); + size = stackSizeTracker.maxStackSize(); + } + recursion(recursionLevel); + size = stackSizeTracker.maxStackSize(); + stackSizeTracker.reset(); + return size; +} + +// Test that max. measured stack size is kept if tracker is reused with other stack top address +// (relevant if restarted processor/thread gets other stack address) +void testStackSizeTrackerKeepSizeCheck(unsigned long long prevSize) +{ + // check that max size is kept on reuse + INIT_STACK_SIZE_TRACKER(stackSizeTracker); + EXPECT_EQ(stackSizeTracker.maxStackSize(), prevSize); +} +void testStackSizeTrackerKeepSize() +{ + // first use + stackSizeTracker.reset(); + INIT_STACK_SIZE_TRACKER(stackSizeTracker); + UPDATE_STACK_SIZE_TRACKER(stackSizeTracker); + testStackSizeTrackerKeepSizeCheck(stackSizeTracker.maxStackSize()); +} + + +TEST(TestCoreStackSizeTracker, SimpleTest) +{ + // Return uninit if not not both functions are called + DEFINE_STACK_SIZE_TRACKER(s1); + EXPECT_EQ(s1.maxStackSize(), StackSizeTracker::uninitialized); + INIT_STACK_SIZE_TRACKER(s1); + EXPECT_EQ(s1.maxStackSize(), StackSizeTracker::uninitialized); + DEFINE_STACK_SIZE_TRACKER(s2); + EXPECT_EQ(s2.maxStackSize(), StackSizeTracker::uninitialized); + UPDATE_STACK_SIZE_TRACKER(s2); + EXPECT_EQ(s2.maxStackSize(), StackSizeTracker::uninitialized); + + // Test that more recursion and more data per function lead to bigger stack size + EXPECT_LT(testStackSizeTracker<10>(0), testStackSizeTracker<10>(1)); + EXPECT_LT(testStackSizeTracker<10>(5), testStackSizeTracker<10>(10)); + EXPECT_LT(testStackSizeTracker<10>(10), testStackSizeTracker<20>(10)); + EXPECT_LT(testStackSizeTracker<30>(20), testStackSizeTracker<30>(40)); + + // Test that max. measured stack size is kept if tracker is reused with other stack top address + testStackSizeTrackerKeepSize(); +} + + +void customStackTest1(void* data) +{ + EXPECT_EQ(data, (void*)5); +} + +void customStackTest2(void* data) +{ + int recursionLevel = (int)(unsigned long long)data; + testStackSizeTracker<10>(recursionLevel); +} + + +TEST(TestCoreCustomStack, SimpleTest) +{ + CustomStack s; + s.init(); + EXPECT_EQ(s.maxStackUsed(), 0); + EXPECT_TRUE(s.alloc(128*1024)); + EXPECT_EQ(s.maxStackUsed(), 0); + + // run function with small stack requirements on custom stack + s.setupFunction(customStackTest1, (void*)5); + CustomStack::runFunction(&s); + auto size1 = s.maxStackUsed(); + EXPECT_GT(size1, 0u); + + // run recursive function with small stack requirements on custom stack + s.setupFunction(customStackTest2, (void*)1); + CustomStack::runFunction(&s); + auto size2 = s.maxStackUsed(); + EXPECT_GT(size2, size1); + + // run recursive function with medium stack requirements on custom stack + s.setupFunction(customStackTest2, (void*)5); + CustomStack::runFunction(&s); + auto size3 = s.maxStackUsed(); + EXPECT_GT(size3, size2); + + // run recursive function with large stack requirements on custom stack + s.setupFunction(customStackTest2, (void*)10); + CustomStack::runFunction(&s); + auto size4 = s.maxStackUsed(); + EXPECT_GT(size4, size3); +} + +void recursiveProfilingTest(int depth) +{ + ProfilingScope profScope(__FUNCTION__, __LINE__); + if (depth > 0) + { + recursiveProfilingTest(depth - 1); + recursiveProfilingTest(depth - 2); + } + else + { + sleepMicroseconds(10); + } +} + +void iterativeProfilingTest(int n) +{ + ProfilingScope profScope(__FUNCTION__, __LINE__); + for (int i = 0; i < n; ++i) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + for (int j = 0; j < n; ++j) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + for (int k = 0; k < n; ++k) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + sleepMicroseconds(10); + } + } + } +} + +TEST(TestCoreProfiling, SleepTest) +{ + ProfilingStopwatch profStopwatch(__FUNCTION__, __LINE__); + profStopwatch.start(); + + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + testStackSizeTrackerKeepSize(); + } + + for (int i = 0; i < 1000; ++i) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + testStackSizeTrackerKeepSize(); + } + + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + recursiveProfilingTest(9); + } + + profStopwatch.stop(); + + for (int i = 0; i < 10; ++i) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + recursiveProfilingTest(4); + + // calling start() multiple times is no problem (last time is relevant for measuring) + profStopwatch.start(); + } + + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + iterativeProfilingTest(5); + profStopwatch.stop(); + } + + for (int i = 0; i < 5; ++i) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + iterativeProfilingTest(3); + } + + gProfilingDataCollector.writeToFile(); + + EXPECT_FALSE(gProfilingDataCollector.init(2)); + gProfilingDataCollector.clear(); + gProfilingDataCollector.deinit(); + gProfilingDataCollector.clear(); + + //gProfilingDataCollector.writeToFile(); +} + +static ProfilingStopwatch gProfStopwatch(__FILE__, __LINE__); + +TEST(TestCoreProfiling, AddMeasurementSpeedTest) +{ + for (unsigned long long i = 0; i < 10000; ++i) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + gProfStopwatch.start(); + for (unsigned long long j = 0; j < 10000; ++j) + { + ProfilingScope profScope(__FUNCTION__, __LINE__); + for (volatile unsigned long long k = 0; k < 10; ++k) + { + } + } + gProfStopwatch.stop(); + } + + gProfilingDataCollector.writeToFile(); +} + +void checkTicksToMicroseconds(int type, unsigned long long ticks, unsigned long long frequency) +{ + ::frequency = frequency; + unsigned long long microsecondsInt = ProfilingDataCollector::ticksToMicroseconds(ticks); + long double microsecondsFloat = long double(ticks) * long double(1000000) / long double(frequency); + long double diff = std::abs(microsecondsFloat - microsecondsInt); + if (type == 0) + { + // no overflow + EXPECT_LT(diff, 1.0); + } + else if (type == 1) + { + // overflow in calculation -> tolerate inaccuracy + EXPECT_LT(diff, 1000000.0); + } + else + { + // overflow in result -> expect max value + EXPECT_EQ(microsecondsInt, 0xffffffffffffffffllu); + } + ::frequency = 0; +} + +TEST(TestCoreProfiling, CheckTicksToMicroseconds) +{ + // non-overflow cases + checkTicksToMicroseconds(0, 10000, 100000000); + checkTicksToMicroseconds(0, 100000000, 10000); + checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu, 1000000llu); + checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu, 1000000llu + 1); + checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu, 1000000llu - 1); + checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu - 1, 1000000llu); + checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu - 1, 1000000llu + 1); + checkTicksToMicroseconds(0, 0xffffffffffffffffllu / 1000000llu - 1, 1000000llu - 1); + + // overflow in calculation + checkTicksToMicroseconds(1, 0xffffffffffffffffllu / 1000000llu + 1, 1000000llu); + checkTicksToMicroseconds(1, 0xffffffffffffffffllu / 1000000llu + 1, 1000000llu + 1); + checkTicksToMicroseconds(1, 0xffffffffffffffffllu, 1000000000llu); + checkTicksToMicroseconds(1, 0xffffffffffffffllu, 1000000000llu); + checkTicksToMicroseconds(1, 0xffffffffffffffffllu, 1234567890llu); + checkTicksToMicroseconds(1, 0xffffffffffffffllu, 1234567890llu); + + // overflow in result (low frequency) + checkTicksToMicroseconds(2, 0xffffffffffffffffllu, 1000); + checkTicksToMicroseconds(2, 0xffffffffffffffllu, 1000); + checkTicksToMicroseconds(2, 0xffffffffffffffffllu, 12345); + checkTicksToMicroseconds(2, 0xffffffffffffffffllu, 123456); +} + +static volatile bool waitingForAcquire = false; + +static void acquireAndReleaseCommonBuffer() +{ + waitingForAcquire = true; + void* buf = commonBuffers.acquireBuffer(64); + waitingForAcquire = false; + EXPECT_NE(buf, nullptr); + sleepMilliseconds(100); + commonBuffers.releaseBuffer(buf); +} + +TEST(TestCoreCommonBuffers, OneBuffer) +{ + if (!frequency) + initTimeStampCounter(); + + // init fail: 0 buffers / size 0 + EXPECT_FALSE(commonBuffers.init(0, 1024)); + EXPECT_FALSE(commonBuffers.init(1, 0)); + + // init success + EXPECT_TRUE(commonBuffers.init(1, 1024)); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // acquire fail: requested buffer size too big + void* buf = commonBuffers.acquireBuffer(2000); + EXPECT_EQ(buf, nullptr); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // acquire success + buf = commonBuffers.acquireBuffer(1024); + EXPECT_NE(buf, nullptr); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + + // release success + commonBuffers.releaseBuffer(buf); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // check stats + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); + EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 0); + + // invalid release (double free) + commonBuffers.releaseBuffer(buf); + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 1); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // invalid release (invalid pointer) + commonBuffers.releaseBuffer((void*)0x123456); + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 2); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // acquire success + buf = commonBuffers.acquireBuffer(512); + EXPECT_NE(buf, nullptr); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + + // acquire in other thread has to wait + auto thread = std::thread(acquireAndReleaseCommonBuffer); + sleepMilliseconds(100); + EXPECT_TRUE(waitingForAcquire); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + + // release buffer -> other thread can acquire it + commonBuffers.releaseBuffer(buf); + sleepMilliseconds(50); // after 50 ms the other thread should have acquired the buffer + EXPECT_FALSE(waitingForAcquire); + thread.join(); // after ending the thread, the buffer is release again + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 1); + + // free all buffers, reset state + commonBuffers.deinit(); + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); + EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 0); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); +} + +TEST(TestCoreCommonBuffers, ThreeBuffers) +{ + if (!frequency) + initTimeStampCounter(); + + // init success + EXPECT_TRUE(commonBuffers.init(3, 1024)); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // acquire fail: requested buffer size too big + void* buf = commonBuffers.acquireBuffer(2000); + EXPECT_EQ(buf, nullptr); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // acquire success + buf = commonBuffers.acquireBuffer(1024); + EXPECT_NE(buf, nullptr); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + + // release + commonBuffers.releaseBuffer(buf); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // acquire three buffer success + void* buf2 = commonBuffers.acquireBuffer(1024); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + void* buf3 = commonBuffers.acquireBuffer(42); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 2); + buf = commonBuffers.acquireBuffer(123); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); + EXPECT_NE(buf, nullptr); + EXPECT_NE(buf2, nullptr); + EXPECT_NE(buf3, nullptr); + + // release three buffers + commonBuffers.releaseBuffer(buf); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 2); + commonBuffers.releaseBuffer(buf2); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + commonBuffers.releaseBuffer(buf3); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + // check stats + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); + EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 1); // heuristic is off by one (in facto no waiting needed) + + // invalid release (double free) + commonBuffers.releaseBuffer(buf); + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 1); + + // invalid release (invalid pointer) + commonBuffers.releaseBuffer((void*)0x123456); + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 2); + + // acquire three buffers + buf = commonBuffers.acquireBuffer(512); + buf2 = commonBuffers.acquireBuffer(1024); + buf3 = commonBuffers.acquireBuffer(987); + EXPECT_NE(buf, nullptr); + EXPECT_NE(buf2, nullptr); + EXPECT_NE(buf3, nullptr); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); + + // acquire in two other thread has to wait + auto thread1 = std::thread(acquireAndReleaseCommonBuffer); + auto thread2 = std::thread(acquireAndReleaseCommonBuffer); + sleepMilliseconds(100); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); + EXPECT_TRUE(waitingForAcquire); + + // release 2 buffers -> other threads can acquire them + commonBuffers.releaseBuffer(buf); + commonBuffers.releaseBuffer(buf2); + sleepMilliseconds(50); + EXPECT_FALSE(waitingForAcquire); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 3); + thread1.join(); + thread2.join(); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 1); + + commonBuffers.releaseBuffer(buf3); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); + + EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 2); + + // free all buffers, reset state + commonBuffers.deinit(); + EXPECT_EQ(commonBuffers.getInvalidReleaseCount(), 0); + EXPECT_EQ(commonBuffers.getMaxWaitingProcessorCount(), 0); + EXPECT_EQ(commonBuffers.acquiredBuffers(), 0); +} diff --git a/test/qpi.cpp b/test/qpi.cpp new file mode 100644 index 000000000..ca0b62c9c --- /dev/null +++ b/test/qpi.cpp @@ -0,0 +1,2313 @@ +#define NO_UEFI + +#include "contract_testing.h" + +#include + +// changing offset simulates changed computor set with changed epoch +void initComputors(unsigned short computorIdOffset) +{ + for (unsigned short computorIndex = 0; computorIndex < NUMBER_OF_COMPUTORS; ++computorIndex) + { + broadcastedComputors.computors.publicKeys[computorIndex] = QPI::id(computorIndex + computorIdOffset, 9, 8, 7); + } +} + +TEST(TestCoreQPI, SafeMath) +{ + { + sint64 a = -1000000000000LL; // This is valid - negative signed integer + sint64 b = 2; // Positive signed integer + EXPECT_EQ(smul(a, b), -2000000000000); + } + + { + uint64_t a = 1000000; + uint64_t b = 2000000; + uint64_t expected_ok = 2000000000000ULL; + EXPECT_EQ(smul(a, b), expected_ok); + } + { + sint64 a = INT64_MIN; // -9223372036854775808 + sint64 b = -1; // -1 + EXPECT_EQ(smul(a, b), INT64_MAX); + } + { + uint64_t a = 123456789ULL; + uint64_t b = 987654321ULL; + + // Case: Multiplication by zero. + EXPECT_EQ(smul(a, 0ULL), 0ULL); + EXPECT_EQ(smul(0ULL, b), 0ULL); + + // Case: Multiplication by one. + EXPECT_EQ(smul(a, 1ULL), a); + EXPECT_EQ(smul(1ULL, b), b); + } + { + // Case: A clear overflow case. + // UINT64_MAX is approximately 1.84e19. + uint64_t c = 4000000000ULL; + uint64_t d = 5000000000ULL; // c * d is 2e19, which overflows. + EXPECT_EQ(smul(c, d), UINT64_MAX); + } + { + // Case: Test the exact boundary of overflow. + uint64_t max_val = UINT64_MAX; + uint64_t divisor = 2; + uint64_t limit = max_val / divisor; + + // This should not overflow. + EXPECT_EQ(smul(limit, divisor), limit * 2); + + // This should overflow and clamp. + EXPECT_EQ(smul(limit + 1, divisor), UINT64_MAX); + } + { + // Case: A simple multiplication that does not overflow. + int64_t e = 1000000; + int64_t f = -2000000; + EXPECT_EQ(smul(e, f), -2000000000000LL); + } + { + // Case: Positive * Positive, causing overflow. + int64_t a = INT64_MAX / 2; + int64_t b = 3; + EXPECT_EQ(smul(a, b), INT64_MAX); + } + { + int64_t a = INT64_MAX / 2; + int64_t b = 3; + int64_t c = -3; + int64_t d = INT64_MIN / 2; + + // Case: Positive * Negative, causing underflow. + EXPECT_EQ(smul(a, c), INT64_MIN); + + // Case: Negative * Positive, causing underflow. + EXPECT_EQ(smul(d, b), INT64_MIN); + } + { + // Case: Negative * Negative, causing overflow. + int64_t c = -3; + int64_t d = INT64_MIN / 2; + EXPECT_EQ(smul(d, c), INT64_MAX); + } + { + // --- Unsigned 32-bit Tests --- + // No Overflow + uint32_t a_u32 = 60000; + uint32_t b_u32 = 60000; + EXPECT_EQ(smul(a_u32, b_u32), 3600000000U); + + // Overflow + uint32_t c_u32 = 70000; + uint32_t d_u32 = 70000; // 70000*70000 = 4,900,000,000 which is > UINT32_MAX (~4.29e9) + EXPECT_EQ(smul(c_u32, d_u32), UINT32_MAX); + + // Boundary + uint32_t limit_u32 = UINT32_MAX / 2; + uint32_t divisor_u32 = 2; + EXPECT_EQ(smul(limit_u32, divisor_u32), limit_u32 * 2); + EXPECT_EQ(smul(limit_u32 + 1, divisor_u32), UINT32_MAX); + + // --- Signed 32-bit Tests --- + // No Overflow + int32_t a_s32 = 10000; + int32_t b_s32 = -10000; + EXPECT_EQ(smul(a_s32, b_s32), -100000000); + + // Positive Overflow + int32_t c_s32 = INT32_MAX / 2; + int32_t d_s32 = 3; + EXPECT_EQ(smul(c_s32, d_s32), INT32_MAX); + + // Underflow + int32_t e_s32 = INT32_MIN / 2; + int32_t f_s32 = 3; + EXPECT_EQ(smul(e_s32, f_s32), INT32_MIN); + + // Negative * Negative, causing overflow. + int32_t g_s32 = -3; + int32_t h_s32 = INT32_MIN / 2; + EXPECT_EQ(smul(h_s32, g_s32), INT32_MAX); + } +} + +TEST(TestCoreQPI, Array) +{ + //QPI::array mustFail; // should raise compile error + + QPI::Array uint8_4; + EXPECT_EQ(uint8_4.capacity(), 4); + //uint8_4.setMem(QPI::id(1, 2, 3, 4)); // should raise compile error + uint8_4.setAll(2); + EXPECT_EQ(uint8_4.get(0), 2); + EXPECT_EQ(uint8_4.get(1), 2); + EXPECT_EQ(uint8_4.get(2), 2); + EXPECT_EQ(uint8_4.get(3), 2); + EXPECT_TRUE(uint8_4.rangeEquals(0, 4, 2)); + EXPECT_TRUE(isArraySorted(uint8_4)); + EXPECT_FALSE(isArraySortedWithoutDuplicates(uint8_4)); + uint8_4.set(3, 1); + EXPECT_FALSE(isArraySorted(uint8_4)); + EXPECT_FALSE(isArraySortedWithoutDuplicates(uint8_4)); + uint8_4.setRange(1, 3, 0); + EXPECT_EQ(uint8_4.get(0), 2); + EXPECT_EQ(uint8_4.get(1), 0); + EXPECT_EQ(uint8_4.get(2), 0); + EXPECT_EQ(uint8_4.get(3), 1); + EXPECT_TRUE(uint8_4.rangeEquals(1, 3, 0)); + EXPECT_FALSE(isArraySorted(uint8_4)); + EXPECT_FALSE(isArraySortedWithoutDuplicates(uint8_4)); + for (int i = 0; i < uint8_4.capacity(); ++i) + uint8_4.set(i, i+1); + for (int i = 0; i < uint8_4.capacity(); ++i) + EXPECT_EQ(uint8_4.get(i), i+1); + EXPECT_FALSE(uint8_4.rangeEquals(0, 4, 2)); + EXPECT_TRUE(isArraySorted(uint8_4)); + EXPECT_TRUE(isArraySortedWithoutDuplicates(uint8_4)); + + QPI::Array uint64_4; + uint64_4.setMem(QPI::id(101, 102, 103, 104)); + for (int i = 0; i < uint64_4.capacity(); ++i) + EXPECT_EQ(uint64_4.get(i), i + 101); + //uint64_4.setMem(uint8_4); // should raise compile error + + QPI::Array uint16_2; + EXPECT_EQ(uint8_4.capacity(), 4); + //uint16_2.setMem(QPI::id(1, 2, 3, 4)); // should raise compile error + uint16_2.setAll(12345); + EXPECT_EQ((int)uint16_2.get(0), 12345); + EXPECT_EQ((int)uint16_2.get(1), 12345); + for (int i = 0; i < uint16_2.capacity(); ++i) + uint16_2.set(i, i + 987); + for (int i = 0; i < uint16_2.capacity(); ++i) + EXPECT_EQ((int)uint16_2.get(i), i + 987); + uint16_2.setMem(uint8_4); + for (int i = 0; i < uint16_2.capacity(); ++i) + EXPECT_EQ((int)uint16_2.get(i), (int)(((2*i+2) << 8) | (2*i + 1))); +} + +TEST(TestCoreQPI, BitArray) +{ + //QPI::BitArray<0> mustFail; + + QPI::BitArray<1> b1; + EXPECT_EQ(b1.capacity(), 1); + b1.setAll(0); + EXPECT_EQ(b1.get(0), 0); + b1.setAll(1); + EXPECT_EQ(b1.get(0), 1); + b1.setAll(true); + EXPECT_EQ(b1.get(0), 1); + b1.set(0, 1); + EXPECT_EQ(b1.get(0), 1); + b1.set(0, 0); + EXPECT_EQ(b1.get(0), 0); + b1.set(0, true); + EXPECT_EQ(b1.get(0), 1); + + b1.setAll(0); + QPI::BitArray<1> b1_2; + b1_2.setAll(0); + QPI::BitArray<1> b1_3; + b1_3.setAll(1); + EXPECT_TRUE(b1 == b1_2); + EXPECT_TRUE(b1 != b1_3); + EXPECT_FALSE(b1 == b1_3); + + QPI::BitArray<64> b64; + EXPECT_EQ(b64.capacity(), 64); + b64.setMem(0x11llu); + EXPECT_EQ(b64.get(0), 1); + EXPECT_EQ(b64.get(1), 0); + EXPECT_EQ(b64.get(2), 0); + EXPECT_EQ(b64.get(3), 0); + EXPECT_EQ(b64.get(4), 1); + EXPECT_EQ(b64.get(5), 0); + EXPECT_EQ(b64.get(6), 0); + EXPECT_EQ(b64.get(7), 0); + QPI::Array llu1; + llu1.setMem(b64); + EXPECT_EQ(llu1.get(0), 0x11llu); + b64.setAll(0); + llu1.setMem(b64); + EXPECT_EQ(llu1.get(0), 0x0); + b64.setAll(1); + llu1.setMem(b64); + EXPECT_EQ(llu1.get(0), 0xffffffffffffffffllu); + + + b64.setMem(0x11llu); + QPI::BitArray<64> b64_2; + EXPECT_EQ(b64.capacity(), 64); + b64_2.setMem(0x11llu); + QPI::BitArray<64> b64_3; + EXPECT_EQ(b64.capacity(), 64); + b64_3.setMem(0x55llu); + EXPECT_TRUE(b64 == b64_2); + EXPECT_TRUE(b64 != b64_3); + EXPECT_FALSE(b64 == b64_3); + + //QPI::BitArray<96> b96; // must trigger compile error + + QPI::BitArray<128> b128; + EXPECT_EQ(b128.capacity(), 128); + QPI::Array llu2; + llu2.setAll(0x4llu); + EXPECT_EQ(llu2.get(0), 0x4llu); + EXPECT_EQ(llu2.get(1), 0x4llu); + b128.setMem(llu2); + for (int i = 0; i < 2; ++i) + { + for (int j = 0; j < 64; ++j) + { + EXPECT_EQ(b128.get(i * 64 + j), j == 2); + } + } + b128.set(0, 1); + b128.set(2, 0); + llu2.setMem(b128); + EXPECT_EQ(llu2.get(0), 0x1llu); + EXPECT_EQ(llu2.get(1), 0x4llu); + for (int i = 0; i < b128.capacity(); ++i) + { + b128.set(i, i % 2 == 0); + EXPECT_EQ(b128.get(i), i % 2 == 0); + } + llu2.setMem(b128); + EXPECT_EQ(llu2.get(0), 0x5555555555555555llu); + EXPECT_EQ(llu2.get(1), 0x5555555555555555llu); + for (int i = 0; i < b128.capacity(); ++i) + { + EXPECT_EQ(b128.get(i), i % 2 == 0); + } + + b128.setAll(1); + QPI::BitArray<128> b128_2; + QPI::BitArray<128> b128_3; + b128_2.setAll(1); + b128_3.setAll(0); + EXPECT_TRUE(b128 == b128_2); + EXPECT_TRUE(b128 != b128_3); + EXPECT_FALSE(b128 == b128_3); +} + +TEST(TestCoreQPI, Div) { + EXPECT_EQ(QPI::div(0, 0), 0); + EXPECT_EQ(QPI::div(10, 0), 0); + EXPECT_EQ(QPI::div(0, 10), 0); + EXPECT_EQ(QPI::div(20, 19), 1); + EXPECT_EQ(QPI::div(20, 20), 1); + EXPECT_EQ(QPI::div(20, 21), 0); + EXPECT_EQ(QPI::div(20, 22), 0); + EXPECT_EQ(QPI::div(50, 24), 2); + EXPECT_EQ(QPI::div(50, 25), 2); + EXPECT_EQ(QPI::div(50, 26), 1); + EXPECT_EQ(QPI::div(50, 27), 1); + EXPECT_EQ(QPI::div(-2, 0), 0); + EXPECT_EQ(QPI::div(-2, 1), -2); + EXPECT_EQ(QPI::div(-2, 2), -1); + EXPECT_EQ(QPI::div(-2, 3), 0); + EXPECT_EQ(QPI::div(2, -3), 0); + EXPECT_EQ(QPI::div(2, -2), -1); + EXPECT_EQ(QPI::div(2, -1), -2); + + EXPECT_EQ(QPI::div(0.0, 0.0), 0.0); + EXPECT_EQ(QPI::div(50.0, 0.0), 0.0); + EXPECT_EQ(QPI::div(0.0, 50.0), 0.0); + EXPECT_EQ(QPI::div(-25.0, 50.0), -0.5); + EXPECT_EQ(QPI::div(-25.0, -0.5), 50.0); +} + +TEST(TestCoreQPI, Mod) { + EXPECT_EQ(QPI::mod(0, 0), 0); + EXPECT_EQ(QPI::mod(10, 0), 0); + EXPECT_EQ(QPI::mod(0, 10), 0); + EXPECT_EQ(QPI::mod(20, 19), 1); + EXPECT_EQ(QPI::mod(20, 20), 0); + EXPECT_EQ(QPI::mod(20, 21), 20); + EXPECT_EQ(QPI::mod(20, 22), 20); + EXPECT_EQ(QPI::mod(50, 23), 4); + EXPECT_EQ(QPI::mod(50, 24), 2); + EXPECT_EQ(QPI::mod(50, 25), 0); + EXPECT_EQ(QPI::mod(50, 26), 24); + EXPECT_EQ(QPI::mod(50, 27), 23); + EXPECT_EQ(QPI::mod(-2, 0), 0); + EXPECT_EQ(QPI::mod(-2, 1), 0); + EXPECT_EQ(QPI::mod(-2, 2), 0); + EXPECT_EQ(QPI::mod(-2, 3), -2); + EXPECT_EQ(QPI::mod(2, -3), 2); + EXPECT_EQ(QPI::mod(2, -2), 0); + EXPECT_EQ(QPI::mod(2, -1), 0); +} + +TEST(TestCoreQPI, IdFromCharacters) +{ + using namespace QPI::Ch; + + QPI::id test('t', 'e', s, t, '!'); + EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("test!")); + + test = QPI::id(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, space, dot, comma, colon, semicolon, null); + EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("abcdefghijklmnopqrstuvwxyz .,:;")); + + test = QPI::id(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, slash, backslash); + EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("ABCDEFGHIJKLMNOPQRSTUVWXYZ/\\")); + + test = QPI::id(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9); + EXPECT_EQ(std::string((char*)test.m256i_i8), std::string("0123456789")); + + test = QPI::id(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); + EXPECT_TRUE(isZero(test)); + + test = QPI::id(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, space); + EXPECT_EQ(test.i8._31, ' '); + test.m256i_i8[31] = 0; + EXPECT_TRUE(isZero(test)); +} + + +struct ContractExecInitDeinitGuard +{ + ContractExecInitDeinitGuard() + { + EXPECT_TRUE(initContractExec()); + } + ~ContractExecInitDeinitGuard() + { + deinitContractExec(); + } +}; + +TEST(TestCoreQPI, ProposalAndVotingByComputors) +{ + ContractExecInitDeinitGuard initDeinitGuard; + QpiContextUserProcedureCall qpi(0, QPI::id(1, 2, 3, 4), 123); + QPI::ProposalAndVotingByComputors pv; + initComputors(0); + + // Memory must be zeroed to work, which is done in contract states on init + QPI::setMemory(pv, 0); + + // vote index is computor index + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + EXPECT_EQ(pv.getVoteIndex(qpi, qpi.computor(i)), i); + EXPECT_EQ(pv.getVoterId(qpi, i), qpi.computor(i)); + EXPECT_EQ(pv.getVoteCount(qpi, i), 1); + } + for (int i = NUMBER_OF_COMPUTORS; i < 800; ++i) + { + QPI::id testId(i, 9, 8, 7); + EXPECT_EQ(pv.getVoteIndex(qpi, testId), QPI::INVALID_VOTE_INDEX); + EXPECT_EQ(pv.getVoterId(qpi, i), QPI::NULL_ID); + EXPECT_EQ(pv.getVoteCount(qpi, i), 0); + } + EXPECT_EQ(pv.getVoteIndex(qpi, qpi.originator()), QPI::INVALID_VOTE_INDEX); + + // valid proposers are computors + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + EXPECT_TRUE(pv.isValidProposer(qpi, qpi.computor(i))); + for (int i = NUMBER_OF_COMPUTORS; i < 2 * NUMBER_OF_COMPUTORS; ++i) + EXPECT_FALSE(pv.isValidProposer(qpi, QPI::id(i, 9, 8, 7))); + EXPECT_FALSE(pv.isValidProposer(qpi, QPI::NULL_ID)); + EXPECT_FALSE(pv.isValidProposer(qpi, qpi.originator())); + + // no existing proposals + for (int i = 0; i < 2*NUMBER_OF_COMPUTORS; ++i) + { + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, qpi.computor(i)), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(pv.getProposerId(qpi, i), NULL_ID); + } + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); + + // fill all slots + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + int j = NUMBER_OF_COMPUTORS - 1 - i; + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, qpi.computor(j)), i); + } + + // proposals now available + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + int j = NUMBER_OF_COMPUTORS - 1 - i; + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, qpi.computor(j)), i); + EXPECT_EQ(pv.getProposerId(qpi, i), qpi.computor(j)); + } + + // using other ID fails if full (new computor ID after epoch change) + QPI::id newId(9, 8, 7, 6); + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, newId), (int)QPI::INVALID_PROPOSAL_INDEX); + + // reusing all slots should work + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + int j = NUMBER_OF_COMPUTORS - 1 - i; + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, qpi.computor(j)), i); + } + + // free one slot + pv.freeProposalByIndex(qpi, 0); + + // using other ID now works (new computor ID after epoch change) + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, newId), 0); + + // check content + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, newId), 0); + for (int i = 1; i < NUMBER_OF_COMPUTORS; ++i) + { + int j = NUMBER_OF_COMPUTORS - 1 - i; + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, qpi.computor(j)), i); + } +} + +static void sortContractShareVector(std::vector> & shareholders) +{ + std::sort(shareholders.begin(), shareholders.end(), + [](const std::pair& a, const std::pair& b) + { + return a.first < b.first; + }); +} + +template +static void checkShareholderVotingRights(const QpiContextUserProcedureCall& qpi, const ProposalAndVotingByShareholders& pv, uint16 proposalIdx, std::vector>& shareholderVec) +{ + unsigned int voterIdx = 0; + for (const auto& ownerSharesPair : shareholderVec) + { + const m256i owner = ownerSharesPair.first; + const auto shareCount = ownerSharesPair.second; + EXPECT_EQ(pv.getVoteIndex(qpi, owner, proposalIdx), voterIdx); + for (unsigned int i = 0; i < shareCount; ++i) + { + EXPECT_EQ(pv.getVoterId(qpi, voterIdx, proposalIdx), owner); + EXPECT_EQ(pv.getVoteCount(qpi, voterIdx, proposalIdx), shareCount - i); + ++voterIdx; + } + } + EXPECT_EQ(voterIdx, NUMBER_OF_COMPUTORS); + EXPECT_EQ(pv.getVoteIndex(qpi, NULL_ID, proposalIdx), INVALID_VOTE_INDEX); + EXPECT_EQ(pv.getVoteIndex(qpi, id(12345678, 901234, 5678, 90), proposalIdx), INVALID_VOTE_INDEX); + EXPECT_EQ(pv.getVoterId(qpi, voterIdx, proposalIdx), NULL_ID); + EXPECT_EQ(pv.getVoteCount(qpi, voterIdx, proposalIdx), 0); +} + +TEST(TestCoreQPI, ProposalAndVotingByShareholders) +{ + ContractTesting test; + test.initEmptyUniverse(); + QpiContextUserProcedureCall qpi(QX_CONTRACT_INDEX, QPI::id(1, 2, 3, 4), 123); + ProposalAndVotingByShareholders<3, MSVAULT_ASSET_NAME> pv; + initComputors(0); + + // Memory must be zeroed to work, which is done in contract states on init + setMemory(pv, 0); + + // create contract shares + std::vector> initialPossessorShares{ + {id(100, 2, 3, 4), 10}, + {id(1, 2, 3, 4), 200}, + {id(1, 2, 2, 1), 65}, + {id(1, 2, 3, 1), 1}, + {id(0, 0, 0, 1), 400}, + }; + issueContractShares(MSVAULT_CONTRACT_INDEX, initialPossessorShares); + sortContractShareVector(initialPossessorShares); + + // no existing proposals + for (int i = 0; i < initialPossessorShares.size(); ++i) + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[i].first), (int)INVALID_PROPOSAL_INDEX); + + // valid proposers are current shareholders + for (int i = 0; i < initialPossessorShares.size(); ++i) + EXPECT_TRUE(pv.isValidProposer(qpi, initialPossessorShares[i].first)); + for (int i = 0; i < 2 * NUMBER_OF_COMPUTORS; ++i) + { + EXPECT_FALSE(pv.isValidProposer(qpi, QPI::id(i, 9, 8, 7))); + EXPECT_EQ(pv.getProposerId(qpi, i), NULL_ID); + } + EXPECT_FALSE(pv.isValidProposer(qpi, QPI::NULL_ID)); + + // add proposal + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[0].first), 0); + for (int i = 0; i < initialPossessorShares.size(); ++i) + { + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[i].first), (i == 0) ? 0 : (int)INVALID_PROPOSAL_INDEX); + EXPECT_EQ(pv.getProposerId(qpi, i), (i == 0) ? initialPossessorShares[i].first : NULL_ID); + } + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); + + // check voting rights (voters are shareholders at time of creating/updating proposal) + checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); + + // transfer some shares + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(20, 2, 3, 5)), 199); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(30, 2, 3, 5)), 198); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(40, 2, 3, 5)), 197); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(50, 2, 3, 5)), 196); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 16, id(1, 2, 3, 5)), 180); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 60, id(1, 2, 3, 1)), 120); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 39, id(1, 2, 3, 0)), 81); + EXPECT_EQ(qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 1, id(1, 2, 3, 2)), 80); + std::vector> changedPossessorShares{ + {id(0, 0, 0, 1), 400}, + {id(1, 2, 2, 1), 65}, + {id(1, 2, 3, 0), 39}, + {id(1, 2, 3, 1), 61}, + {id(1, 2, 3, 2), 1}, + {id(1, 2, 3, 4), 80}, + {id(1, 2, 3, 5), 16}, + {id(20, 2, 3, 5), 1}, + {id(30, 2, 3, 5), 1}, + {id(40, 2, 3, 5), 1}, + {id(50, 2, 3, 5), 1}, + {id(100, 2, 3, 4), 10}, + }; + + // assetsEndEpoch() and as.indexLists.rebuild(), which are called at the end of each epoch, lead to speeding up creating a new proposal (by requiring less sorting operations) + as.indexLists.rebuild(); + + // add another proposal (has changed set of voters) + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[1].first), 1); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), 1); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), (int)INVALID_PROPOSAL_INDEX); + + // check voting rights (voters are shareholders at time of creating/updating proposal) + checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); + checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares); + + // add third proposal (has changed set of voters) + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[2].first), 2); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), 1); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), 2); + + // check voting rights (voters are shareholders at time of creating/updating proposal) + checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); + checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares); + checkShareholderVotingRights(qpi, pv, 2, changedPossessorShares); + + // transfer some shares + qpi.transferShareOwnershipAndPossession(MSVAULT_ASSET_NAME, NULL_ID, qpi.invocator(), qpi.invocator(), 80, id(1, 2, 2, 0)); + std::vector> changedPossessorShares2{ + {id(0, 0, 0, 1), 400}, + {id(1, 2, 2, 0), 80}, + {id(1, 2, 2, 1), 65}, + {id(1, 2, 3, 0), 39}, + {id(1, 2, 3, 1), 61}, + {id(1, 2, 3, 2), 1}, + //{id(1, 2, 3, 4), 0}, + {id(1, 2, 3, 5), 16}, + {id(20, 2, 3, 5), 1}, + {id(30, 2, 3, 5), 1}, + {id(40, 2, 3, 5), 1}, + {id(50, 2, 3, 5), 1}, + {id(100, 2, 3, 4), 10}, + }; + + // all slots filled -> adding proposal using other ID fails + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[3].first), (int)INVALID_PROPOSAL_INDEX); + + // free one slot + pv.freeProposalByIndex(qpi, 1); + + // using other ID now works + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[3].first), 1); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), (int)INVALID_PROPOSAL_INDEX); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), 2); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[3].first), 1); + + // check voting rights (voters are shareholders at time of creating/updating proposal) + checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); + checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares2); + checkShareholderVotingRights(qpi, pv, 2, changedPossessorShares); + + // reusing all slots should work (overwriting proposals sets voters) + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[0].first), 0); + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[1].first), (int)INVALID_PROPOSAL_INDEX); + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[2].first), 2); + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[3].first), 1); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[0].first), 0); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[1].first), (int)INVALID_PROPOSAL_INDEX); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[2].first), 2); + EXPECT_EQ((int)pv.getExistingProposalIndex(qpi, initialPossessorShares[3].first), 1); + + // check voting rights (voters are shareholders at time of creating/updating proposal) + checkShareholderVotingRights(qpi, pv, 0, changedPossessorShares2); + checkShareholderVotingRights(qpi, pv, 1, changedPossessorShares2); + checkShareholderVotingRights(qpi, pv, 2, changedPossessorShares2); +} + +TEST(TestCoreQPI, ProposalAndVotingByShareholdersTestSorting) +{ + constexpr int shareholderFactor = 512; // set 2 to run more tests + constexpr int shareholdersWithTwoRecordsStep = 5; // set 1 to run more tests + + static constexpr uint64 MSVAULT_ASSET_NAME = 23727827095802701; + for (int shareholders = 1; shareholders <= 512; shareholders *= shareholderFactor) + { + for (int shareholdersWithTwoRecords = min(shareholders, 4); shareholdersWithTwoRecords > 0; shareholdersWithTwoRecords -= shareholdersWithTwoRecordsStep) + { + ContractTesting test; + test.initEmptyUniverse(); + QpiContextUserProcedureCall qpi(QX_CONTRACT_INDEX, QPI::id(1, 2, 3, 4), 123); + ProposalAndVotingByShareholders<3, MSVAULT_ASSET_NAME> pv; + initComputors(0); + + // Memory must be zeroed to work, which is done in contract states on init + setMemory(pv, 0); + + // create contract shares + std::vector> initialPossessorShares; + std::vector twoRecords; + for (int i = 0; i < shareholders; ++i) + { + initialPossessorShares.push_back({ id::randomValue(), 1 }); + } + for (int i = 0; i < shareholdersWithTwoRecords; ++i) + { + // randomly select entries that shall have two records + int idx = (int)initialPossessorShares[i].first.u64._0 % initialPossessorShares.size(); + initialPossessorShares[idx].second = 2; + twoRecords.push_back(idx); + } + issueContractShares(MSVAULT_CONTRACT_INDEX, initialPossessorShares, false); + + // transfer assset management rights in order to create two records + Asset asset{ NULL_ID, MSVAULT_ASSET_NAME }; + for (int i = 0; i < shareholdersWithTwoRecords; ++i) + { + const id& possessorId = initialPossessorShares[twoRecords[i]].first; + AssetPossessionIterator it(asset, { possessorId, QX_CONTRACT_INDEX }, { possessorId, QX_CONTRACT_INDEX }); + EXPECT_TRUE(transferShareManagementRights(it.ownershipIndex(), it.possessionIndex(), MSVAULT_CONTRACT_INDEX, MSVAULT_CONTRACT_INDEX, 1, nullptr, nullptr, true)); + } + + // add proposal + EXPECT_EQ((int)pv.getNewProposalIndex(qpi, initialPossessorShares[0].first), 0); + + // check voting rights (voters are shareholders at time of creating/updating proposal) + sortContractShareVector(initialPossessorShares); + checkShareholderVotingRights(qpi, pv, 0, initialPossessorShares); + } + } +} + +// Test internal class ProposalWithAllVoteData that stores valid proposals along with its votes +template +void testProposalWithAllVoteDataOptionVotes( + QPI::ProposalWithAllVoteData& pwav, + const ProposalT& proposal, + QPI::sint64 numOptions +) +{ + ASSERT_TRUE(numOptions >= 2); + ASSERT_TRUE(proposal.checkValidity()); + ASSERT_TRUE(QPI::ProposalTypes::isValid(proposal.type)); + if (proposal.type == QPI::ProposalTypes::VariableScalarMean) + ASSERT_TRUE(QPI::ProposalTypes::optionCount(proposal.type) == 0); + else + ASSERT_TRUE(QPI::ProposalTypes::optionCount(proposal.type) == numOptions); + + // set proposal / clear votes + EXPECT_TRUE(pwav.set(proposal)); + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_EQ(pwav.getVoteValue(i), QPI::NO_VOTE_VALUE); + + // test that out-of-range voter indices cannot be set + EXPECT_FALSE(pwav.setVoteValue(numVoters, 0)); + EXPECT_FALSE(pwav.setVoteValue(numVoters, QPI::NO_VOTE_VALUE)); + EXPECT_FALSE(pwav.setVoteValue(numVoters+1, 123)); + + // test that out-of-range vote values cannot be set + EXPECT_FALSE(pwav.setVoteValue(0, -123456)); + EXPECT_FALSE(pwav.setVoteValue(0, -1)); + EXPECT_FALSE(pwav.setVoteValue(0, numOptions)); + EXPECT_FALSE(pwav.setVoteValue(0, 123456)); + + // set and check valid vote range + for (QPI::uint32 j = 0; j < numOptions; ++j) + { + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_TRUE(pwav.setVoteValue(i, (j + i) % numOptions)); + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_EQ(pwav.getVoteValue(i), (j + i) % numOptions); + + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_TRUE(pwav.setVoteValue(i, (j + numVoters - i) % numOptions)); + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_EQ(pwav.getVoteValue(i), (j + numVoters - i) % numOptions); + } + + // clear vote + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_TRUE(pwav.setVoteValue(i, QPI::NO_VOTE_VALUE)); + for (QPI::uint32 i = 0; i < numVoters; ++i) + EXPECT_EQ(pwav.getVoteValue(i), QPI::NO_VOTE_VALUE); +} + +// Test internal class ProposalWithAllVoteData that stores valid proposals along with its votes +template +void testProposalWithAllVoteData() +{ + typedef QPI::ProposalDataV1 ProposalT; + QPI::ProposalWithAllVoteData pwav; + ProposalT proposal; + + // YesNo proposal + proposal.type = QPI::ProposalTypes::YesNo; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // ThreeOption proposal + proposal.type = QPI::ProposalTypes::ThreeOptions; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); + + // Proposal with 8 options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 8); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 8); + + // TransferYesNo proposal + proposal.type = QPI::ProposalTypes::TransferYesNo; + proposal.data.transfer.destination = QPI::id(1, 2, 3, 4); + proposal.data.transfer.amounts.setAll(0); + proposal.data.transfer.amounts.set(0, 1234); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // TransferTwoAmounts + proposal.type = QPI::ProposalTypes::TransferTwoAmounts; + proposal.data.transfer.amounts.set(1, 12345); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); + + // TransferThreeAmounts + proposal.type = QPI::ProposalTypes::TransferThreeAmounts; + proposal.data.transfer.amounts.set(2, 123456); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 4); + + // TransferFourAmounts + proposal.type = QPI::ProposalTypes::TransferFourAmounts; + proposal.data.transfer.amounts.set(3, 1234567); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 5); + + // TransferInEpochYesNo proposal + proposal.type = QPI::ProposalTypes::TransferInEpochYesNo; + proposal.data.transferInEpoch.destination = QPI::id(1, 2, 3, 4); + proposal.data.transferInEpoch.amount = 10; + proposal.data.transferInEpoch.targetEpoch = 123; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // fail: test TransferInEpoch proposal with too many or too few options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::TransferInEpoch, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::TransferInEpoch, 3); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + + // VariableYesNo proposal + proposal.type = QPI::ProposalTypes::VariableYesNo; + proposal.data.variableOptions.variable = 42; + proposal.data.variableOptions.values.set(0, 987); + proposal.data.variableOptions.values.setRange(1, proposal.data.variableOptions.values.capacity(), 0); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // VariableTwoValues proposal + proposal.type = QPI::ProposalTypes::VariableTwoValues; + proposal.data.variableOptions.values.set(1, 9876); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); + + // VariableThreeValues proposal + proposal.type = QPI::ProposalTypes::VariableThreeValues; + proposal.data.variableOptions.values.set(2, 98765); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 4); + + // VariableFourValues proposal + proposal.type = QPI::ProposalTypes::VariableFourValues; + proposal.data.variableOptions.values.set(3, 987654); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 5); + + // fail: test variable proposal with too many or too few options (0 options means scalar) + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 6); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + + // fail: wrong sorting with class Variable + proposal.type = QPI::ProposalTypes::VariableFourValues; + for (int i = 0; i < 4; ++i) + proposal.data.variableOptions.values.set(i, 20 - i); + EXPECT_FALSE(proposal.checkValidity()); + + // VariableScalarMean proposal + proposal.type = QPI::ProposalTypes::VariableScalarMean; + proposal.data.variableScalar.variable = 42; + proposal.data.variableScalar.minValue = 0; + proposal.data.variableScalar.maxValue = 25; + proposal.data.variableScalar.proposedValue = 1; + if (supportScalarVotes) + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 26); + else + EXPECT_FALSE(pwav.set(proposal)); + + // MultiVariablesYesNo proposal + proposal.type = QPI::ProposalTypes::MultiVariablesYesNo; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // MultiVariablesThreeOptions proposal + proposal.type = QPI::ProposalTypes::MultiVariablesThreeOptions; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); + + // MultiVariablesFourOptions proposal + proposal.type = QPI::ProposalTypes::MultiVariablesFourOptions; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 4); + + // MultiVariables proposal with 8 options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::MultiVariables, 8); + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 8); + + // fail: test MultiVariables proposal with too many or too few options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::MultiVariables, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::MultiVariables, 9); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); +} + +TEST(TestCoreQPI, ProposalWithAllVoteDataWithScalarVoteSupport) +{ + testProposalWithAllVoteData(); +} + +TEST(TestCoreQPI, ProposalWithAllVoteDataWithoutScalarVoteSupport) +{ + testProposalWithAllVoteData(); +} + +TEST(TestCoreQPI, ProposalWithAllVoteDataYesNoProposals) +{ + // Using ProposalDataYesNo saves storage space by only supporting yes/no choices + // (or up to 3 options for proposal classes that don't store option values) + ContractExecInitDeinitGuard initDeinitGuard; + typedef QPI::ProposalDataYesNo ProposalT; + QPI::ProposalWithAllVoteData pwav; + ProposalT proposal; + + // YesNo proposal + proposal.type = QPI::ProposalTypes::YesNo; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // ThreeOption proposal (accepted for general proposal, because it does not cost anything) + proposal.type = QPI::ProposalTypes::ThreeOptions; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); + + // Proposal with 4 options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 4); + EXPECT_FALSE(proposal.checkValidity()); + + // Proposal with 8 options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 8); + EXPECT_FALSE(proposal.checkValidity()); + + // TransferYesNo proposal + proposal.type = QPI::ProposalTypes::TransferYesNo; + proposal.data.transfer.destination = QPI::id(1, 2, 3, 4); + proposal.data.transfer.amount = 1234; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // TransferTwoAmounts + proposal.type = QPI::ProposalTypes::TransferTwoAmounts; + EXPECT_FALSE(proposal.checkValidity()); + + // TransferThreeAmounts + proposal.type = QPI::ProposalTypes::TransferThreeAmounts; + EXPECT_FALSE(proposal.checkValidity()); + + // fail: TransferInEpochYesNo proposal not supported due to lack of storage + proposal.type = QPI::ProposalTypes::TransferInEpochYesNo; + EXPECT_FALSE(proposal.checkValidity()); + + // VariableYesNo proposal + proposal.type = QPI::ProposalTypes::VariableYesNo; + proposal.data.variableOptions.variable = 42; + proposal.data.variableOptions.value = 987; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // VariableTwoValues proposal + proposal.type = QPI::ProposalTypes::VariableTwoValues; + EXPECT_FALSE(proposal.checkValidity()); + + // VariableThreeValues proposal + proposal.type = QPI::ProposalTypes::VariableThreeValues; + EXPECT_FALSE(proposal.checkValidity()); + + // fail: test variable proposal with too many or too few options (0 options means scalar) + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Variable, 6); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + EXPECT_FALSE(proposal.checkValidity()); + + // VariableScalarMean proposal + proposal.type = QPI::ProposalTypes::VariableScalarMean; + EXPECT_FALSE(proposal.checkValidity()); + + // MultiVariablesYesNo proposal + proposal.type = QPI::ProposalTypes::MultiVariablesYesNo; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 2); + + // MultiVariablesThreeOptions proposal (accepted for multiple variables proposal, because it does not cost anything) + proposal.type = QPI::ProposalTypes::MultiVariablesThreeOptions; + testProposalWithAllVoteDataOptionVotes(pwav, proposal, 3); + + // MultiVariablesFourOptions proposal + proposal.type = QPI::ProposalTypes::MultiVariablesFourOptions; + EXPECT_FALSE(proposal.checkValidity()); +} + +template +void expectNoVotes( + const QPI::QpiContextFunctionCall& qpi, + const ProposalVotingType& pv, + QPI::uint16 proposalIndex +) +{ + QPI::ProposalSingleVoteDataV1 vote; + for (QPI::uint32 i = 0; i < pv->maxVotes; ++i) + { + EXPECT_TRUE(qpi(*pv).getVote(proposalIndex, i, vote)); + EXPECT_EQ(vote.voteValue, QPI::NO_VOTE_VALUE); + } + + QPI::ProposalSummarizedVotingDataV1 votingSummaryReturned; + EXPECT_TRUE(qpi(*pv).getVotingSummary(proposalIndex, votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 0); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); +} + +template +bool isReturnedProposalAsExpected( + const QPI::QpiContextFunctionCall& qpi, + const QPI::ProposalDataV1& proposalReturnedByGet, const QPI::ProposalDataV1& proposalSet) +{ + bool expected = + proposalReturnedByGet.tick == qpi.tick() + && proposalReturnedByGet.epoch == qpi.epoch() + && proposalReturnedByGet.type == proposalSet.type + && proposalReturnedByGet.supportScalarVotes == proposalSet.supportScalarVotes + && (memcmp(&proposalReturnedByGet.data.transfer, &proposalSet.data.transfer, sizeof(proposalSet.data.transfer)) == 0) + && (memcmp(&proposalReturnedByGet.data.variableOptions, &proposalSet.data.variableOptions, sizeof(proposalSet.data.variableOptions)) == 0) + && (memcmp(&proposalReturnedByGet.data.variableScalar, &proposalSet.data.variableScalar, sizeof(proposalSet.data.variableScalar)) == 0) + && (memcmp(&proposalReturnedByGet.url, &proposalSet.url, sizeof(proposalSet.url)) == 0); + return expected; +} + +bool operator==(const QPI::ProposalSingleVoteDataV1& p1, const QPI::ProposalSingleVoteDataV1& p2) +{ + return memcmp(&p1, &p2, sizeof(p1)) == 0; +} + +bool operator==(const QPI::ProposalMultiVoteDataV1& p1, const QPI::ProposalMultiVoteDataV1& p2) +{ + return memcmp(&p1, &p2, sizeof(p1)) == 0; +} + + +template +void setProposalWithSuccessCheck(const QPI::QpiContextProcedureCall& qpi, const ProposalVotingType& pv, const QPI::id& proposerId, const ProposalDataType& proposal) +{ + ProposalDataType proposalReturned; + EXPECT_NE((int)qpi(*pv).setProposal(proposerId, proposal), (int)QPI::INVALID_PROPOSAL_INDEX); + QPI::uint16 proposalIdx = qpi(*pv).proposalIndex(proposerId); + EXPECT_NE((int)proposalIdx, (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(qpi(*pv).proposerId(proposalIdx), proposerId); + EXPECT_TRUE(qpi(*pv).getProposal(proposalIdx, proposalReturned)); + EXPECT_TRUE(isReturnedProposalAsExpected(qpi, proposalReturned, proposal)); + expectNoVotes(qpi, pv, proposalIdx); +} + +template +void setProposalExpectFailure(const QPI::QpiContextProcedureCall& qpi, const ProposalVotingType& pv, const QPI::id& proposerId, const ProposalDataType& proposal) +{ + EXPECT_EQ((int)qpi(*pv).setProposal(proposerId, proposal), (int)QPI::INVALID_PROPOSAL_INDEX); +} + +template +void voteWithValidVoter( + const QPI::QpiContextProcedureCall& qpi, + ProposalVotingType& pv, + const QPI::id& voterId, + QPI::uint16 proposalIndex, + QPI::uint16 proposalType, + QPI::uint32 proposalTick, + QPI::sint64 voteValue +) +{ + QPI::uint32 voterIdx = qpi(pv).voteIndex(voterId); + EXPECT_NE(voterIdx, QPI::INVALID_VOTE_INDEX); + QPI::id voterIdReturned = qpi(pv).voterId(voterIdx); + EXPECT_EQ(voterIdReturned, voterId); + + QPI::ProposalSingleVoteDataV1 voteReturnedBefore; + bool oldVoteAvailable = qpi(pv).getVote(proposalIndex, voterIdx, voteReturnedBefore); + + QPI::ProposalSingleVoteDataV1 vote; + vote.proposalIndex = proposalIndex; + vote.proposalType = proposalType; + vote.proposalTick = proposalTick; + vote.voteValue = voteValue; + EXPECT_EQ(qpi(pv).vote(voterId, vote), successExpected); + + QPI::ProposalSingleVoteDataV1 voteReturned; + if (successExpected) + { + EXPECT_TRUE(qpi(pv).getVote(vote.proposalIndex, voterIdx, voteReturned)); + EXPECT_TRUE(vote == voteReturned); + + typename ProposalVotingType::ProposalDataType proposalReturned; + EXPECT_TRUE(qpi(pv).getProposal(vote.proposalIndex, proposalReturned)); + EXPECT_TRUE(proposalReturned.type == voteReturned.proposalType); + EXPECT_TRUE(proposalReturned.tick == voteReturned.proposalTick); + } + else if (oldVoteAvailable) + { + EXPECT_TRUE(qpi(pv).getVote(vote.proposalIndex, voterIdx, voteReturned)); + EXPECT_TRUE(voteReturnedBefore == voteReturned); + } +} + +template +void voteWithValidVoterMultiVote( + const QPI::QpiContextProcedureCall& qpi, + ProposalVotingType& pv, + const QPI::id& voterId, + QPI::uint16 proposalIndex, + QPI::uint16 proposalType, + QPI::uint32 proposalTick, + QPI::sint64 voteValue, + QPI::uint32 voteCount = 0, // for first voteValue, 0 means all + QPI::sint64 voteValue2 = 0, + QPI::uint32 voteCount2 = 0, + QPI::sint64 voteValue3 = 0, + QPI::uint32 voteCount3 = 0 +) +{ + QPI::uint32 voterIdx = qpi(pv).voteIndex(voterId); + EXPECT_NE(voterIdx, QPI::INVALID_VOTE_INDEX); + QPI::id voterIdReturned = qpi(pv).voterId(voterIdx); + EXPECT_EQ(voterIdReturned, voterId); + + QPI::ProposalMultiVoteDataV1 voteReturnedBefore; + bool oldVoteAvailable = qpi(pv).getVotes(proposalIndex, voterId, voteReturnedBefore); + + // set all votes of voter (1 in computor voting) with multi-data voting function + QPI::ProposalMultiVoteDataV1 vote; + vote.proposalIndex = proposalIndex; + vote.proposalType = proposalType; + vote.proposalTick = proposalTick; + vote.voteValues.setAll(0); + vote.voteCounts.setAll(0); + vote.voteValues.set(0, voteValue); + if (voteCount != 0) + { + vote.voteCounts.set(0, voteCount); + vote.voteValues.set(1, voteValue2); + vote.voteCounts.set(1, voteCount2); + vote.voteValues.set(2, voteValue3); + vote.voteCounts.set(2, voteCount3); + } + EXPECT_EQ(qpi(pv).vote(voterId, vote), successExpected); + + QPI::ProposalMultiVoteDataV1 voteReturned; + if (successExpected) + { + EXPECT_TRUE(qpi(pv).getVotes(vote.proposalIndex, voterId, voteReturned)); + EXPECT_EQ((int)vote.proposalIndex, (int)voteReturned.proposalIndex); + EXPECT_EQ((int)vote.proposalType, (int)voteReturned.proposalType); + EXPECT_EQ(vote.proposalTick, voteReturned.proposalTick); + int totalVoteCount = qpi(pv).voteCount(voterIdx, proposalIndex); + if (voteCount == 0) + { + for (int i = 0; i < voteReturned.voteCounts.capacity(); ++i) + { + if (voteReturned.voteValues.get(i) == voteValue) + { + EXPECT_EQ(voteReturned.voteCounts.get(i), totalVoteCount); + totalVoteCount = 0; // for duplicates (e.g. value 0), expect count 0 + } + else + { + EXPECT_EQ(voteReturned.voteCounts.get(i), 0); + } + } + } + else + { + std::set voteInputIdx = { 0, 1, 2 }; + for (int i = 0; i < voteReturned.voteCounts.capacity(); ++i) + { + if (voteReturned.voteCounts.get(i) != 0) + { + bool found = false; + for (int j = 0; j < 4; ++j) + { + if (vote.voteValues.get(j) == voteReturned.voteValues.get(i)) + { + EXPECT_EQ(vote.voteCounts.get(j), voteReturned.voteCounts.get(i)); + EXPECT_TRUE(voteInputIdx.contains(j)); + voteInputIdx.erase(j); + found = true; + break; + } + } + EXPECT_TRUE(found); + } + } + EXPECT_TRUE(voteInputIdx.empty() || voteCount3 == 0); + } + + typename ProposalVotingType::ProposalDataType proposalReturned; + EXPECT_TRUE(qpi(pv).getProposal(vote.proposalIndex, proposalReturned)); + EXPECT_TRUE(proposalReturned.type == voteReturned.proposalType); + EXPECT_TRUE(proposalReturned.tick == voteReturned.proposalTick); + } + else if (oldVoteAvailable) + { + EXPECT_TRUE(qpi(pv).getVotes(vote.proposalIndex, voterId, voteReturned)); + EXPECT_TRUE(voteReturnedBefore == voteReturned); + } +} + + +template +void voteWithInvalidVoter( + const QPI::QpiContextProcedureCall& qpi, + ProposalVotingType& pv, + const QPI::id& voterId, + QPI::uint16 proposalIndex, + QPI::uint16 proposalType, + QPI::uint32 proposalTick, + QPI::sint64 voteValue +) +{ + QPI::ProposalSingleVoteDataV1 vote; + vote.proposalIndex = proposalIndex; + vote.proposalType = proposalType; + vote.proposalTick = proposalTick; + vote.voteValue = voteValue; + EXPECT_FALSE(qpi(pv).vote(voterId, vote)); + + QPI::ProposalMultiVoteDataV1 vote2; + vote2.proposalIndex = proposalIndex; + vote2.proposalType = proposalType; + vote2.proposalTick = proposalTick; + vote2.voteValues.setAll(0); + vote2.voteValues.set(0, voteValue); + vote2.voteCounts.setAll(0); + EXPECT_FALSE(qpi(pv).vote(voterId, vote2)); +} + + +template +int countActiveProposals( + const QPI::QpiContextFunctionCall& qpi, + const ProposalVotingType& pv +) +{ + int activeProposals = 0; + QPI::sint32 idx = -1; + while ((idx = qpi(*pv).nextProposalIndex(idx)) >= 0) + ++activeProposals; + return activeProposals; +} + +template +int countFinishedProposals( + const QPI::QpiContextFunctionCall& qpi, + const ProposalVotingType& pv +) +{ + int finishedProposals = 0; + QPI::sint32 idx = -1; + while ((idx = qpi(*pv).nextFinishedProposalIndex(idx)) >= 0) + ++finishedProposals; + return finishedProposals; +} + +template +void testProposalVotingComputorsV1() +{ + ContractExecInitDeinitGuard initDeinitGuard; + + system.tick = 123456789; + system.epoch = 12345; + initComputors(0); + + typedef std::conditional< + proposalByComputorsOnly, + QPI::ProposalAndVotingByComputors<200>, // Allow less proposals than NUMBER_OF_COMPUTORS to check handling of full arrays + QPI::ProposalByAnyoneVotingByComputors<200> + >::type ProposerAndVoterHandling; + + QpiContextUserProcedureCall qpi(0, QPI::id(1,2,3,4), 123); + auto * pv = new QPI::ProposalVoting< + ProposerAndVoterHandling, + QPI::ProposalDataV1>; + + // Memory must be zeroed to work, which is done in contract states on init + QPI::setMemory(*pv, 0); + + // fail: get before proposals have been set + QPI::ProposalDataV1 proposalReturned; + QPI::ProposalSingleVoteDataV1 voteDataReturned; + QPI::ProposalSummarizedVotingDataV1 votingSummaryReturned; + for (int i = 0; i < pv->maxProposals; ++i) + { + proposalReturned.type = 42; // test that additional error indicator is set 0 + EXPECT_FALSE(qpi(*pv).getProposal(i, proposalReturned)); + EXPECT_EQ((int)proposalReturned.type, 0); + + voteDataReturned.proposalType = 42; // test that additional error indicator is set 0 + EXPECT_FALSE(qpi(*pv).getVote(i, 0, voteDataReturned)); + EXPECT_EQ((int)voteDataReturned.proposalType, 0); + + votingSummaryReturned.totalVotesAuthorized = 42; // test that additional error indicator is set 0 + EXPECT_FALSE(qpi(*pv).getVotingSummary(i, votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, 0); + } + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), -1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(123456), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(0), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(123456), -1); + + // fail: get with invalid proposal index + EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals, proposalReturned)); + EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals + 1, proposalReturned)); + EXPECT_FALSE(qpi(*pv).getVote(pv->maxProposals, 0, voteDataReturned)); + EXPECT_FALSE(qpi(*pv).getVote(pv->maxProposals + 1, 0, voteDataReturned)); + EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals, votingSummaryReturned)); + EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals + 1, votingSummaryReturned)); + + // fail: no proposals for given IDs and invalid input + EXPECT_EQ((int)qpi(*pv).proposalIndex(QPI::NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); // always equal + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(0)), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(qpi(*pv).proposerId(QPI::INVALID_PROPOSAL_INDEX), QPI::NULL_ID); // always equal + EXPECT_EQ(qpi(*pv).proposerId(pv->maxProposals), QPI::NULL_ID); // always equal + EXPECT_EQ(qpi(*pv).proposerId(0), QPI::NULL_ID); + EXPECT_EQ(qpi(*pv).proposerId(1), QPI::NULL_ID); + + // okay: voters are available independently of proposals (= computors) + for (int i = 0; i < pv->maxVotes; ++i) + { + EXPECT_EQ(qpi(*pv).voteIndex(qpi.computor(i)), i); + EXPECT_EQ(qpi(*pv).voteCount(i), 1); + EXPECT_EQ(qpi(*pv).voterId(i), qpi.computor(i)); + } + + // fail: IDs / indices of non-voters + EXPECT_EQ(qpi(*pv).voteIndex(qpi.originator()), QPI::INVALID_VOTE_INDEX); + EXPECT_EQ(qpi(*pv).voteIndex(QPI::NULL_ID), QPI::INVALID_VOTE_INDEX); + EXPECT_EQ(qpi(*pv).voteCount(QPI::INVALID_VOTE_INDEX), 0); + EXPECT_EQ(qpi(*pv).voteCount(1000), 0); + EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes), QPI::NULL_ID); + EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes + 1), QPI::NULL_ID); + + // okay: set proposal for computor 0 + QPI::ProposalDataV1 proposal; + proposal.url.set(0, 0); + proposal.epoch = qpi.epoch(); + proposal.type = QPI::ProposalTypes::YesNo; + setProposalWithSuccessCheck(qpi, pv, qpi.computor(0), proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(0)), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote although no proposal is available at proposal index + voteWithValidVoter(qpi, *pv, qpi.computor(0), 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithValidVoter(qpi, *pv, qpi.computor(0), 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + + // fail: vote with wrong type + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); + + // fail: vote with non-computor + voteWithInvalidVoter(qpi, *pv, qpi.originator(), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + + // fail: vote with invalid value + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); + + // fail: vote with wrong tick + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()-1, 0); + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()-1, 0); + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()+1, 0); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick()+1, 0); + + // okay: correct votes in proposalIndex 0 + expectNoVotes(qpi, pv, 0); + for (int i = 0; i < pv->maxVotes; ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); + voteWithValidVoter(qpi, *pv, qpi.computor(i), 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); + } + voteWithValidVoter(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + EXPECT_TRUE(qpi(*pv).getVotingSummary(0, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 0); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes - 1); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), pv->maxVotes / 2 - 1); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), pv->maxVotes / 2); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 1); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 1); + + if (proposalByComputorsOnly) + { + // fail: originator id(1,2,3,4) is no computor (see custom qpi.computor() above) + setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); + } + else + { + // okay if anyone is allowed to set proposal + setProposalWithSuccessCheck(qpi, pv, qpi.originator(), proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // clear proposal again + EXPECT_TRUE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.originator()))); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); + } + + // fail: invalid type (more options than supported) + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 9); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // fail: invalid type (less options than supported) + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 0); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // okay: set proposal for computor 2 / other ID (proposal index 1, first use) + QPI::id secondNonComputorId(12345, 6789, 987, 654); + QPI::id secondProposer = (proposalByComputorsOnly) ? qpi.computor(2) : secondNonComputorId; + proposal.type = QPI::ProposalTypes::FourOptions; + proposal.epoch = 1; // non-zero means current epoch + setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) + voteWithValidVoter(qpi, *pv, qpi.computor(0), 1, proposal.type, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), 1, proposal.type, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(1), 1, proposal.type, qpi.tick(), 4); + voteWithValidVoter(qpi, *pv, qpi.computor(1), 1, proposal.type, qpi.tick(), 4); + + // fail: vote with non-computor + voteWithInvalidVoter(qpi, *pv, qpi.originator(), 1, proposal.type, qpi.tick(), 0); + voteWithInvalidVoter(qpi, *pv, secondNonComputorId, 1, proposal.type, qpi.tick(), 0); + voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 1, proposal.type, qpi.tick(), 0); + + // okay: correct votes in proposalIndex 1 (first use) + expectNoVotes(qpi, pv, 1); + for (int i = 0; i < pv->maxVotes; ++i) + { + voteWithValidVoter(qpi, *pv, qpi.computor(i), 1, proposal.type, qpi.tick(), (i < 100) ? i % 4 : 3); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), 1, proposal.type, qpi.tick(), (i < 100) ? i % 4 : 3); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), 25); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), 25); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(2), 25); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(3), pv->maxVotes - 75); + for (int i = 4; i < votingSummaryReturned.optionVoteCount.capacity(); ++i) + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), 0); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 3); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 3); + + // fail: proposal of transfer with wrong address + proposal.type = QPI::ProposalTypes::TransferYesNo; + proposal.data.transfer.destination = QPI::NULL_ID; + proposal.data.transfer.amounts.setAll(0); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + // check that overwrite did not work + EXPECT_TRUE(qpi(*pv).getProposal(qpi(*pv).proposalIndex(secondProposer), proposalReturned)); + EXPECT_FALSE(isReturnedProposalAsExpected(qpi, proposalReturned, proposal)); + + // fail: proposal of transfer with too many or too few options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 0); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 6); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + + // fail: proposal of revenue distribution with invalid amount + proposal.type = QPI::ProposalTypes::TransferYesNo; + proposal.data.transfer.destination = qpi.originator(); + proposal.data.transfer.amounts.set(0, -123456); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + + // okay: revenue distribution, overwrite existing proposal of comp 2 (proposal index 1, reused) + proposal.data.transfer.destination = qpi.originator(); + proposal.data.transfer.amounts.set(0, 1005); + setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) + QPI::uint16 secondProposalIdx = qpi(*pv).proposalIndex(secondProposer); + voteWithValidVoter(qpi, *pv, qpi.computor(0), secondProposalIdx, proposal.type, qpi.tick(), -1); + voteWithValidVoter(qpi, *pv, qpi.computor(1), secondProposalIdx, proposal.type, qpi.tick(), 2); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), secondProposalIdx, proposal.type, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(1), secondProposalIdx, proposal.type, qpi.tick(), 2); + + // okay: correct votes in proposalIndex 1 (reused) + expectNoVotes(qpi, pv, 1); // checks that setProposal clears previous votes + for (int i = 0; i < pv->maxVotes; ++i) + { + voteWithValidVoter(qpi, *pv, qpi.computor(i), secondProposalIdx, proposal.type, qpi.tick(), i % 2); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), secondProposalIdx, proposal.type, qpi.tick(), i % 2); + } + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(3), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(5), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + voteWithValidVoter(qpi, *pv, qpi.computor(3), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + voteWithValidVoter(qpi, *pv, qpi.computor(5), secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes - 2); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), pv->maxVotes / 2); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), pv->maxVotes / 2 - 2); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 0); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 0); + + if (!supportScalarVotes) + { + // fail: scalar proposal not supported + proposal.type = QPI::ProposalTypes::VariableScalarMean; + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + } + else + { + // fail: scalar proposal with wrong min/max + proposal.type = QPI::ProposalTypes::VariableScalarMean; + proposal.data.variableScalar.proposedValue = 10; + proposal.data.variableScalar.minValue = 11; + proposal.data.variableScalar.maxValue = 20; + proposal.data.variableScalar.variable = 123; // not checked, full range usable + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + proposal.data.variableScalar.minValue = 0; + proposal.data.variableScalar.maxValue = 9; + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // fail: scalar proposal with full range is invalid, because NO_VOTE_VALUE is reserved for no vote + proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue - 1; + proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // okay: scalar proposal with nearly full range + proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue; + proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; + setProposalWithSuccessCheck(qpi, pv, qpi.computor(1), proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(1)), 2); + EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), (int)secondProposalIdx); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); + EXPECT_EQ(qpi(*pv).nextProposalIndex(2), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // okay: votes in proposalIndex of computor 1 for testing overflow-avoiding summary algorithm for average + expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(qpi.computor(1))); + for (int i = 0; i < 99; ++i) + { + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.maxSupportedValue - 2 + i % 3); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.maxSupportedValue - 2 + i % 3); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(qpi.computor(1)), votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, (int)qpi(*pv).proposalIndex(qpi.computor(1))); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 99); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), -1); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.maxSupportedValue - 1); + for (int i = 0; i < 555; ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.minSupportedValue + 10 - i % 5); + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(1)), proposal.type, qpi.tick(), proposal.data.variableScalar.minSupportedValue + 10 - i % 5); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(qpi.computor(1)), votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 555); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.minSupportedValue + 8); + + // okay: scalar proposal with limited range + proposal.data.variableScalar.minValue = -1000; + proposal.data.variableScalar.maxValue = 1000; + setProposalWithSuccessCheck(qpi, pv, qpi.computor(10), proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(10)), 3); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); + EXPECT_EQ(qpi(*pv).nextProposalIndex(2), 3); + EXPECT_EQ(qpi(*pv).nextProposalIndex(3), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote with invalid values + voteWithValidVoter(qpi, *pv, qpi.computor(0), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), -1001); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(0), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), -1001); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(1), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 1001); + voteWithValidVoter(qpi, *pv, qpi.computor(1), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 1001); + + // okay: correct votes in proposalIndex of computor 10 + expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(qpi.computor(10))); + for (int i = 0; i < 603; ++i) + { + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), (i % 201) - 100); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), (i % 201) - 100); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 603); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, 0); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), -1); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); + + // another case for scalar voting summary + for (int i = 0; i < 603; ++i) + { + voteWithValidVoterMultiVote (qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + } + for (int i = 0; i < 200; ++i) + { + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), i + 1); + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), i + 1); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 200); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, (200 * 201 / 2) / 200); + } + + // fail: test multi-option transfer proposal with invalid amounts + proposal.type = QPI::ProposalTypes::TransferThreeAmounts; + proposal.data.transfer.destination = qpi.originator(); + for (int i = 0; i < 4; ++i) + { + proposal.data.transfer.amounts.setAll(0); + proposal.data.transfer.amounts.set(i, -100 * i - 1); + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + } + proposal.data.transfer.amounts.set(0, 0); + proposal.data.transfer.amounts.set(1, 10); + proposal.data.transfer.amounts.set(2, 20); + proposal.data.transfer.amounts.set(3, 100); // for ProposalTypes::TransferThreeAmounts, fourth must be 0 + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // fail: duplicate options + proposal.data.transfer.amounts.setAll(0); + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // fail: options not sorted + for (int i = 0; i < 3; ++i) + proposal.data.transfer.amounts.set(i, 100 - i); + setProposalExpectFailure(qpi, pv, qpi.computor(1), proposal); + + // okay: fill proposal storage + proposal.data.transfer.amounts.setAll(0); + constexpr QPI::uint16 computorProposalToFillAll = (proposalByComputorsOnly) ? pv->maxProposals : pv->maxProposals - 1; + for (int i = 0; i < computorProposalToFillAll; ++i) + { + proposal.data.transfer.amounts.set(0, i); + proposal.data.transfer.amounts.set(1, i * 2 + 1); + proposal.data.transfer.amounts.set(2, i * 3 + 2); + setProposalWithSuccessCheck(qpi, pv, qpi.computor(i), proposal); + } + EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: no space left + setProposalExpectFailure(qpi, pv, qpi.computor(pv->maxProposals), proposal); + + // cast some votes before epoch change to test querying voting summary afterwards + for (int i = 0; i < 20; ++i) + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 0); + for (int i = 20; i < 60; ++i) + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 1); + for (int i = 60; i < 160; ++i) + voteWithValidVoter(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 2); + for (int i = 160; i < 360; ++i) + voteWithValidVoterMultiVote(qpi, *pv, qpi.computor(i), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 3); + + // simulate epoch change + ++system.epoch; + EXPECT_EQ(countActiveProposals(qpi, pv), 0); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals); + + // okay: same setProposal after epoch change, because the oldest proposal will be deleted + proposal.epoch = qpi.epoch(); + setProposalWithSuccessCheck(qpi, pv, qpi.computor(pv->maxProposals), proposal); + EXPECT_EQ(countActiveProposals(qpi, pv), 1); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 1); + + // fail: vote in wrong epoch + voteWithValidVoter(qpi, *pv, qpi.computor(123), qpi(*pv).proposalIndex(qpi.computor(10)), proposal.type, qpi.tick(), 0); + + // okay: query voting summary of other epoch + EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(qpi.computor(10)), votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 20+40+100+200); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), 20); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), 40); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(2), 100); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(3), 200); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(4), 0); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 3); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); + + // manually clear some proposals + EXPECT_FALSE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.originator()))); + EXPECT_TRUE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.computor(5)))); + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(5)), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(countActiveProposals(qpi, pv), 1); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); + proposal.epoch = 0; + setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); + EXPECT_NE((int)qpi(*pv).setProposal(qpi.computor(7), proposal), (int)QPI::INVALID_PROPOSAL_INDEX); // success + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.computor(7)), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(countActiveProposals(qpi, pv), 1); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 3); + + // simulate epoch change with changes in computors + ++system.epoch; + initComputors(100); + EXPECT_EQ(countActiveProposals(qpi, pv), 0); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); + + // set new proposals, adding new computor IDs (simulated next epoch) + proposal.epoch = qpi.epoch(); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 6); + for (int i = 0; i < pv->maxProposals; ++i) + setProposalWithSuccessCheck(qpi, pv, qpi.computor(i), proposal); + for (int i = 0; i < pv->maxProposals; ++i) + expectNoVotes(qpi, pv, i); + EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); + EXPECT_EQ(countFinishedProposals(qpi, pv), 0); + + delete pv; +} + +TEST(TestCoreQPI, ProposalVotingV1proposalOnlyByComputorWithScalarVoteSupport) +{ + testProposalVotingComputorsV1(); +} + +TEST(TestCoreQPI, ProposalVotingV1proposalOnlyByComputorWithoutScalarVoteSupport) +{ + testProposalVotingComputorsV1(); +} + +TEST(TestCoreQPI, ProposalVotingV1proposalByAnyoneWithScalarVoteSupport) +{ + testProposalVotingComputorsV1(); +} + +TEST(TestCoreQPI, ProposalVotingV1proposalByAnyoneWithoutScalarVoteSupport) +{ + testProposalVotingComputorsV1(); +} + +// TODO: ProposalVoting YesNo + +template +void testProposalVotingShareholdersV1() +{ + ContractTesting test; + test.initEmptyUniverse(); + + system.tick = 123456789; + system.epoch = 12345; + initComputors(0); + + typedef QPI::ProposalAndVotingByShareholders<6, MSVAULT_ASSET_NAME> ProposerAndVoterHandling; + + QpiContextUserProcedureCall qpi(0, QPI::id(1, 2, 3, 4), 123); + auto* pv = new QPI::ProposalVoting< + ProposerAndVoterHandling, + QPI::ProposalDataV1>; + + // Memory must be zeroed to work, which is done in contract states on init + QPI::setMemory(*pv, 0); + + std::vector> shareholderShares{ + {id(100, 20, 3, 4), 200}, + {id(0, 0, 0, 3), 100}, + {id(0, 0, 0, 2), 250}, + {id(0, 0, 0, 1), 50}, + {id(10, 20, 3, 4), 10}, + {id(10, 20, 2, 1), 54}, + {id(10, 20, 3, 1), 12}, + }; + issueContractShares(MSVAULT_CONTRACT_INDEX, shareholderShares); + sortContractShareVector(shareholderShares); + + // fail: get before proposals have been set + QPI::ProposalDataV1 proposalReturned; + QPI::ProposalMultiVoteDataV1 voteDataReturned; + QPI::ProposalSummarizedVotingDataV1 votingSummaryReturned; + for (int i = 0; i < pv->maxProposals; ++i) + { + proposalReturned.type = 42; // test that additional error indicator is set 0 + EXPECT_FALSE(qpi(*pv).getProposal(i, proposalReturned)); + EXPECT_EQ((int)proposalReturned.type, 0); + + voteDataReturned.proposalType = 42; // test that additional error indicator is set 0 + EXPECT_FALSE(qpi(*pv).getVotes(i, shareholderShares[0].first, voteDataReturned)); + EXPECT_EQ((int)voteDataReturned.proposalType, 0); + + votingSummaryReturned.totalVotesAuthorized = 42; // test that additional error indicator is set 0 + EXPECT_FALSE(qpi(*pv).getVotingSummary(i, votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, 0); + } + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), -1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(123456), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(0), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(123456), -1); + + // fail: get with invalid proposal index + EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals, proposalReturned)); + EXPECT_FALSE(qpi(*pv).getProposal(pv->maxProposals + 1, proposalReturned)); + EXPECT_FALSE(qpi(*pv).getVotes(pv->maxProposals, shareholderShares[0].first, voteDataReturned)); + EXPECT_FALSE(qpi(*pv).getVotes(pv->maxProposals + 1, shareholderShares[0].first, voteDataReturned)); + EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals, votingSummaryReturned)); + EXPECT_FALSE(qpi(*pv).getVotingSummary(pv->maxProposals + 1, votingSummaryReturned)); + + // fail: no proposals for given IDs and invalid input + EXPECT_EQ((int)qpi(*pv).proposalIndex(QPI::NULL_ID), (int)QPI::INVALID_PROPOSAL_INDEX); // always equal + EXPECT_EQ((int)qpi(*pv).proposalIndex(qpi.originator()), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[0].first), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(qpi(*pv).proposerId(QPI::INVALID_PROPOSAL_INDEX), QPI::NULL_ID); // always equal + EXPECT_EQ(qpi(*pv).proposerId(pv->maxProposals), QPI::NULL_ID); // always equal + EXPECT_EQ(qpi(*pv).proposerId(0), QPI::NULL_ID); + EXPECT_EQ(qpi(*pv).proposerId(1), QPI::NULL_ID); + + // fail: IDs / indices of non-voters + EXPECT_EQ(qpi(*pv).voteIndex(qpi.originator()), QPI::INVALID_VOTE_INDEX); + EXPECT_EQ(qpi(*pv).voteIndex(QPI::NULL_ID), QPI::INVALID_VOTE_INDEX); + EXPECT_EQ(qpi(*pv).voteCount(QPI::INVALID_VOTE_INDEX), 0); + EXPECT_EQ(qpi(*pv).voteCount(1000), 0); + EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes), QPI::NULL_ID); + EXPECT_EQ(qpi(*pv).voterId(pv->maxVotes + 1), QPI::NULL_ID); + + // okay: set proposal for shareholder 0 + QPI::ProposalDataV1 proposal; + proposal.url.set(0, 0); + proposal.epoch = qpi.epoch(); + proposal.type = QPI::ProposalTypes::YesNo; + setProposalWithSuccessCheck(qpi, pv, shareholderShares[0].first, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[0].first), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // check that voters match shareholders + for (size_t i = 0; i < shareholderShares.size(); ++i) + { + uint32 voterIdx = qpi(*pv).voteIndex(shareholderShares[i].first); + EXPECT_NE(voterIdx, NO_VOTE_VALUE); + EXPECT_EQ(qpi(*pv).voteCount(voterIdx), shareholderShares[i].second); + EXPECT_EQ(qpi(*pv).voterId(voterIdx), shareholderShares[i].first); + } + + // fail: vote although no proposal is available at proposal index + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 1, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 12345, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + + // fail: vote with wrong type + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::TransferYesNo, qpi.tick(), 0); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::VariableScalarMean, qpi.tick(), 0); + + // fail: vote with non-computor + voteWithInvalidVoter(qpi, *pv, qpi.originator(), 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 0); + + // fail: vote with invalid value + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), 2); + + // fail: vote with wrong tick + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() - 1, 0); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() - 1, 0); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() + 1, 0); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick() + 1, 0); + + // okay: correct votes in proposalIndex 0 + expectNoVotes(qpi, pv, 0); + int optionProposalVoteCounts[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + for (size_t i = 0; i < shareholderShares.size(); ++i) + { + if (i % 3 != 0) + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); + else + voteWithValidVoter(qpi, *pv, shareholderShares[i].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), i % 2); + optionProposalVoteCounts[i % 2] += shareholderShares[i].second; + } + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 0, QPI::ProposalTypes::YesNo, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + EXPECT_TRUE(qpi(*pv).getVotingSummary(0, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 0); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes - shareholderShares[0].second); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), optionProposalVoteCounts[0] - shareholderShares[0].second); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), optionProposalVoteCounts[1]); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 1); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 1); + + // fail: originator id(1,2,3,4) is no shareholder (see custom qpi.computor() above) + setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); + + // fail: invalid type (more options than supported) + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 9); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // fail: invalid type (less options than supported) + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 0); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // okay: set proposal for computor 2 (proposal index 1, first use) + QPI::id secondProposer = shareholderShares[2].first; + proposal.type = QPI::ProposalTypes::FourOptions; + proposal.epoch = 1; // non-zero means current epoch + setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, 1, proposal.type, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, 1, proposal.type, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[1].first, 1, proposal.type, qpi.tick(), 4); + voteWithValidVoter(qpi, *pv, shareholderShares[1].first, 1, proposal.type, qpi.tick(), 4); + + // fail: vote with non-shareholder + voteWithInvalidVoter(qpi, *pv, qpi.originator(), 1, proposal.type, qpi.tick(), 0); + voteWithInvalidVoter(qpi, *pv, QPI::NULL_ID, 1, proposal.type, qpi.tick(), 0); + + // okay: cast votes in proposalIndex 1 (first use) + expectNoVotes(qpi, pv, 1); + for (int i = 0; i < 8; ++i) + optionProposalVoteCounts[i] = 0; + for (size_t i = 0; i < shareholderShares.size(); ++i) + { + int voteValue = i % 4; + optionProposalVoteCounts[voteValue] += shareholderShares[i].second; + if (i & 1) + voteWithValidVoter(qpi, *pv, shareholderShares[i].first, 1, proposal.type, qpi.tick(), voteValue); + else + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, 1, proposal.type, qpi.tick(), voteValue); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, pv->maxVotes); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); + for (int i = 0; i < 4; ++i) + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), optionProposalVoteCounts[i]); + for (int i = 4; i < votingSummaryReturned.optionVoteCount.capacity(); ++i) + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), 0); + + // fail: proposal of transfer with wrong address + proposal.type = QPI::ProposalTypes::TransferYesNo; + proposal.data.transfer.destination = QPI::NULL_ID; + proposal.data.transfer.amounts.setAll(0); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + // check that overwrite did not work + EXPECT_TRUE(qpi(*pv).getProposal(qpi(*pv).proposalIndex(secondProposer), proposalReturned)); + EXPECT_FALSE(isReturnedProposalAsExpected(qpi, proposalReturned, proposal)); + + // fail: proposal of transfer with too many or too few options + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 0); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 1); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::Transfer, 6); + EXPECT_FALSE(QPI::ProposalTypes::isValid(proposal.type)); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + + // fail: proposal of revenue distribution with invalid amount + proposal.type = QPI::ProposalTypes::TransferYesNo; + proposal.data.transfer.destination = qpi.originator(); + proposal.data.transfer.amounts.set(0, -123456); + setProposalExpectFailure(qpi, pv, secondProposer, proposal); + + // okay: revenue distribution, overwrite existing proposal of comp 2 (proposal index 1, reused) + proposal.data.transfer.destination = qpi.originator(); + proposal.data.transfer.amounts.set(0, 1005); + setProposalWithSuccessCheck(qpi, pv, secondProposer, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote with invalid values (for yes/no only the values 0 and 1 are valid) + QPI::uint16 secondProposalIdx = qpi(*pv).proposalIndex(secondProposer); + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, secondProposalIdx, proposal.type, qpi.tick(), -1); + voteWithValidVoter(qpi, *pv, shareholderShares[1].first, secondProposalIdx, proposal.type, qpi.tick(), 2); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, secondProposalIdx, proposal.type, qpi.tick(), -1); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[1].first, secondProposalIdx, proposal.type, qpi.tick(), 2); + + // okay: cast votes in proposalIndex 1 (reused) + expectNoVotes(qpi, pv, 1); // checks that setProposal clears previous votes + optionProposalVoteCounts[0] = 0; + optionProposalVoteCounts[1] = 0; + for (size_t i = 0; i < shareholderShares.size(); ++i) + { + int voteValue = i % 2; + optionProposalVoteCounts[voteValue] += shareholderShares[i].second; + if (i & 1) + voteWithValidVoter(qpi, *pv, shareholderShares[i].first, secondProposalIdx, proposal.type, qpi.tick(), voteValue); + else + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, secondProposalIdx, proposal.type, qpi.tick(), voteValue); + } + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[3].first, secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + optionProposalVoteCounts[1] -= shareholderShares[3].second; + voteWithValidVoter(qpi, *pv, shareholderShares[5].first, secondProposalIdx, proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + optionProposalVoteCounts[1] -= shareholderShares[5].second; + EXPECT_TRUE(qpi(*pv).getVotingSummary(1, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 1); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, optionProposalVoteCounts[0] + optionProposalVoteCounts[1]); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 2); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(0), optionProposalVoteCounts[0]); + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(1), optionProposalVoteCounts[1]); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 0); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), 0); + + if (!supportScalarVotes) + { + // fail: scalar proposal not supported + proposal.type = QPI::ProposalTypes::VariableScalarMean; + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + } + else + { + // fail: scalar proposal with wrong min/max + proposal.type = QPI::ProposalTypes::VariableScalarMean; + proposal.data.variableScalar.proposedValue = 10; + proposal.data.variableScalar.minValue = 11; + proposal.data.variableScalar.maxValue = 20; + proposal.data.variableScalar.variable = 123; // not checked, full range usable + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + proposal.data.variableScalar.minValue = 0; + proposal.data.variableScalar.maxValue = 9; + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // fail: scalar proposal with full range is invalid, because NO_VOTE_VALUE is reserved for no vote + proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue - 1; + proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // okay: scalar proposal with nearly full range + proposal.data.variableScalar.minValue = proposal.data.variableScalar.minSupportedValue; + proposal.data.variableScalar.maxValue = proposal.data.variableScalar.maxSupportedValue; + setProposalWithSuccessCheck(qpi, pv, shareholderShares[1].first, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[1].first), 2); + EXPECT_EQ((int)qpi(*pv).proposalIndex(secondProposer), (int)secondProposalIdx); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); + EXPECT_EQ(qpi(*pv).nextProposalIndex(2), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // okay: votes in proposalIndex for testing overflow-avoiding summary algorithm for average + expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(shareholderShares[1].first)); + for (int i = 0; i < 3; ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[1].first), + proposal.type, qpi.tick(), + proposal.data.variableScalar.maxSupportedValue - 2 + i % 3, 5, + proposal.data.variableScalar.maxSupportedValue - 2 + (i + 1) % 3, 3, + proposal.data.variableScalar.maxSupportedValue - 2 + (i + 2) % 3, 2); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(shareholderShares[1].first), votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, (int)qpi(*pv).proposalIndex(shareholderShares[1].first)); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 30); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.maxSupportedValue - 1); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), -1); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); + + for (int i = 0; i < 5; ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[1].first), + proposal.type, qpi.tick(), + proposal.data.variableScalar.minSupportedValue + 4 - i % 5, 4, + proposal.data.variableScalar.minSupportedValue + 12 - i % 5, 2); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(shareholderShares[1].first), votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 30); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, proposal.data.variableScalar.minSupportedValue + 2 + 3); + + // okay: scalar proposal with limited range + proposal.data.variableScalar.minValue = -1000; + proposal.data.variableScalar.maxValue = 1000; + setProposalWithSuccessCheck(qpi, pv, shareholderShares[5].first, proposal); + EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[5].first), 3); + EXPECT_EQ(qpi(*pv).nextProposalIndex(-1), 0); + EXPECT_EQ(qpi(*pv).nextProposalIndex(0), 1); + EXPECT_EQ(qpi(*pv).nextProposalIndex(1), 2); + EXPECT_EQ(qpi(*pv).nextProposalIndex(2), 3); + EXPECT_EQ(qpi(*pv).nextProposalIndex(3), -1); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: vote with invalid values + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), -1001); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[0].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), -1001); + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[1].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), 1001); + voteWithValidVoter(qpi, *pv, shareholderShares[1].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), 1001); + + // okay: cast votes in proposalIndex of shareholder 5 + expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(shareholderShares[5].first)); + for (int i = 0; i < (int)shareholderShares.size(); ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), + proposal.type, qpi.tick(), + (i + 1) * 5, 6, + (i + 1) * -10, 3, + 0, shareholderShares[i].second - 9); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, 676); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, 0); + + // another case for scalar voting summary + for (size_t i = 0; i < shareholderShares.size(); ++i) + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), QPI::NO_VOTE_VALUE); // remove vote + expectNoVotes(qpi, pv, qpi(*pv).proposalIndex(shareholderShares[5].first)); + for (size_t i = 0; i < shareholderShares.size(); ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), + proposal.type, qpi.tick(), + i * 3, 2, + i * 3 + 1, 3, + i * 3 + 2, 2); + } + EXPECT_TRUE(qpi(*pv).getVotingSummary(3, votingSummaryReturned)); + EXPECT_EQ((int)votingSummaryReturned.proposalIndex, 3); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, shareholderShares.size() * 7); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 0); + EXPECT_EQ(votingSummaryReturned.scalarVotingResult, (shareholderShares.size() / 2) * 3 + 1); + } + + // fail: test multi-option transfer proposal with invalid amounts + proposal.type = QPI::ProposalTypes::TransferThreeAmounts; + proposal.data.transfer.destination = qpi.originator(); + for (int i = 0; i < 4; ++i) + { + proposal.data.transfer.amounts.setAll(0); + proposal.data.transfer.amounts.set(i, -100 * i - 1); + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + } + proposal.data.transfer.amounts.set(0, 0); + proposal.data.transfer.amounts.set(1, 10); + proposal.data.transfer.amounts.set(2, 20); + proposal.data.transfer.amounts.set(3, 100); // for ProposalTypes::TransferThreeAmounts, fourth must be 0 + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // fail: duplicate options + proposal.data.transfer.amounts.setAll(0); + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // fail: options not sorted + for (int i = 0; i < 3; ++i) + proposal.data.transfer.amounts.set(i, 100 - i); + setProposalExpectFailure(qpi, pv, shareholderShares[1].first, proposal); + + // okay: fill proposal storage + proposal.data.transfer.amounts.setAll(0); + ASSERT_EQ((int)pv->maxProposals, (int)shareholderShares.size() - 1); + for (int i = 0; i < pv->maxProposals; ++i) + { + proposal.data.transfer.amounts.set(0, i); + proposal.data.transfer.amounts.set(1, i * 2 + 1); + proposal.data.transfer.amounts.set(2, i * 3 + 2); + setProposalWithSuccessCheck(qpi, pv, shareholderShares[i].first, proposal); + } + EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); + EXPECT_EQ(qpi(*pv).nextFinishedProposalIndex(-1), -1); + + // fail: no space left + setProposalExpectFailure(qpi, pv, shareholderShares[pv->maxProposals].first, proposal); + + // cast some votes before epoch change to test querying voting summary afterwards + for (int i = 0; i < 8; ++i) + optionProposalVoteCounts[i] = 0; + for (size_t i = 0; i < shareholderShares.size(); ++i) + { + voteWithValidVoterMultiVote(qpi, *pv, shareholderShares[i].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), + i & 1, shareholderShares[i].second / 2, + 2, shareholderShares[i].second / 8, + 3, shareholderShares[i].second / 4); + optionProposalVoteCounts[i & 1] += shareholderShares[i].second / 2; + optionProposalVoteCounts[2] += shareholderShares[i].second / 8; + optionProposalVoteCounts[3] += shareholderShares[i].second / 4; + } + + // simulate epoch change + ++system.epoch; + EXPECT_EQ(countActiveProposals(qpi, pv), 0); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals); + + // okay: same setProposal after epoch change, because the oldest proposal will be deleted + proposal.epoch = qpi.epoch(); + setProposalWithSuccessCheck(qpi, pv, shareholderShares[pv->maxProposals].first, proposal); + EXPECT_EQ(countActiveProposals(qpi, pv), 1); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 1); + + // fail: vote in wrong epoch + voteWithValidVoter(qpi, *pv, shareholderShares[0].first, qpi(*pv).proposalIndex(shareholderShares[5].first), proposal.type, qpi.tick(), 0); + + // okay: query voting summary of other epoch + EXPECT_TRUE(qpi(*pv).getVotingSummary(qpi(*pv).proposalIndex(shareholderShares[5].first), votingSummaryReturned)); + EXPECT_EQ(votingSummaryReturned.totalVotesAuthorized, pv->maxVotes); + uint32 voteCountSum = 0; + for (int i = 0; i < 4; ++i) + voteCountSum += optionProposalVoteCounts[i]; + EXPECT_EQ(votingSummaryReturned.totalVotesCasted, voteCountSum); + EXPECT_EQ((int)votingSummaryReturned.optionCount, 4); + for (int i = 0; i < 8; ++i) + EXPECT_EQ(votingSummaryReturned.optionVoteCount.get(i), (i < 4) ? optionProposalVoteCounts[i] : 0); + EXPECT_EQ(votingSummaryReturned.getMostVotedOption(), 0); + EXPECT_EQ(votingSummaryReturned.getAcceptedOption(), -1); + + // manually clear some proposals + EXPECT_FALSE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(qpi.originator()))); + EXPECT_TRUE(qpi(*pv).clearProposal(qpi(*pv).proposalIndex(shareholderShares[5].first))); + EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[5].first), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(countActiveProposals(qpi, pv), 1); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); + proposal.epoch = 0; + setProposalExpectFailure(qpi, pv, qpi.originator(), proposal); + EXPECT_NE((int)qpi(*pv).setProposal(shareholderShares[3].first, proposal), (int)QPI::INVALID_PROPOSAL_INDEX); // success (clear by epoch 0) + EXPECT_EQ((int)qpi(*pv).proposalIndex(shareholderShares[3].first), (int)QPI::INVALID_PROPOSAL_INDEX); + EXPECT_EQ(countActiveProposals(qpi, pv), 1); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 3); + + // simulate epoch change + ++system.epoch; + EXPECT_EQ(countActiveProposals(qpi, pv), 0); + EXPECT_EQ(countFinishedProposals(qpi, pv), (int)pv->maxProposals - 2); + + // change of shareholders + AssetPossessionIterator iter({ NULL_ID, MSVAULT_ASSET_NAME }); + int newPossessorCount = 0; + while (!iter.reachedEnd()) + { + if (iter.numberOfPossessedShares()) + { + int destinationOwnershipIndex, destinationPossessionIndex; + EXPECT_TRUE(transferShareOwnershipAndPossession(iter.ownershipIndex(), iter.possessionIndex(), qpi.computor(newPossessorCount), + iter.numberOfPossessedShares(), &destinationOwnershipIndex, &destinationPossessionIndex, true)); + ++newPossessorCount; + } + iter.next(); + } + EXPECT_GE(newPossessorCount, (int)pv->maxProposals); + + // set new proposals by new shareholders + proposal.epoch = qpi.epoch(); + proposal.type = QPI::ProposalTypes::type(QPI::ProposalTypes::Class::GeneralOptions, 6); + for (int i = 0; i < pv->maxProposals; ++i) + setProposalWithSuccessCheck(qpi, pv, qpi.computor(i), proposal); + for (int i = 0; i < pv->maxProposals; ++i) + expectNoVotes(qpi, pv, i); + EXPECT_EQ(countActiveProposals(qpi, pv), (int)pv->maxProposals); + EXPECT_EQ(countFinishedProposals(qpi, pv), 0); + + delete pv; +} + +TEST(TestCoreQPI, ProposalVotingV1shareholderWithScalarVoteSupport) +{ + testProposalVotingShareholdersV1(); +} + +TEST(TestCoreQPI, ProposalVotingV1shareholderWithoutScalarVoteSupport) +{ + testProposalVotingShareholdersV1(); +} diff --git a/test/qpi_collection.cpp b/test/qpi_collection.cpp new file mode 100644 index 000000000..5988bfd6c --- /dev/null +++ b/test/qpi_collection.cpp @@ -0,0 +1,1716 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#include "../src/contract_core/pre_qpi_def.h" +#include "../src/contracts/qpi.h" +#include "../src/common_buffers.h" +#include "../src/contract_core/qpi_collection_impl.h" +#include "../src/contract_core/qpi_trivial_impl.h" + + +#include +#include +#include +#include + +template +void checkPriorityQueue(const QPI::Collection& coll, const QPI::id& pov, bool print = false) +{ + if (print) + { + std::cout << "Priority queue ID(" << pov.u64._0 << ", " << pov.u64._1 << ", " + << pov.u64._2 << ", " << pov.u64._3 << ")" << std::endl; + } + bool first = true; + QPI::sint64 elementIndex = coll.headIndex(pov); + QPI::sint64 prevPriority; + QPI::sint64 prevElementIdx = QPI::NULL_INDEX; + int elementCount = 0; + while (elementIndex != QPI::NULL_INDEX) + { + if (print) + { + std::cout << "\tindex " << elementIndex << ", value " << coll.element(elementIndex) + << ", priority " << coll.priority(elementIndex) + << ", prev " << coll.prevElementIndex(elementIndex) + << ", next " << coll.nextElementIndex(elementIndex) << std::endl; + } + + if (!first) + { + EXPECT_LE(coll.priority(elementIndex), prevPriority); + } + EXPECT_EQ(coll.prevElementIndex(elementIndex), prevElementIdx); + EXPECT_EQ(coll.pov(elementIndex), pov); + + prevElementIdx = elementIndex; + prevPriority = coll.priority(elementIndex); + + first = false; + elementIndex = coll.nextElementIndex(elementIndex); + ++elementCount; + } + EXPECT_EQ(elementCount, coll.population(pov)); + EXPECT_EQ(prevElementIdx, coll.tailIndex(pov)); +} + +void printPovElementCounts(const std::map& povElementCounts) +{ + std::cout << "PoV element counts:\n"; + for (const auto& id_count_pair : povElementCounts) + { + QPI::id id = id_count_pair.first; + unsigned long long count = id_count_pair.second; + std::cout << "\t(" << id.u64._0 << ", " << id.u64._1 << ", " << id.u64._2 << ", " << id.u64._3 << "): " << count << std::endl; + } +} + +// return sorted set of PoVs +template +std::map getPovElementCounts(const QPI::Collection& coll) +{ + // use that in current implementation elements are always in range 0 to N-1 + std::map povs; + for (unsigned long long i = 0; i < coll.population(); ++i) + { + QPI::id id = coll.pov(i); + EXPECT_NE(coll.headIndex(id), QPI::NULL_INDEX); + EXPECT_NE(coll.tailIndex(id), QPI::NULL_INDEX); + ++povs[id]; + } + + for (const auto& id_count_pair : povs) + { + EXPECT_EQ(coll.population(id_count_pair.first), id_count_pair.second); + } + + return povs; +} + +template +void checkCollectionValidState(const QPI::Collection& collection, QPI::sint64 expectedNumOfPoV = -1, bool verbose = false) +{ + auto povCounts = getPovElementCounts(collection); + if (expectedNumOfPoV != -1) + { + EXPECT_EQ(expectedNumOfPoV, povCounts.size()); + } + for (const auto& idCountPair : povCounts) + { + QPI::id pov = idCountPair.first; + checkPriorityQueue(collection, pov, verbose); + } +} + +template +struct CollectionReferenceImpl : std::map>> +{ + void add(const QPI::id& pov, const ValueT& element, QPI::sint64 priority) + { + (*this)[pov].insert(std::pair{ priority, element }); + } + + void remove(const QPI::id& pov, const ValueT& element, QPI::sint64 priority) + { + auto queueIt = this->find(pov); + EXPECT_NE(queueIt, this->end()); + if (queueIt == this->end()) + return; + auto& queue = queueIt->second; + auto range = queue.equal_range(priority); + for (auto elementIt = range.first; elementIt != range.second; ++elementIt) + { + if (elementIt->second == element) + { + queue.erase(elementIt); + if (queue.size() == 0) + this->erase(pov); + return; + } + } + bool elementMissing = true; + EXPECT_FALSE(elementMissing); + } + + template + void checkEqualContent(const QPI::Collection& coll) const + { + auto povQueueSizes = getPovElementCounts(coll); + EXPECT_EQ(povQueueSizes.size(), this->size()); + for (const auto& povPairs : povQueueSizes) + { + auto queueIt = this->find(povPairs.first); + EXPECT_NE(queueIt, this->end()); + if (queueIt == this->end()) + continue; + EXPECT_EQ(queueIt->second.size(), povPairs.second); + const auto& queue = queueIt->second; + auto elementIdx = coll.headIndex(povPairs.first); + for (auto refElementIt = queue.begin(); refElementIt != queue.end(); ++refElementIt) + { + EXPECT_NE(elementIdx, QPI::NULL_INDEX); + EXPECT_EQ(refElementIt->first, coll.priority(elementIdx)); + EXPECT_EQ(refElementIt->second, coll.element(elementIdx)); + elementIdx = coll.nextElementIndex(elementIdx); + } + EXPECT_EQ(elementIdx, QPI::NULL_INDEX); + } + } +}; + +template +bool isCompletelySame(const QPI::Collection& coll1, const QPI::Collection& coll2) +{ + return memcmp(&coll1, &coll2, sizeof(coll1)) == 0; +} + +template +bool haveSameContent(const QPI::Collection& coll1, const QPI::Collection& coll2, bool verbose = true) +{ + // check that both contain the same PoVs, each with the same number of elements + auto coll1PovCounts = getPovElementCounts(coll1); + auto coll2PovCounts = getPovElementCounts(coll2); + if (coll1PovCounts != coll2PovCounts) + { + if (verbose) + { + std::cout << "Differences in PoV sets of collections!" << std::endl; + if (coll1PovCounts.size() != coll2PovCounts.size()) + std::cout << "\tPoV count: " << coll1PovCounts.size() << " vs " << coll2PovCounts.size() << std::endl; + printPovElementCounts(coll1PovCounts); + printPovElementCounts(coll2PovCounts); + } + return false; + } + + // check that values and priorities of the elements are the same + for (const auto& id_count_pair : coll1PovCounts) + { + QPI::id pov = id_count_pair.first; + QPI::sint64 elementIndex1 = coll1.headIndex(pov); + QPI::sint64 elementIndex2 = coll2.headIndex(pov); + while (elementIndex1 != QPI::NULL_INDEX && elementIndex2 != QPI::NULL_INDEX) + { + if (coll1.priority(elementIndex1) != coll2.priority(elementIndex2)) + return false; + if (coll1.element(elementIndex1) != coll2.element(elementIndex2)) + return false; + + EXPECT_EQ(coll1.pov(elementIndex1), pov); + EXPECT_EQ(coll2.pov(elementIndex2), pov); + + elementIndex1 = coll1.nextElementIndex(elementIndex1); + elementIndex2 = coll2.nextElementIndex(elementIndex2); + } + EXPECT_EQ(elementIndex1, QPI::NULL_INDEX); + EXPECT_EQ(elementIndex2, QPI::NULL_INDEX); + EXPECT_EQ(coll1.nextElementIndex(coll1.tailIndex(pov)), QPI::NULL_INDEX); + EXPECT_EQ(coll2.nextElementIndex(coll2.tailIndex(pov)), QPI::NULL_INDEX); + } + + return true; +} + +template +void cleanupCollectionReferenceImplementation(const QPI::Collection& coll, QPI::Collection& newColl) +{ + newColl.reset(); + + // for each pov, add all elements of priority queue in order + auto povs = getPovElementCounts(coll); + for (const auto& id_count_pair : povs) + { + QPI::id pov = id_count_pair.first; + QPI::sint64 elementIndex = coll.headIndex(pov); + while (elementIndex != QPI::NULL_INDEX) + { + newColl.add(pov, coll.element(elementIndex), coll.priority(elementIndex)); + elementIndex = coll.nextElementIndex(elementIndex); + } + } +} + +template +void cleanupCollection(QPI::Collection& coll) +{ + // check that collection in itself is in valid state + checkCollectionValidState(coll); + + // save original data for checking + QPI::Collection origColl; + copyMem(&origColl, &coll, sizeof(coll)); + + // run reference cleanup and test that cleanup did not change any relevant content + cleanupCollectionReferenceImplementation(origColl, coll); + EXPECT_TRUE(haveSameContent(origColl, coll)); + + // run faster cleanup and check result + origColl.cleanup(); + EXPECT_TRUE(haveSameContent(origColl, coll)); +} + + +TEST(TestCoreQPI, CollectionMultiPovMultiElements) +{ + QPI::id id1(1, 2, 3, 4); + QPI::id id2(3, 100, 579, 5431); + QPI::id id3(1, 100, 579, 5431); + + constexpr unsigned long long capacity = 8; + + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::Collection coll; + coll.reset(); + + // test behavior of empty collection + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 0); + EXPECT_EQ(coll.headIndex(id1), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id1), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id1), 0); + EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id2), 0); + EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id3), 0); + // all properties of non-occupied elements are initialized to 0 (by reset function), but in practice only occupied + // elements should be accessed + EXPECT_EQ(coll.pov(0), QPI::id(0, 0, 0, 0)); + EXPECT_EQ(coll.element(0), 0); + EXPECT_EQ(coll.nextElementIndex(0), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(0), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(0), 0); + + // add an element with id1 + constexpr int firstElementValue = 42; + constexpr QPI::sint64 firstElementPriority = 1234; + QPI::sint64 firstElementIdx = coll.add(id1, firstElementValue, firstElementPriority); + EXPECT_TRUE(firstElementIdx != QPI::NULL_INDEX); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 1); + EXPECT_EQ(coll.headIndex(id1), firstElementIdx); + EXPECT_EQ(coll.tailIndex(id1), firstElementIdx); + EXPECT_EQ(coll.population(id1), 1); + EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id2), 0); + EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id3), 0); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + + // add another element with with id1, but higher priority value + // id1 priority queue order: secondElement, firstElement + constexpr int secondElementValue = 987; + constexpr QPI::sint64 secondElementPriority = 12345; + QPI::sint64 secondElementIdx = coll.add(id1, secondElementValue, secondElementPriority); + EXPECT_TRUE(secondElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(secondElementIdx != firstElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 2); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), firstElementIdx); + EXPECT_EQ(coll.population(id1), 2); + EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id2), 0); + EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id3), 0); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + + // add another element with id1, but lower priority value + // id1 priority queue order: secondElement, firstElement, thirdElement + constexpr int thirdElementValue = 98; + constexpr QPI::sint64 thirdElementPriority = 12; + QPI::sint64 thirdElementIdx = coll.add(id1, thirdElementValue, thirdElementPriority); + EXPECT_TRUE(thirdElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(thirdElementIdx != firstElementIdx); + EXPECT_TRUE(thirdElementIdx != secondElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 3); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), thirdElementIdx); + EXPECT_EQ(coll.population(id1), 3); + EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id2), 0); + EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id3), 0); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); + EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); + EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); + + // add element with id2 + // id2 priority queue order: fourthElement + constexpr int fourthElementValue = 4; + constexpr QPI::sint64 fourthElementPriority = -10; + QPI::sint64 fourthElementIdx = coll.add(id2, fourthElementValue, fourthElementPriority); + EXPECT_TRUE(fourthElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(fourthElementIdx != firstElementIdx); + EXPECT_TRUE(fourthElementIdx != secondElementIdx); + EXPECT_TRUE(fourthElementIdx != thirdElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 4); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), thirdElementIdx); + EXPECT_EQ(coll.population(id1), 3); + EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.population(id2), 1); + EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(id3), 0); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + EXPECT_EQ(coll.pov(thirdElementIdx), id1); + EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); + EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); + EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); + EXPECT_EQ(coll.pov(fourthElementIdx), id2); + EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); + EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); + + // add element with id3 + // id3 priority queue order: fifthElement + constexpr int fifthElementValue = 50; + constexpr QPI::sint64 fifthElementPriority = -10; + QPI::sint64 fifthElementIdx = coll.add(id3, fifthElementValue, fifthElementPriority); + EXPECT_TRUE(fifthElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(fifthElementIdx != firstElementIdx); + EXPECT_TRUE(fifthElementIdx != secondElementIdx); + EXPECT_TRUE(fifthElementIdx != thirdElementIdx); + EXPECT_TRUE(fifthElementIdx != fourthElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 5); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), thirdElementIdx); + EXPECT_EQ(coll.population(id1), 3); + EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.population(id2), 1); + EXPECT_EQ(coll.headIndex(id3), fifthElementIdx); + EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); + EXPECT_EQ(coll.population(id3), 1); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + EXPECT_EQ(coll.pov(thirdElementIdx), id1); + EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); + EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); + EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); + EXPECT_EQ(coll.pov(fourthElementIdx), id2); + EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); + EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); + EXPECT_EQ(coll.pov(fifthElementIdx), id3); + EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); + EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); + + // add another element with id1, with lowest priority value + // id1 priority queue order: secondElement, firstElement, thirdElement, sixthElement + constexpr int sixthElementValue = 600; + constexpr QPI::sint64 sixthElementPriority = -60; + QPI::sint64 sixthElementIdx = coll.add(id1, sixthElementValue, sixthElementPriority); + EXPECT_TRUE(sixthElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(sixthElementIdx != firstElementIdx); + EXPECT_TRUE(sixthElementIdx != secondElementIdx); + EXPECT_TRUE(sixthElementIdx != thirdElementIdx); + EXPECT_TRUE(sixthElementIdx != fourthElementIdx); + EXPECT_TRUE(sixthElementIdx != fifthElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 6); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), sixthElementIdx); + EXPECT_EQ(coll.population(id1), 4); + EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.population(id2), 1); + EXPECT_EQ(coll.headIndex(id3), fifthElementIdx); + EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); + EXPECT_EQ(coll.population(id3), 1); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + EXPECT_EQ(coll.pov(thirdElementIdx), id1); + EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); + EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), sixthElementIdx); + EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); + EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); + EXPECT_EQ(coll.pov(fourthElementIdx), id2); + EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); + EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); + EXPECT_EQ(coll.pov(fifthElementIdx), id3); + EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); + EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); + EXPECT_EQ(coll.pov(sixthElementIdx), id1); + EXPECT_EQ(coll.element(sixthElementIdx), sixthElementValue); + EXPECT_EQ(coll.nextElementIndex(sixthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(sixthElementIdx), thirdElementIdx); + EXPECT_EQ(coll.priority(sixthElementIdx), sixthElementPriority); + + // add another element with id3, with highest priority value + // id3 priority queue order: seventhElement, fifthElement + constexpr int seventhElementValue = 700; + constexpr QPI::sint64 seventhElementPriority = 70000; + QPI::sint64 seventhElementIdx = coll.add(id3, seventhElementValue, seventhElementPriority); + EXPECT_TRUE(seventhElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(seventhElementIdx != firstElementIdx); + EXPECT_TRUE(seventhElementIdx != secondElementIdx); + EXPECT_TRUE(seventhElementIdx != thirdElementIdx); + EXPECT_TRUE(seventhElementIdx != fourthElementIdx); + EXPECT_TRUE(seventhElementIdx != fifthElementIdx); + EXPECT_TRUE(seventhElementIdx != sixthElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 7); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), sixthElementIdx); + EXPECT_EQ(coll.population(id1), 4); + EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.population(id2), 1); + EXPECT_EQ(coll.headIndex(id3), seventhElementIdx); + EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); + EXPECT_EQ(coll.population(id3), 2); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), thirdElementIdx); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + EXPECT_EQ(coll.pov(thirdElementIdx), id1); + EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); + EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), sixthElementIdx); + EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), firstElementIdx); + EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); + EXPECT_EQ(coll.pov(fourthElementIdx), id2); + EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); + EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); + EXPECT_EQ(coll.pov(fifthElementIdx), id3); + EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); + EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), seventhElementIdx); + EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); + EXPECT_EQ(coll.pov(sixthElementIdx), id1); + EXPECT_EQ(coll.element(sixthElementIdx), sixthElementValue); + EXPECT_EQ(coll.nextElementIndex(sixthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(sixthElementIdx), thirdElementIdx); + EXPECT_EQ(coll.priority(sixthElementIdx), sixthElementPriority); + EXPECT_EQ(coll.pov(seventhElementIdx), id3); + EXPECT_EQ(coll.element(seventhElementIdx), seventhElementValue); + EXPECT_EQ(coll.nextElementIndex(seventhElementIdx), fifthElementIdx); + EXPECT_EQ(coll.prevElementIndex(seventhElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(seventhElementIdx), seventhElementPriority); + + // add another element with id1, with medium priority value + // id1 priority queue order: secondElement, firstElement, eighthElement, thirdElement, sixthElement + // priorities: 12345, 1234, 123, 12, -60 + constexpr int eighthElementValue = 800; + constexpr QPI::sint64 eighthElementPriority = 123; + QPI::sint64 eighthElementIdx = coll.add(id1, eighthElementValue, eighthElementPriority); + EXPECT_TRUE(eighthElementIdx != QPI::NULL_INDEX); + EXPECT_TRUE(eighthElementIdx != firstElementIdx); + EXPECT_TRUE(eighthElementIdx != secondElementIdx); + EXPECT_TRUE(eighthElementIdx != thirdElementIdx); + EXPECT_TRUE(eighthElementIdx != fourthElementIdx); + EXPECT_TRUE(eighthElementIdx != fifthElementIdx); + EXPECT_TRUE(eighthElementIdx != sixthElementIdx); + EXPECT_TRUE(eighthElementIdx != seventhElementIdx); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 8); + EXPECT_EQ(coll.headIndex(id1), secondElementIdx); + EXPECT_EQ(coll.tailIndex(id1), sixthElementIdx); + EXPECT_EQ(coll.population(id1), 5); + EXPECT_EQ(coll.headIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.tailIndex(id2), fourthElementIdx); + EXPECT_EQ(coll.population(id2), 1); + EXPECT_EQ(coll.headIndex(id3), seventhElementIdx); + EXPECT_EQ(coll.tailIndex(id3), fifthElementIdx); + EXPECT_EQ(coll.population(id3), 2); + EXPECT_EQ(coll.pov(firstElementIdx), id1); + EXPECT_EQ(coll.element(firstElementIdx), firstElementValue); + EXPECT_EQ(coll.nextElementIndex(firstElementIdx), eighthElementIdx); + EXPECT_EQ(coll.prevElementIndex(firstElementIdx), secondElementIdx); + EXPECT_EQ(coll.priority(firstElementIdx), firstElementPriority); + EXPECT_EQ(coll.pov(secondElementIdx), id1); + EXPECT_EQ(coll.element(secondElementIdx), secondElementValue); + EXPECT_EQ(coll.nextElementIndex(secondElementIdx), firstElementIdx); + EXPECT_EQ(coll.prevElementIndex(secondElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(secondElementIdx), secondElementPriority); + EXPECT_EQ(coll.pov(thirdElementIdx), id1); + EXPECT_EQ(coll.element(thirdElementIdx), thirdElementValue); + EXPECT_EQ(coll.nextElementIndex(thirdElementIdx), sixthElementIdx); + EXPECT_EQ(coll.prevElementIndex(thirdElementIdx), eighthElementIdx); + EXPECT_EQ(coll.priority(thirdElementIdx), thirdElementPriority); + EXPECT_EQ(coll.pov(fourthElementIdx), id2); + EXPECT_EQ(coll.element(fourthElementIdx), fourthElementValue); + EXPECT_EQ(coll.nextElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fourthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(fourthElementIdx), fourthElementPriority); + EXPECT_EQ(coll.pov(fifthElementIdx), id3); + EXPECT_EQ(coll.element(fifthElementIdx), fifthElementValue); + EXPECT_EQ(coll.nextElementIndex(fifthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(fifthElementIdx), seventhElementIdx); + EXPECT_EQ(coll.priority(fifthElementIdx), fifthElementPriority); + EXPECT_EQ(coll.pov(sixthElementIdx), id1); + EXPECT_EQ(coll.element(sixthElementIdx), sixthElementValue); + EXPECT_EQ(coll.nextElementIndex(sixthElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(sixthElementIdx), thirdElementIdx); + EXPECT_EQ(coll.priority(sixthElementIdx), sixthElementPriority); + EXPECT_EQ(coll.pov(seventhElementIdx), id3); + EXPECT_EQ(coll.element(seventhElementIdx), seventhElementValue); + EXPECT_EQ(coll.nextElementIndex(seventhElementIdx), fifthElementIdx); + EXPECT_EQ(coll.prevElementIndex(seventhElementIdx), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(seventhElementIdx), seventhElementPriority); + EXPECT_EQ(coll.pov(eighthElementIdx), id1); + EXPECT_EQ(coll.element(eighthElementIdx), eighthElementValue); + EXPECT_EQ(coll.nextElementIndex(eighthElementIdx), thirdElementIdx); + EXPECT_EQ(coll.prevElementIndex(eighthElementIdx), firstElementIdx); + EXPECT_EQ(coll.priority(eighthElementIdx), eighthElementPriority); + + checkPriorityQueue(coll, id1); + checkPriorityQueue(coll, id2); + checkPriorityQueue(coll, id3); + + // test that nothing is added to full + EXPECT_EQ(capacity, 8); + QPI::sint64 ninthElementIdx = coll.add(id1, 1234, 6544); + EXPECT_TRUE(ninthElementIdx == QPI::NULL_INDEX); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 8); + + // test comparison function of full collection + QPI::Collection empty_coll; + empty_coll.reset(); + EXPECT_TRUE(isCompletelySame(coll, coll)); + EXPECT_TRUE(haveSameContent(coll, coll)); + EXPECT_FALSE(isCompletelySame(coll, empty_coll)); + EXPECT_FALSE(haveSameContent(coll, empty_coll, false)); + + // test behavior of collection after resetting non-empty collection + coll.reset(); + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), 0); + EXPECT_EQ(coll.headIndex(id1), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id1), QPI::NULL_INDEX); + EXPECT_EQ(coll.headIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id2), QPI::NULL_INDEX); + EXPECT_EQ(coll.headIndex(id3), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(id3), QPI::NULL_INDEX); + // all properties of non-occupied elements are initialized to 0 (by reset function), but in practice only occupied + // elements should be accessed + EXPECT_EQ(coll.pov(0), QPI::id(0, 0, 0, 0)); + EXPECT_EQ(coll.element(0), 0); + EXPECT_EQ(coll.nextElementIndex(0), QPI::NULL_INDEX); + EXPECT_EQ(coll.prevElementIndex(0), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(0), 0); +} + +template +void testCollectionOnePovMultiElements(int prioAmpFactor, int prioFreqDiv) +{ + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::Collection coll; + coll.reset(); + + // scratchpad may be needed if Collection::_rebuild() is called + EXPECT_TRUE(commonBuffers.init(1, sizeof(coll))); + + // check that behavior of collection and reference implementation matches + CollectionReferenceImpl collReference; + + // these tests support changing the implementation of the element array filling to non-sequential + // by saving element indices in order + std::vector elementIndices; + + // fill completely with alternating priorities + QPI::id pov(1, 2, 3, 4); + for (int i = 0; i < capacity; ++i) + { + QPI::sint64 prio = QPI::sint64(i * prioAmpFactor * sin(i / prioFreqDiv)); + int value = i * 4; + + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), i); + EXPECT_EQ(coll.population(pov), i); + + QPI::sint64 elementIndex = coll.add(pov, value, prio); + elementIndices.push_back(elementIndex); + checkPriorityQueue(coll, pov); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.population(pov), i + 1); + EXPECT_EQ(coll.population(), i + 1); + + collReference.add(pov, value, prio); + collReference.checkEqualContent(coll); + } + + // check that nothing can be added + QPI::sint64 elementIndex = coll.add(pov, 1234, 12345); + EXPECT_TRUE(elementIndex == QPI::NULL_INDEX); + EXPECT_EQ(coll.capacity(), coll.population()); + EXPECT_EQ(coll.population(pov), coll.capacity()); + + // check validity of data + checkPriorityQueue(coll, pov); + for (int i = 0; i < capacity; ++i) + { + QPI::sint64 prio = QPI::sint64(i * prioAmpFactor * sin(i / prioFreqDiv)); + int value = i * 4; + + QPI::sint64 elementIndex = elementIndices[i]; + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + } + + // remove first element + { + QPI::sint64 headIndex = coll.headIndex(pov); + QPI::sint64 afterHeadIndex = coll.nextElementIndex(headIndex); + QPI::sint64 afterHeadPrio = coll.priority(afterHeadIndex); + int afterHeadValue = coll.element(afterHeadIndex); + EXPECT_EQ(coll.population(), coll.capacity()); + EXPECT_EQ(coll.population(pov), coll.capacity()); + checkPriorityQueue(coll, pov); + QPI::sint64 followingRemovedIndex = coll.remove(headIndex); + EXPECT_EQ(coll.priority(followingRemovedIndex), afterHeadPrio); + EXPECT_EQ(coll.element(followingRemovedIndex), afterHeadValue); + EXPECT_EQ(coll.population(), coll.capacity() - 1); + EXPECT_EQ(coll.population(pov), coll.capacity() - 1); + + checkPriorityQueue(coll, pov); + + headIndex = coll.headIndex(pov); + EXPECT_EQ(coll.prevElementIndex(headIndex), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(headIndex), afterHeadPrio); + EXPECT_EQ(coll.element(headIndex), afterHeadValue); + } + + // remove last element + { + QPI::sint64 tailIndex = coll.tailIndex(pov); + QPI::sint64 beforeTailIndex = coll.prevElementIndex(tailIndex); + QPI::sint64 beforeTailPrio = coll.priority(beforeTailIndex); + int beforeTailValue = coll.element(beforeTailIndex); + EXPECT_EQ(coll.population(), coll.capacity() - 1); + EXPECT_EQ(coll.population(pov), coll.capacity() - 1); + QPI::sint64 followingRemovedIndex = coll.remove(tailIndex); + EXPECT_EQ(followingRemovedIndex, QPI::NULL_INDEX); + EXPECT_EQ(coll.population(), coll.capacity() - 2); + EXPECT_EQ(coll.population(pov), coll.capacity() - 2); + + checkPriorityQueue(coll, pov); + + tailIndex = coll.tailIndex(pov); + EXPECT_EQ(coll.nextElementIndex(tailIndex), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(tailIndex), beforeTailPrio); + EXPECT_EQ(coll.element(tailIndex), beforeTailValue); + } + + // remove element in front of tail + { + QPI::sint64 tailIndex = coll.tailIndex(pov); + QPI::sint64 beforeTailIndex = coll.prevElementIndex(tailIndex); + QPI::sint64 twoBeforeTailIndex = coll.prevElementIndex(beforeTailIndex); + QPI::sint64 tailPrio = coll.priority(tailIndex); + QPI::sint64 twoBeforeTailPrio = coll.priority(twoBeforeTailIndex); + int tailValue = coll.element(tailIndex); + int twoBeforeTailValue = coll.element(twoBeforeTailIndex); + EXPECT_EQ(coll.population(), coll.capacity() - 2); + EXPECT_EQ(coll.population(pov), coll.capacity() - 2); + QPI::sint64 followingRemovedIndex = coll.remove(beforeTailIndex); + EXPECT_EQ(coll.priority(followingRemovedIndex), tailPrio); + EXPECT_EQ(coll.element(followingRemovedIndex), tailValue); + EXPECT_EQ(coll.population(), coll.capacity() - 3); + EXPECT_EQ(coll.population(pov), coll.capacity() - 3); + + checkPriorityQueue(coll, pov); + + tailIndex = coll.tailIndex(pov); + beforeTailIndex = coll.prevElementIndex(tailIndex); + EXPECT_EQ(coll.nextElementIndex(tailIndex), QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(tailIndex), tailPrio); + EXPECT_EQ(coll.priority(beforeTailIndex), twoBeforeTailPrio); + EXPECT_EQ(coll.element(tailIndex), tailValue); + EXPECT_EQ(coll.element(beforeTailIndex), twoBeforeTailValue); + } + + // add new highest and lowest priority element and remove others to trigger uncovered case of moving + // last element to other index + { + int newValue1 = 4278956; + QPI::sint64 newPrio1 = 10000000000ll; + QPI::sint64 newIdx1 = coll.add(pov, newValue1, newPrio1); + EXPECT_EQ(newIdx1, coll.population() - 1); + int newValue2 = 2568956; + QPI::sint64 newPrio2 = -10000000000ll; + QPI::sint64 newIdx2 = coll.add(pov, newValue2, newPrio2); + EXPECT_EQ(newIdx2, coll.population() - 1); + + EXPECT_EQ(coll.population(), coll.capacity() - 1); + EXPECT_EQ(coll.population(pov), coll.capacity() - 1); + + checkPriorityQueue(coll, pov); + + // remove one + int followingRemovedValue = coll.element(coll.nextElementIndex(0)); + QPI::sint64 followingRemovedPrio = coll.priority(coll.nextElementIndex(0)); + QPI::sint64 followingRemovedIndex = coll.remove(0); + EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); + EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); + checkPriorityQueue(coll, pov); + + // remove another (not head or tail!) + QPI::sint64 removeIdx = followingRemovedIndex; + followingRemovedValue = coll.element(coll.nextElementIndex(removeIdx)); + followingRemovedPrio = coll.priority(coll.nextElementIndex(removeIdx)); + followingRemovedIndex = coll.remove(removeIdx); + EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); + EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); + checkPriorityQueue(coll, pov); + + EXPECT_EQ(coll.population(), coll.capacity() - 3); + EXPECT_EQ(coll.population(pov), coll.capacity() - 3); + + // check tail and head + newIdx1 = coll.tailIndex(pov); + newIdx2 = coll.headIndex(pov); + EXPECT_EQ(coll.priority(newIdx1), newPrio2); + EXPECT_EQ(coll.priority(newIdx2), newPrio1); + EXPECT_EQ(coll.element(newIdx1), newValue2); + EXPECT_EQ(coll.element(newIdx2), newValue1); + } + + // remove remaining elements except last + while (coll.population() > 1) + { + int followingRemovedValue = coll.element(coll.nextElementIndex(0)); + QPI::sint64 followingRemovedPrio = coll.priority(coll.nextElementIndex(0)); + QPI::sint64 followingRemovedIndex = coll.remove(0); + EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); + EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); + checkPriorityQueue(coll, pov); + EXPECT_EQ(coll.population(), coll.population(pov)); + } + + // remove last element + { + EXPECT_EQ(coll.headIndex(pov), 0); + EXPECT_EQ(coll.tailIndex(pov), 0); + EXPECT_EQ(coll.remove(0), QPI::NULL_INDEX); + checkPriorityQueue(coll, pov); + EXPECT_EQ(coll.headIndex(pov), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(pov), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(), 0); + EXPECT_EQ(coll.population(pov), 0); + } + + // test that removing element from empty collection has no effect + { + EXPECT_EQ(coll.remove(0), QPI::NULL_INDEX); + checkPriorityQueue(coll, pov); + EXPECT_EQ(coll.headIndex(pov), QPI::NULL_INDEX); + EXPECT_EQ(coll.tailIndex(pov), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(), 0); + EXPECT_EQ(coll.population(pov), 0); + } + + // check that cleanup after removing all elements leads to same as reset() in terms of memory + QPI::Collection resetColl; + resetColl.reset(); + EXPECT_FALSE(isCompletelySame(resetColl, coll)); + coll.cleanup(); + EXPECT_TRUE(isCompletelySame(resetColl, coll)); + + // cleanup + commonBuffers.deinit(); +} + +TEST(TestCoreQPI, CollectionOnePovMultiElements) +{ + testCollectionOnePovMultiElements<16>(10, 3); + testCollectionOnePovMultiElements<128>(10, 3); + testCollectionOnePovMultiElements<128>(10, 10); + testCollectionOnePovMultiElements<128>(1, 10); + testCollectionOnePovMultiElements<128>(1, 1); +} + +TEST(TestCoreQPI, CollectionOnePovMultiElementsSamePrioOrder) +{ + constexpr unsigned long long capacity = 16; + + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::Collection coll; + coll.reset(); + + // these tests support changing the implementation of the element array filling to non-sequential + // by saving element indices in order + std::vector elementIndices; + + // check that behavior of collection and reference implementation matches + CollectionReferenceImpl collReference; + + // fill completely with same priority + QPI::id pov(1, 2, 3, 4); + constexpr QPI::sint64 prio = 100; + for (int i = 0; i < capacity; ++i) + { + int value = i * 3; + + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), i); + EXPECT_EQ(coll.population(pov), i); + + QPI::sint64 elementIndex = coll.add(pov, value, prio); + elementIndices.push_back(elementIndex); + checkPriorityQueue(coll, pov); + collReference.add(pov, value, prio); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.population(pov), i + 1); + EXPECT_EQ(coll.population(), i + 1); + } + + collReference.checkEqualContent(coll); + + // check that priority queue order of same priorty items matches the order of insertion + QPI::sint64 elementIndex = coll.headIndex(pov); + for (int i = 0; i < capacity; ++i) + { + int value = i * 3; + EXPECT_NE(elementIndex, QPI::NULL_INDEX); + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + elementIndex = coll.nextElementIndex(elementIndex); + } + EXPECT_EQ(elementIndex, QPI::NULL_INDEX); +} + +template +void testCollectionMultiPovOneElement(bool cleanupAfterEachRemove) +{ + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::Collection coll; + coll.reset(); + + for (int i = 0; i < capacity; ++i) + { + // select pov to ensure hash collisions + QPI::id pov(i / 2, i % 2, i * 2, i * 3); + int value = i * 4; + QPI::sint64 prio = i * 5; + + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(pov), 0); + EXPECT_EQ(coll.population(), i); + + QPI::sint64 elementIndex = coll.add(pov, value, prio); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.population(pov), 1); + EXPECT_EQ(coll.population(), i + 1); + + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.pov(elementIndex), pov); + checkPriorityQueue(coll, pov, false); + } + + // check that nothing can be added + QPI::sint64 elementIndex = coll.add(QPI::id(1, 2, 3, 4), 12345, 123456); + EXPECT_TRUE(elementIndex == QPI::NULL_INDEX); + EXPECT_EQ(coll.capacity(), coll.population()); + + // check and remove + for (int j = 0; j < capacity; ++j) + { + // check integrity of povs not removed yet + for (int i = j; i < capacity; ++i) + { + QPI::id pov(i / 2, i % 2, i * 2, i * 3); + int value = i * 4; + QPI::sint64 prio = i * 5; + + QPI::sint64 elementIndex = coll.headIndex(pov); + EXPECT_NE(elementIndex, -1); + EXPECT_EQ(elementIndex, coll.tailIndex(pov)); + + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.pov(elementIndex), pov); + checkPriorityQueue(coll, pov, false); + } + + // remove + QPI::id removePov(j / 2, j % 2, j * 2, j * 3); + EXPECT_EQ(coll.population(removePov), 1); + EXPECT_EQ(coll.remove(coll.headIndex(removePov)), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(removePov), 0); + EXPECT_EQ(coll.population(), capacity - j - 1); + + if (cleanupAfterEachRemove) + coll.cleanup(); + } + + // check that cleanup after removing all elements leads to same as reset() in terms of memory + QPI::Collection resetColl; + resetColl.reset(); + if (!cleanupAfterEachRemove) + EXPECT_FALSE(isCompletelySame(resetColl, coll)); + EXPECT_TRUE(haveSameContent(resetColl, coll, true)); + coll.cleanup(); + EXPECT_TRUE(isCompletelySame(resetColl, coll)); +} + +TEST(TestCoreQPI, CollectionMultiPovOneElement) +{ + bool cleanupAfterEachRemove = false; + testCollectionMultiPovOneElement<16>(cleanupAfterEachRemove); + testCollectionMultiPovOneElement<32>(cleanupAfterEachRemove); + testCollectionMultiPovOneElement<64>(cleanupAfterEachRemove); + testCollectionMultiPovOneElement<128>(cleanupAfterEachRemove); +} + +template +void testCollectionMultiPovOneElementReuseFreedSlotsBeforeCleanup() +{ + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::Collection coll; + coll.reset(); + + // for checking that content of collection matches with reference implementation + CollectionReferenceImpl collReference; + + // add and remove same item multiple times for testing simple case of reusing slot + for (int i = 0; i < capacity / 2; ++i) + { + for (int j = 0; j <= i; ++j) + { + QPI::id pov(j, 0, 0, 0); + QPI::sint64 elementIndex = coll.add(pov, i, 2 * j); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.pov(elementIndex), pov); + EXPECT_EQ(coll.population(pov), 1); + EXPECT_EQ(coll.population(), 1); + checkCollectionValidState(coll, 1); + + EXPECT_EQ(coll.remove(elementIndex), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(pov), 0); + EXPECT_EQ(coll.population(), 0); + checkCollectionValidState(coll, 0); + } + } + + // fill collection up to capacity + for (int i = 0; i < capacity; ++i) + { + // select pov to ensure hash collisions + QPI::id pov(i / 3, i % 2, i * 2, i * 3); + int value = i * 4; + QPI::sint64 prio = i * 5; + + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(pov), 0); + EXPECT_EQ(coll.population(), i); + + QPI::sint64 elementIndex = coll.add(pov, value, prio); + collReference.add(pov, value, prio); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.population(pov), 1); + EXPECT_EQ(coll.population(), i + 1); + checkCollectionValidState(coll, i + 1); + + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.pov(elementIndex), pov); + + collReference.checkEqualContent(coll); + } + + // check that nothing can be added + QPI::sint64 elementIndex = coll.add(QPI::id(1, 2, 3, 4), 12345, 123456); + EXPECT_TRUE(elementIndex == QPI::NULL_INDEX); + EXPECT_EQ(coll.capacity(), coll.population()); + + // check and remove all one by one + for (int j = 0; j < capacity; ++j) + { + // check integrity of povs not removed yet + checkCollectionValidState(coll, capacity - j); + + // remove + QPI::id removePov(j / 3, j % 2, j * 2, j * 3); + int value = j * 4; + QPI::sint64 prio = j * 5; + EXPECT_EQ(coll.population(removePov), 1); + EXPECT_EQ(coll.remove(coll.headIndex(removePov)), QPI::NULL_INDEX); + EXPECT_EQ(coll.population(removePov), 0); + EXPECT_EQ(coll.population(), capacity - j - 1); + + collReference.remove(removePov, value, prio); + collReference.checkEqualContent(coll); + } + + // reuse pov slots without cleanup + for (int i = 0; i < capacity; ++i) + { + // select pov to ensure hash collisions + QPI::id pov(i / 2, i % 4, i * 5, i + 1); + int value = i * 6; + QPI::sint64 prio = i * 9; + + EXPECT_EQ(coll.population(pov), 0); + EXPECT_EQ(coll.population(), i); + checkCollectionValidState(coll, i); + + QPI::sint64 elementIndex = coll.add(pov, value, prio); + collReference.add(pov, value, prio); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.population(pov), 1); + EXPECT_EQ(coll.population(), i + 1); + checkCollectionValidState(coll, i + 1); + + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.pov(elementIndex), pov); + + collReference.checkEqualContent(coll); + } +} + +TEST(TestCoreQPI, CollectionMultiPovOneElementReuseSlotsBeforeCleanup) +{ + testCollectionMultiPovOneElementReuseFreedSlotsBeforeCleanup<4>(); + testCollectionMultiPovOneElementReuseFreedSlotsBeforeCleanup<16>(); +} + +TEST(TestCoreQPI, CollectionOneRemoveLastHeadTail) +{ + // Minimal test cases for bug fixed in + // https://github.com/qubic/core/commit/b379a36666f747b25992d025dd68949b771b1cd0#diff-2435a5cdb31de2a231e71d143e1cba9e4f9207181a6223d736293d40da41d002 + + QPI::id pov(1, 2, 3, 4); + constexpr unsigned long long capacity = 4; + + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::Collection coll; + coll.reset(); + + bool print = false; + coll.add(pov, 1234, 1000); + coll.add(pov, 1234, 10000); + checkPriorityQueue(coll, pov, print); + EXPECT_EQ(coll.remove(1), 0); + checkPriorityQueue(coll, pov, print); + + coll.reset(); + coll.add(pov, 1234, 10000); + coll.add(pov, 1234, 1000); + checkPriorityQueue(coll, pov, print); + EXPECT_EQ(coll.remove(1), QPI::NULL_INDEX); + checkPriorityQueue(coll, pov, print); +} + +TEST(TestCoreQPI, CollectionSubCollections) +{ + QPI::id pov(1, 2, 3, 4); + + QPI::Collection coll; + coll.reset(); + + // test empty + auto headIdx = coll.headIndex(pov, 0); + EXPECT_EQ(headIdx, QPI::NULL_INDEX); + auto tailIdx = coll.tailIndex(pov, 0); + EXPECT_EQ(tailIdx, QPI::NULL_INDEX); + + std::vector priorities = { + 44, 22, 88, 111, 55, 56, 11, 55, 55, 54, 66, 77, 99 + }; + + for (size_t i = 0; i < priorities.size(); i++) + { + coll.add(pov, i, priorities[i]); + } + checkPriorityQueue(coll, pov, false); + + // sorted priorities: 111, 99, 88, 77, 66, .... 44, 22, 11 + + // test head/tail + headIdx = coll.headIndex(pov); + EXPECT_EQ(coll.priority(headIdx), 111); + tailIdx = coll.tailIndex(pov); + EXPECT_EQ(coll.priority(tailIdx), 11); + + // test prev/next + auto idx = coll.prevElementIndex(tailIdx); + idx = coll.prevElementIndex(idx); + EXPECT_EQ(coll.priority(idx), 44); + idx = coll.nextElementIndex(headIdx); + idx = coll.nextElementIndex(idx); + EXPECT_EQ(coll.priority(idx), 88); + + // test sub-collection's head priority <= maxPriority + headIdx = coll.headIndex(pov, 112); + EXPECT_EQ(coll.priority(headIdx), 111); + + // test sub-collection's tail priority > maxPriority + headIdx = coll.headIndex(pov, 10); + EXPECT_EQ(headIdx, QPI::NULL_INDEX); + + // test sub-collection's head priority < minPriority + tailIdx = coll.tailIndex(pov, 112); + EXPECT_EQ(tailIdx, QPI::NULL_INDEX); + + // test sub-collection's tail priority >= minPriority + tailIdx = coll.tailIndex(pov, 10); + EXPECT_EQ(coll.priority(tailIdx), 11); + + // test sub-collection's head + headIdx = coll.headIndex(pov, 100); + EXPECT_EQ(coll.priority(headIdx), 99); + headIdx = coll.headIndex(pov, 99); + EXPECT_EQ(coll.priority(headIdx), 99); + + // test sub-collection's head: duplicated priorites + headIdx = coll.headIndex(pov, 55); + EXPECT_EQ(coll.priority(headIdx), 55); + idx = coll.prevElementIndex(headIdx); + EXPECT_EQ(coll.priority(idx), 56); + + // test sub-collection's tail + tailIdx = coll.tailIndex(pov, 33); + EXPECT_EQ(coll.priority(tailIdx), 44); + tailIdx = coll.tailIndex(pov, 44); + EXPECT_EQ(coll.priority(tailIdx), 44); + + // test sub-collection's tail: duplicated priorites + tailIdx = coll.tailIndex(pov, 55); + EXPECT_EQ(coll.priority(tailIdx), 55); + idx = coll.nextElementIndex(tailIdx); + EXPECT_EQ(coll.priority(idx), 54); +} + +TEST(TestCoreQPI, CollectionSubCollectionsRandom) +{ + QPI::id pov(1, 2, 3, 4); + + QPI::Collection coll; + coll.reset(); + + // scratchpad may be needed if Collection::_rebuild() is called + EXPECT_TRUE(commonBuffers.init(1, sizeof(coll))); + + const int seed = 246357; + std::mt19937_64 gen64(seed); + + const int numTests = 10; + for (int test = 1; test <= numTests; test++) + { + coll.reset(); + std::vector< QPI::sint64> priorities(777); + for (size_t i = 0; i < priorities.size(); i++) { + auto v = std::abs((QPI::sint64)gen64()) % 0xFFFF; + priorities[i] = v; + coll.add(pov, (i + 1) * test, priorities[i]); + } + checkPriorityQueue(coll, pov, false); + + std::sort(priorities.begin(), priorities.end(), std::greater<>()); + const auto size = priorities.size(); + + // test sub-collection's head priority <= maxPriority + auto headIdx = coll.headIndex(pov, priorities[0] + 1); + EXPECT_EQ(coll.priority(headIdx), priorities[0]); + headIdx = coll.headIndex(pov, priorities[0]); + EXPECT_EQ(coll.priority(headIdx), priorities[0]); + + // test sub-collection's tail priority > maxPriority + headIdx = coll.headIndex(pov, priorities[size - 1] - 1); + EXPECT_EQ(headIdx, QPI::NULL_INDEX); + + // test sub-collection's head priority < minPriority + auto tailIdx = coll.tailIndex(pov, priorities[0] + 1); + EXPECT_EQ(tailIdx, QPI::NULL_INDEX); + + // test sub-collection's tail priority >= minPriority + tailIdx = coll.tailIndex(pov, priorities[size - 1] - 1); + EXPECT_EQ(coll.priority(tailIdx), priorities[size - 1]); + tailIdx = coll.tailIndex(pov, priorities[size - 1]); + EXPECT_EQ(coll.priority(tailIdx), priorities[size - 1]); + + std::vector indices(std::min(size, std::max(1llu, size / 5))); + for (size_t i = 0; i < indices.size(); i++) { + indices[i] = std::abs((QPI::sint64)gen64()) % indices.size(); + } + + for (size_t i : indices) + { + const auto priority = priorities[i]; + + // test sub-collection: head + auto idx = coll.headIndex(pov, priority); + EXPECT_EQ(coll.priority(idx), priority); + + // test sub-collection: higher priority + if (idx != coll.headIndex(pov)) + { + auto higher_priority = priority; + for (size_t j = i - 1; j >= 0; j--) + { + if (priorities[j] > priority) + { + higher_priority = priorities[j]; + break; + } + } + auto prev_idx = coll.prevElementIndex(idx); + EXPECT_EQ(coll.priority(prev_idx), higher_priority); + } + + // test sub-collection: tail + idx = coll.tailIndex(pov, priority); + EXPECT_EQ(coll.priority(idx), priority); + + // test sub-collection: lower priority + if (idx != coll.tailIndex(pov)) + { + auto lower_priority = priority; + for (size_t j = i + 1; j < size; j++) + { + if (priorities[j] < priority) + { + lower_priority = priorities[j]; + break; + } + } + auto next_idx = coll.nextElementIndex(idx); + EXPECT_EQ(coll.priority(next_idx), lower_priority); + } + } + } + + commonBuffers.deinit(); +} + +TEST(TestCoreQPI, CollectionReplaceElements) +{ + QPI::id pov(1, 2, 3, 4); + + QPI::Collection coll; + coll.reset(); + + const int seed = 246357; + std::mt19937_64 gen64(seed); + + // init a collection for test + const size_t numElements = 1000; + std::vector priorities(numElements); + for (size_t i = 0; i < numElements; i++) + { + priorities[i] = std::abs((QPI::sint64)gen64()) % 0xFFFF; + coll.add(pov, i, priorities[i]); + } + checkPriorityQueue(coll, pov, false); + + // test for special cases out of bound element index + size_t replaceElement = 999; + + // out of collection's capacity + coll.replace(coll.capacity(), replaceElement); + for (size_t i = 0; i < numElements; i++) + { + EXPECT_EQ(coll.element(i), i); + } + + // out of collection's size + coll.replace(numElements, replaceElement); + for (size_t i = 0; i < numElements; i++) + { + EXPECT_EQ(coll.element(i), i); + } + + // generate random replace indices + const size_t numTests = 500; + std::vector replaceIndices(numTests); + for (size_t i = 0; i < numTests; i++) + { + replaceIndices[i] = std::abs((QPI::sint64)gen64()) % numElements; + // remove some of indices in the list + if (i % 4) + { + coll.remove(replaceIndices[i]); + } + } + + // get the new priorities list + size_t numRemainedElements = coll.population(); + priorities.resize(numRemainedElements); + for (size_t i = 0; i < numRemainedElements; i++) + { + priorities[i] = coll.priority(i); + } + + // run the test on the list + for (size_t i = 0; i < numTests; i++) + { + QPI::sint64 replaceElement = i; + QPI::sint64 replaceIndex = replaceIndices[i]; + + QPI::sint64 nextElementIndex = coll.nextElementIndex(replaceIndex); + QPI::sint64 prevElementIndex = coll.prevElementIndex(replaceIndex); + + coll.replace(replaceIndex, replaceElement); + + if (size_t(replaceIndex) < numRemainedElements) + { + EXPECT_EQ(coll.element(replaceIndex), replaceElement); + EXPECT_EQ(coll.priority(replaceIndex), priorities[replaceIndex]); + EXPECT_EQ(coll.nextElementIndex(replaceIndex), nextElementIndex); + EXPECT_EQ(coll.prevElementIndex(replaceIndex), prevElementIndex); + } + } +} + +template +void testCollectionPseudoRandom(int povs, int seed, bool povCollisions, int cleanups, int percentAdd = 70, int percentAddSecondHalf = -1) +{ + // add and remove entries with pseudo-random sequence + std::mt19937_64 gen64(seed); + + QPI::Collection coll; + coll.reset(); + CollectionReferenceImpl collReference; + + // test cleanup of empty collection + cleanupCollection(coll); + + int cleanupCounter = 0; + while (cleanupCounter < cleanups) + { + int p = gen64() % 100; + + if (p == 0) + { + // cleanup (after about 100 add/remove) + cleanupCollection(coll); + ++cleanupCounter; + + if (cleanupCounter == cleanups / 2 && percentAddSecondHalf >= 0) + percentAdd = percentAddSecondHalf; + } + + if (p < percentAdd) + { + // add to collection (more probable than remove) + QPI::id pov = (povCollisions) ? QPI::id(0, 0, 0, gen64() % povs) : QPI::id(gen64() % povs, 0, 0, 0); + unsigned long long value = gen64(); + QPI::sint64 priority = gen64(); + if (coll.population() != coll.capacity()) + { + EXPECT_NE(coll.add(pov, value, priority), QPI::NULL_INDEX); + collReference.add(pov, value, priority); + } + else + { + EXPECT_EQ(coll.add(pov, value, priority), QPI::NULL_INDEX); + } + } + else if (coll.population() > 0) + { + // remove from collection (also testing next index returned by remove) + QPI::sint64 removeIdx = gen64() % coll.population(); + QPI::id pov = coll.pov(removeIdx); + QPI::sint64 priority = coll.priority(removeIdx); + unsigned long long value = coll.element(removeIdx); + QPI::sint64 followingRemovedIndex = coll.nextElementIndex(removeIdx); + if (followingRemovedIndex != QPI::NULL_INDEX) + { + unsigned long long followingRemovedValue = coll.element(followingRemovedIndex); + QPI::sint64 followingRemovedPrio = coll.priority(followingRemovedIndex); + followingRemovedIndex = coll.remove(removeIdx); + EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); + EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); + } + else + { + EXPECT_EQ(coll.remove(removeIdx), QPI::NULL_INDEX); + } + collReference.remove(pov, value, priority); + } + + collReference.checkEqualContent(coll); + + // std::cout << "population: " << coll.population() << " = " << coll.population() * 100 / coll.capacity() << " %" << std::endl; + } +} + +TEST(TestCoreQPI, CollectionInsertRemoveCleanupRandom) +{ + commonBuffers.init(1, 10 * 1024 * 1024); + constexpr unsigned int numCleanups = 30; + for (int i = 0; i < 10; ++i) + { + bool povCollisions = false; + testCollectionPseudoRandom<512>(300, 12345 + i, povCollisions, numCleanups, 70, 40); + testCollectionPseudoRandom<256>(256, 1234 + i, povCollisions, numCleanups, 60, 40); + testCollectionPseudoRandom<256>(10, 123 + i, povCollisions, numCleanups, 60, 40); + testCollectionPseudoRandom<16>(10, 12 + i, povCollisions, numCleanups, 55, 45); + testCollectionPseudoRandom<4>(4, 42 + i, povCollisions, numCleanups, 52, 48); + + povCollisions = true; + testCollectionPseudoRandom<512>(300, 12345 + i, povCollisions, numCleanups, 70, 40); + testCollectionPseudoRandom<256>(256, 1234 + i, povCollisions, numCleanups, 60, 40); + testCollectionPseudoRandom<256>(10, 123 + i, povCollisions, numCleanups, 60, 40); + testCollectionPseudoRandom<16>(10, 12 + i, povCollisions, numCleanups, 55, 45); + testCollectionPseudoRandom<4>(4, 42 + i, povCollisions, numCleanups, 52, 48); + } + commonBuffers.deinit(); +} + +TEST(TestCoreQPI, CollectionCleanupWithPovCollisions) +{ + // Shows bugs in cleanup() that occur in case of massive pov hash map collisions and in case of capacity < 32 + commonBuffers.init(1, 10 * 1024 * 1024); + bool cleanupAfterEachRemove = true; + testCollectionMultiPovOneElement<16>(cleanupAfterEachRemove); + testCollectionMultiPovOneElement<32>(cleanupAfterEachRemove); + testCollectionMultiPovOneElement<64>(cleanupAfterEachRemove); + testCollectionMultiPovOneElement<128>(cleanupAfterEachRemove); + commonBuffers.deinit(); +} + + +template +T genNumber( + const T* genBuffer, + const QPI::uint64 genSize, + QPI::uint64& idx) +{ + T val = genBuffer[idx]; + idx = (idx + 1) % genSize; + return val; +} + +// TODO: move all performance tests into a separate project!? +template +QPI::uint64 testCollectionPerformance( + QPI::Collection& coll, + const QPI::uint64 povs, + const QPI::sint64* genBuffer, + const QPI::uint64 genSize, + const QPI::uint64 genSeed, + const QPI::uint64 maxCleanupCounter) +{ + // add and remove entries with pseudo-random sequence + QPI::uint64 idx = genSeed % genSize; + + // test cleanup of empty collection + coll.cleanup(); + +#define GEN64 genNumber(genBuffer, genSize, idx) + + QPI::uint64 cleanupCounter = 0; + while (cleanupCounter < maxCleanupCounter) + { + int p = GEN64 % 100; + + if (p == 0) + { + // cleanup (after about 100 add/remove) + coll.cleanup(); + ++cleanupCounter; + } + + if (p < 70) + { + // add to collection (more probable than remove) + QPI::id pov(GEN64 % povs, 0, 0, 0); + if (coll.add(pov, GEN64, GEN64) == QPI::NULL_INDEX) + { + for (int i = 0; i < 10; i++) + { + p = GEN64 % 100; + if (p < 70) + { + if (coll.population() > 0) + { + coll.remove(GEN64 % coll.population()); + if (!coll.population()) + { + break; + } + } + else + { + coll.cleanup(); + ++cleanupCounter; + break; + } + } + } + } + } + else if (coll.population() > 0) + { + // remove from collection + coll.remove(GEN64 % coll.population()); + } + } + + return coll.population(); +} + +template +QPI::uint64 testCollectionPerformance( + const QPI::uint64 maxPovsCount, const QPI::uint64 maxCleanupCounter) +{ + std::mt19937_64 gen64(113377); + const QPI::uint64 genSize = 113377; + QPI::sint64 gen_buffers[genSize]; + for (QPI::uint64 i = 0; i < genSize; i++) + { + gen_buffers[i] = gen64(); + } + + QPI::Collection* coll = new QPI::Collection(); + coll->reset(); + + auto t0 = std::chrono::high_resolution_clock::now(); + + for (int i = 0; i < 333; ++i) + { + testCollectionPerformance(*coll, + maxPovsCount, gen_buffers, genSize, i + 11, maxCleanupCounter); + } + + auto t1 = std::chrono::high_resolution_clock::now(); + auto duration = t1 - t0; + auto ms = std::chrono::duration_cast(t1 - t0); + + delete coll; + + return ms.count(); +} + +TEST(TestCoreQPI, CollectionPerformance) +{ + + commonBuffers.init(1, 16 * 1024 * 1024); + + std::vector durations; + std::vector descriptions; + + durations.push_back(testCollectionPerformance<1024>(128, 333)); + descriptions.push_back("[CollectionPerformance] Collection<1024>(128, 333)"); + + durations.push_back(testCollectionPerformance<1024>(64, 333)); + descriptions.push_back("[CollectionPerformance] Collection<1024>(64, 333)"); + + durations.push_back(testCollectionPerformance<1024>(32, 333)); + descriptions.push_back("[CollectionPerformance] Collection<1024>(32, 333)"); + + durations.push_back(testCollectionPerformance<1024>(16, 333)); + descriptions.push_back("[CollectionPerformance] Collection<1024>(16, 333)"); + + durations.push_back(testCollectionPerformance<512>(128, 333)); + descriptions.push_back("[CollectionPerformance] Collection<512>(128, 333)"); + + durations.push_back(testCollectionPerformance<512>(64, 333)); + descriptions.push_back("[CollectionPerformance] Collection<512>(64, 333)"); + + durations.push_back(testCollectionPerformance<512>(32, 333)); + descriptions.push_back("[CollectionPerformance] Collection<512>(32, 333)"); + + durations.push_back(testCollectionPerformance<512>(16, 333)); + descriptions.push_back("[CollectionPerformance] Collection<512>(16, 333)"); + + commonBuffers.deinit(); + + bool verbose = true; + if (verbose) + { + QPI::uint64 total = 0; + for (size_t i = 0; i < durations.size(); i++) + { + total += durations[i]; + std::cout << "- " << descriptions[i] << ":\t" << durations[i] << " ms\n"; + } + std::cout << "* [CollectionPerformance] Total:\t\t" << total << " ms\n"; + } +} diff --git a/test/qpi_date_time.cpp b/test/qpi_date_time.cpp new file mode 100644 index 000000000..654d02bd4 --- /dev/null +++ b/test/qpi_date_time.cpp @@ -0,0 +1,589 @@ +#define NO_UEFI +#include "gtest/gtest.h" + +#include + +// workaround for name clash with stdlib +#define system qubicSystemStruct + +#include "contract_core/contract_def.h" +#include "contract_core/contract_exec.h" +#include "contract_core/qpi_spectrum_impl.h" + +#include "../src/contract_core/qpi_ticking_impl.h" + +#include + +::std::ostream& operator<<(::std::ostream& os, const DateAndTime& dt) +{ + std::ios_base::fmtflags f(os.flags()); + os << std::setfill('0') << dt.getYear() << "-" + << std::setw(2) << (int)dt.getMonth() << "-" + << std::setw(2) << (int)dt.getDay() << " " + << std::setw(2) << (int)dt.getHour() << ":" + << std::setw(2) << (int)dt.getMinute() << ":" + << std::setw(2) << (int)dt.getSecond() << "." + << std::setw(3) << dt.getMillisec() << "'" + << std::setw(3) << dt.getMicrosecDuringMillisec(); + os.flags(f); + return os; +} + +TEST(DateAndTimeTest, IsLeapYear) +{ + EXPECT_TRUE(DateAndTime::isLeapYear(1600)); + EXPECT_FALSE(DateAndTime::isLeapYear(1700)); + EXPECT_FALSE(DateAndTime::isLeapYear(1800)); + EXPECT_FALSE(DateAndTime::isLeapYear(1900)); + EXPECT_TRUE(DateAndTime::isLeapYear(2000)); + EXPECT_FALSE(DateAndTime::isLeapYear(2019)); + EXPECT_TRUE(DateAndTime::isLeapYear(2020)); + EXPECT_FALSE(DateAndTime::isLeapYear(2021)); + EXPECT_FALSE(DateAndTime::isLeapYear(2022)); + EXPECT_FALSE(DateAndTime::isLeapYear(2023)); + EXPECT_TRUE(DateAndTime::isLeapYear(2024)); + EXPECT_FALSE(DateAndTime::isLeapYear(2025)); + EXPECT_FALSE(DateAndTime::isLeapYear(2026)); + EXPECT_FALSE(DateAndTime::isLeapYear(2027)); + EXPECT_TRUE(DateAndTime::isLeapYear(2028)); + EXPECT_FALSE(DateAndTime::isLeapYear(2029)); + EXPECT_FALSE(DateAndTime::isLeapYear(2030)); + EXPECT_FALSE(DateAndTime::isLeapYear(2031)); + EXPECT_TRUE(DateAndTime::isLeapYear(2032)); + EXPECT_FALSE(DateAndTime::isLeapYear(2100)); + EXPECT_FALSE(DateAndTime::isLeapYear(2200)); + EXPECT_TRUE(DateAndTime::isLeapYear(2400)); + EXPECT_FALSE(DateAndTime::isLeapYear(2500)); + EXPECT_TRUE(DateAndTime::isLeapYear(2800)); + EXPECT_TRUE(DateAndTime::isLeapYear(3200)); +} + +TEST(DateAndTimeTest, IsValid) +{ + // Checking year + EXPECT_TRUE(DateAndTime::isValid(0, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2100, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(65535, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(65536, 1, 1, 0, 0, 0, 0, 0)); + + // Checking month + EXPECT_FALSE(DateAndTime::isValid(2025, 0, 1, 0, 0, 0, 0, 0)); + for (int i = 1; i <= 12; ++i) + EXPECT_TRUE(DateAndTime::isValid(2025, i, 1, 0, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 13, 1, 0, 0, 0, 0, 0)); + + // Checking day range + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 0, 0, 0, 0, 0, 0)); + for (int i = 1; i <= 31; ++i) + EXPECT_TRUE(DateAndTime::isValid(2025, 1, i, 0, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 32, 0, 0, 0, 0, 0)); + + // Checking last day of month (except Feb) + int daysPerMonth[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + for (int i = 0; i < 12; ++i) + { + if (daysPerMonth[i]) + { + EXPECT_TRUE(DateAndTime::isValid(2025, i + 1, daysPerMonth[i], 0, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, i + 1, daysPerMonth[i] + 1, 0, 0, 0, 0, 0)); + } + } + + // Checking last day of February + for (int year = 1582; year < 3000; ++year) + { + int daysInFeb = (DateAndTime::isLeapYear(year)) ? 29 : 28; + EXPECT_TRUE(DateAndTime::isValid(year, 2, daysInFeb, 0, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(year, 2, daysInFeb + 1, 0, 0, 0, 0, 0)); + } + + // Checking hour + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 3, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 14, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 23, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 24, 0, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 25, 0, 0, 0, 0)); + + // Checking minute + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 49, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 59, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 60, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 61, 0, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 101, 0, 0, 0)); + + // Checking second + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 49, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 59, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 60, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 61, 0, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 101, 0, 0)); + + // Checking millisec + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 999, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 1000, 0)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 1002, 0)); + + // Checking microsec + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 999)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 1000)); + EXPECT_FALSE(DateAndTime::isValid(2025, 1, 1, 0, 0, 0, 0, 1002)); +} + +TEST(DateAndTimeTest, SetAndGet) +{ + // Default constructor (invalid value) + DateAndTime d1; + EXPECT_FALSE(d1.isValid()); + EXPECT_EQ((int)d1.getYear(), 0); + EXPECT_EQ(d1.getMonth(), 0); + EXPECT_EQ(d1.getDay(), 0); + EXPECT_EQ(d1.getHour(), 0); + EXPECT_EQ(d1.getMinute(), 0); + EXPECT_EQ(d1.getSecond(), 0); + EXPECT_EQ((int)d1.getMillisec(), 0); + EXPECT_EQ((int)d1.getMicrosecDuringMillisec(), 0); + + // Set if valid + EXPECT_FALSE(d1.setIfValid(2025, 0, 0, 0, 0, 0)); + EXPECT_TRUE(d1.setIfValid(2025, 1, 2, 3, 4, 5, 6, 7)); + EXPECT_EQ((int)d1.getYear(), 2025); + EXPECT_EQ(d1.getMonth(), 1); + EXPECT_EQ(d1.getDay(), 2); + EXPECT_EQ(d1.getHour(), 3); + EXPECT_EQ(d1.getMinute(), 4); + EXPECT_EQ(d1.getSecond(), 5); + EXPECT_EQ((int)d1.getMillisec(), 6); + EXPECT_EQ((int)d1.getMicrosecDuringMillisec(), 7); + + // Copy constructor + DateAndTime d2(d1); + EXPECT_EQ((int)d2.getYear(), 2025); + EXPECT_EQ(d2.getMonth(), 1); + EXPECT_EQ(d2.getDay(), 2); + EXPECT_EQ(d2.getHour(), 3); + EXPECT_EQ(d2.getMinute(), 4); + EXPECT_EQ(d2.getSecond(), 5); + EXPECT_EQ((int)d2.getMillisec(), 6); + EXPECT_EQ((int)d2.getMicrosecDuringMillisec(), 7); + + // Set edge case values (invalid as date but good for checking if + // bit-level processing works) + d2.set(65535, 15, 31, 31, 63, 63, 1023, 1023); + EXPECT_FALSE(d2.isValid()); + EXPECT_EQ((int)d2.getYear(), 65535); + EXPECT_EQ(d2.getMonth(), 15); + EXPECT_EQ(d2.getDay(), 31); + EXPECT_EQ(d2.getHour(), 31); + EXPECT_EQ(d2.getMinute(), 63); + EXPECT_EQ(d2.getSecond(), 63); + EXPECT_EQ((int)d2.getMillisec(), 1023); + EXPECT_EQ((int)d2.getMicrosecDuringMillisec(), 1023); + + // Operator = + EXPECT_NE(d1, d2); + d2 = d1; + EXPECT_EQ(d1, d2); + + // Test setTime() and setDate(), which runs without checking validity + EXPECT_EQ(d1, DateAndTime(2025, 1, 2, 3, 4, 5, 6, 7)); + d1.setDate(65535, 15, 31); + EXPECT_EQ(d1, DateAndTime(65535, 15, 31, 3, 4, 5, 6, 7)); + d1.setTime(31, 63, 63, 1023, 1023); + EXPECT_EQ(d1, DateAndTime(65535, 15, 31, 31, 63, 63, 1023, 1023)); + d1.setDate(2030, 7, 10); + EXPECT_EQ(d1, DateAndTime(2030, 7, 10, 31, 63, 63, 1023, 1023)); + d1.setTime(20, 15, 16, 457, 738); + EXPECT_EQ(d1, DateAndTime(2030, 7, 10, 20, 15, 16, 457, 738)); +} + +TEST(DateAndTimeTest, Equality) +{ + DateAndTime d1{ 2024, 3, 3, 8, 15, 30, 500 }; // 2024-03-03 8:15:30.500 + DateAndTime d2{ 2024, 3, 3, 8, 15, 30, 500 }; + DateAndTime d3{ 2024, 3, 3, 8, 15, 30, 501 }; // Different millisecond + DateAndTime d4{ 2025, 3, 3, 8, 15, 30, 500 }; // Different year + + EXPECT_EQ(d1, d2); + EXPECT_EQ(d1, d1); + EXPECT_NE(d1, d3); + EXPECT_NE(d1, d4); +} + +TEST(DateAndTimeTest, Comparison) +{ + DateAndTime sorted[] = { + { 2024, 6, 1, 0, 0, 0 }, // June 1, 2024, 00:00:00.0 + { 2025, 5, 1, 0, 0, 0 }, // May 1, 2025, 00:00:00.0 + { 2025, 5, 29, 12, 0, 0 }, + { 2025, 6, 1, 10, 0, 0 }, + { 2025, 6, 1, 10, 10, 0 }, + { 2025, 6, 1, 10, 10, 10 }, + { 2025, 6, 1, 12, 0, 0 }, // June 1, 2025, 12:00:00.0 + { 2025, 6, 1, 12, 0, 0, 0, 999 }, // June 1, 2025, 12:00:00.000999 + { 2025, 6, 1, 12, 0, 0, 1, 0 }, // June 1, 2025, 12:00:00.001000 + { 2025, 6, 1, 12, 0, 1 }, // June 1, 2025, 12:00:01.0 + { 2025, 6, 1, 12, 1, 0 }, // June 1, 2025, 12:01:00.0 + { 2025, 6, 1, 13, 0, 0 }, // June 1, 2025, 13:01:00.0 + { 2025, 6, 2, 10, 0, 0 }, // June 2, 2025, 10:00:00.0 + { 2025, 7, 1, 10, 0, 0 }, // July 1, 2025, 10:00:00.0 + { 2026, 6, 1, 10, 0, 0 }, // June 1, 2026, 10:00:00.0 + }; + const int count = sizeof(sorted) / sizeof(sorted[0]); + + for (int i = 0; i < count; ++i) + { + for (int j = i; j < count; ++j) + { + if (i == j) + { + EXPECT_EQ(sorted[i], sorted[j]); + } + else + { + EXPECT_LT(sorted[i], sorted[j]); + EXPECT_GT(sorted[j], sorted[i]); + } + } + } +} + +TEST(DateAndTimeTest, Addition) +{ + // changing date only (using day count) + DateAndTime d1{ 2025, 1, 1 }; + EXPECT_TRUE(d1.add(0, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1)); + EXPECT_TRUE(d1.add(0, 0, 10)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 11)); + EXPECT_TRUE(d1.add(0, 0, -6)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 5)); + EXPECT_TRUE(d1.add(0, 0, 26)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 31)); + EXPECT_TRUE(d1.add(0, 0, 15)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 15)); + EXPECT_TRUE(d1.add(0, 0, 15)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 2)); + EXPECT_TRUE(d1.add(0, 0, -2)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, -13)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 15)); + EXPECT_TRUE(d1.add(0, 0, -20)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 26)); + EXPECT_TRUE(d1.add(0, 0, -27)); + EXPECT_EQ(d1, DateAndTime(2024, 12, 30)); + EXPECT_TRUE(d1.add(0, 0, 31)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 30)); + EXPECT_TRUE(d1.add(0, 0, 31)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 2)); + EXPECT_TRUE(d1.add(0, 0, -31)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 30)); + EXPECT_TRUE(d1.add(0, 0, 52)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 23)); + EXPECT_TRUE(d1.add(0, 0, 70)); + EXPECT_EQ(d1, DateAndTime(2025, 6, 1)); + EXPECT_TRUE(d1.add(0, 0, -122)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 30)); + EXPECT_TRUE(d1.add(0, 0, 132)); + EXPECT_EQ(d1, DateAndTime(2025, 6, 11)); + EXPECT_TRUE(d1.add(0, 0, -162)); + EXPECT_EQ(d1, DateAndTime(2024, 12, 31)); + EXPECT_TRUE(d1.add(0, 0, -35)); + EXPECT_EQ(d1, DateAndTime(2024, 11, 26)); + EXPECT_TRUE(d1.add(0, 0, -25)); + EXPECT_EQ(d1, DateAndTime(2024, 11, 1)); + EXPECT_TRUE(d1.add(0, 0, 60)); + EXPECT_EQ(d1, DateAndTime(2024, 12, 31)); + EXPECT_TRUE(d1.add(0, 0, -140)); + EXPECT_EQ(d1, DateAndTime(2024, 8, 13)); + EXPECT_TRUE(d1.add(0, 0, -49)); + EXPECT_EQ(d1, DateAndTime(2024, 6, 25)); + EXPECT_TRUE(d1.add(0, 0, 190)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1)); + EXPECT_TRUE(d1.add(0, 0, -200)); + EXPECT_EQ(d1, DateAndTime(2024, 6, 15)); + EXPECT_TRUE(d1.add(0, 0, 220)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 21)); + EXPECT_TRUE(d1.add(0, 0, -220)); + EXPECT_EQ(d1, DateAndTime(2024, 6, 15)); + EXPECT_TRUE(d1.add(0, 0, -21)); + EXPECT_EQ(d1, DateAndTime(2024, 5, 25)); + EXPECT_TRUE(d1.add(0, 0, -50)); + EXPECT_EQ(d1, DateAndTime(2024, 4, 5)); + EXPECT_TRUE(d1.add(0, 0, 70)); + EXPECT_EQ(d1, DateAndTime(2024, 6, 14)); + EXPECT_TRUE(d1.add(0, 0, -80)); + EXPECT_EQ(d1, DateAndTime(2024, 3, 26)); + EXPECT_TRUE(d1.add(0, 0, -26)); + EXPECT_EQ(d1, DateAndTime(2024, 2, 29)); + EXPECT_TRUE(d1.add(0, 0, 1)); + EXPECT_EQ(d1, DateAndTime(2024, 3, 1)); + EXPECT_TRUE(d1.add(0, 0, -2)); + EXPECT_EQ(d1, DateAndTime(2024, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, 366)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, -366)); + EXPECT_EQ(d1, DateAndTime(2024, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, 366 + 365)); + EXPECT_EQ(d1, DateAndTime(2026, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, 1)); + EXPECT_EQ(d1, DateAndTime(2026, 3, 1)); + EXPECT_TRUE(d1.add(0, 0, -1)); + EXPECT_EQ(d1, DateAndTime(2026, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, -365 - 366 - 365)); + EXPECT_EQ(d1, DateAndTime(2023, 2, 28)); + d1.setDate(2025, 1, 31); + EXPECT_TRUE(d1.add(0, 0, 31)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 3)); + d1.setDate(2025, 12, 31); + EXPECT_TRUE(d1.add(0, 0, 31)); + EXPECT_EQ(d1, DateAndTime(2026, 1, 31)); + d1.setDate(2025, 3, 31); + EXPECT_TRUE(d1.add(0, 0, -31)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); + d1.setDate(2024, 2, 28); + EXPECT_TRUE(d1.add(0, 0, 366)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, -366)); + EXPECT_EQ(d1, DateAndTime(2024, 2, 28)); + d1.setDate(2024, 2, 29); + EXPECT_TRUE(d1.add(0, 0, 365)); + EXPECT_EQ(d1, DateAndTime(2025, 2, 28)); + EXPECT_TRUE(d1.add(0, 0, -365)); + EXPECT_EQ(d1, DateAndTime(2024, 2, 29)); + d1.setDate(2024, 2, 29); + EXPECT_TRUE(d1.add(0, 0, 366)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); + EXPECT_TRUE(d1.add(0, 0, -366)); + EXPECT_EQ(d1, DateAndTime(2024, 2, 29)); + d1.setDate(2024, 3, 1); + EXPECT_TRUE(d1.add(0, 0, 365)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); + EXPECT_TRUE(d1.add(0, 0, -365)); + EXPECT_EQ(d1, DateAndTime(2024, 3, 1)); + d1.setDate(2024, 3, 1); + EXPECT_TRUE(d1.add(0, 0, 366)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 2)); + EXPECT_TRUE(d1.add(0, 0, -366)); + EXPECT_EQ(d1, DateAndTime(2024, 3, 1)); + + // changing date only using months count + d1.setDate(2025, 10, 31); + EXPECT_TRUE(d1.add(0, 1, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 12, 1)); + EXPECT_TRUE(d1.add(0, -1, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 11, 1)); + EXPECT_TRUE(d1.add(0, 5, 0)); + EXPECT_EQ(d1, DateAndTime(2026, 4, 1)); + EXPECT_TRUE(d1.add(0, -6, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 10, 1)); + EXPECT_TRUE(d1.add(0, 9, 0)); + EXPECT_EQ(d1, DateAndTime(2026, 7, 1)); + EXPECT_TRUE(d1.add(0, -12, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 7, 1)); + EXPECT_TRUE(d1.add(0, 12, 0)); + EXPECT_EQ(d1, DateAndTime(2026, 7, 1)); + EXPECT_TRUE(d1.add(0, -18, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1)); + EXPECT_TRUE(d1.add(0, 18, 0)); + EXPECT_EQ(d1, DateAndTime(2026, 7, 1)); + EXPECT_TRUE(d1.add(0, -24, 0)); + EXPECT_EQ(d1, DateAndTime(2024, 7, 1)); + EXPECT_TRUE(d1.add(0, 36, 0)); + EXPECT_EQ(d1, DateAndTime(2027, 7, 1)); + d1.setDate(2024, 2, 29); + EXPECT_TRUE(d1.add(0, -12, 0)); + EXPECT_EQ(d1, DateAndTime(2023, 3, 1)); + d1.setDate(2024, 2, 29); + EXPECT_TRUE(d1.add(0, 12, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); + + // changing date only using year count + d1.setDate(2025, 10, 31); + EXPECT_TRUE(d1.add(100, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2125, 10, 31)); + EXPECT_TRUE(d1.add(-200, 0, 0)); + EXPECT_EQ(d1, DateAndTime(1925, 10, 31)); + d1.setDate(2024, 2, 29); + EXPECT_TRUE(d1.add(1, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2025, 3, 1)); + d1.setDate(2024, 2, 29); + EXPECT_TRUE(d1.add(-3, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2021, 3, 1)); + + // change time and date + d1 = DateAndTime{2025, 1, 1, 12, 0, 0, 0, 0}; + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 1)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 0, 1)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 3500)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 3, 501)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 3, 500)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -2500)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 1, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 0, 999)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 2)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 1, 1)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -10)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 0, 991)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1000)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 11, 59, 59, 999, 991)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 1009)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 0, 0, 1, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -1001000)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 11, 59, 59, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 61 * 1000000)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 12, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 60 * 60 * 1000000ll)); + EXPECT_EQ(d1, DateAndTime(2025, 1, 1, 13, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, -14 * 60 * 60 * 1000000ll)); + EXPECT_EQ(d1, DateAndTime(2024, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 365 * 24 * 60 * 60 * 1000000ll)); + EXPECT_EQ(d1, DateAndTime(2025, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 365 * 24 * 60 * 60 * 1000ll)); + EXPECT_EQ(d1, DateAndTime(2026, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 365 * 24 * 60 * 60)); + EXPECT_EQ(d1, DateAndTime(2027, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 366 * 24 * 60, 0)); + EXPECT_EQ(d1, DateAndTime(2028, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 365 * 24, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2029, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 365, 0, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 12, 0, 0, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2031, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(1, 0, 0, 0, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2032, 12, 31, 23, 1, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, -10, -3, 0, -1, 0)); + EXPECT_EQ(d1, DateAndTime(2032, 2, 28, 23, 0, 0, 0, 0)); + EXPECT_TRUE(d1.add(0, 0, 1, 0, 59, 59, 999, 999)); + EXPECT_EQ(d1, DateAndTime(2032, 2, 29, 23, 59, 59, 999, 999)); + EXPECT_TRUE(d1.add(-1, 0, 0, 0, 0, 0, 0, 0)); + EXPECT_EQ(d1, DateAndTime(2031, 3, 1, 23, 59, 59, 999, 999)); + EXPECT_TRUE(d1.add(0, -1, -29, 0, 2, -120, 1, -1999)); + EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 23, 59, 59, 999, 0)); + EXPECT_TRUE(d1.add(0, 0, 0, 0, 0, 0, 0, 1000)); + EXPECT_EQ(d1, DateAndTime(2031, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(d1.add(1, -12, 2, -48, 2, -120, 10, -10000)); + EXPECT_EQ(d1, DateAndTime(2031, 1, 1, 0, 0, 0, 0, 0)); + EXPECT_TRUE(d1.add(-3, 36, -1, 24, -3, 180, -5, 4999)); + EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 23, 59, 59, 999, 999)); + + // test addDays() and addMicrosec() helpers + EXPECT_TRUE(d1.addDays(15)); + EXPECT_EQ(d1, DateAndTime(2031, 1, 15, 23, 59, 59, 999, 999)); + EXPECT_TRUE(d1.addDays(-16)); + EXPECT_EQ(d1, DateAndTime(2030, 12, 30, 23, 59, 59, 999, 999)); + EXPECT_TRUE(d1.addMicrosec(2)); + EXPECT_EQ(d1, DateAndTime(2030, 12, 31, 0, 0, 0, 0, 1)); + EXPECT_TRUE(d1.addMicrosec(-2002)); + EXPECT_EQ(d1, DateAndTime(2030, 12, 30, 23, 59, 59, 997, 999)); + + // test speedup of adding large number of days + d1.set(2000, 1, 1, 0, 0, 0); + EXPECT_TRUE(d1.addDays(366)); + EXPECT_EQ(d1, DateAndTime(2001, 1, 1, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(-366 - 365)); + EXPECT_EQ(d1, DateAndTime(1999, 1, 1, 0, 0, 0)); + d1.set(2000, 3, 1, 0, 0, 0); + EXPECT_TRUE(d1.addDays(365)); + EXPECT_EQ(d1, DateAndTime(2001, 3, 1, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(-365)); + EXPECT_EQ(d1, DateAndTime(2000, 3, 1, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(-366)); + EXPECT_EQ(d1, DateAndTime(1999, 3, 1, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(3 * 366 + 7 * 365)); // leap years: 2000, 2004, 2008 + EXPECT_EQ(d1, DateAndTime(2009, 3, 1, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(7 * 366 + 23 * 365)); // leap years: 2012, 2016, 2020, 2024, 2028, 2032, 2036 + EXPECT_EQ(d1, DateAndTime(2039, 3, 1, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(-(3 * 366 + 12 * 365))); // leap years: 2028, 2032, 2036 (2024 not included due to date after Feb) + EXPECT_EQ(d1, DateAndTime(2024, 3, 1, 0, 0, 0)); + d1.set(2039, 2, 28, 0, 0, 0); + EXPECT_TRUE(d1.addDays(-(4 * 366 + 11 * 365))); // leap years: 2024, 2028, 2032, 2036 + EXPECT_EQ(d1, DateAndTime(2024, 2, 28, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(97 * 366 + 303 * 365)); // 400 years always have the same number of leap years + EXPECT_EQ(d1, DateAndTime(2424, 2, 28, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(2 * 366 + 3 * 365)); // leap years: 2424, 2028 + EXPECT_EQ(d1, DateAndTime(2429, 2, 28, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(97 * 366 + 304 * 365)); // 400 years always have the same number of leap years + 1 year + EXPECT_EQ(d1, DateAndTime(2830, 2, 28, 0, 0, 0)); + EXPECT_TRUE(d1.addDays(-3 * (97 * 366 + 303 * 365))); // 400 years always have the same number of leap years + EXPECT_EQ(d1, DateAndTime(1630, 2, 28, 0, 0, 0)); + EXPECT_TRUE(d1.addDays((97 * 366 + 303 * 365) + 365 + 1)); // + 400 years + 1 year (2031) + 1 day + EXPECT_EQ(d1, DateAndTime(2031, 3, 1, 0, 0, 0)); + + // test some error cases + EXPECT_FALSE(d1.addDays(366 * 66000)); + EXPECT_FALSE(d1.add(INT64_MAX - 1000, 0, 0)); + EXPECT_FALSE(d1.add(0, INT64_MAX, 0)); + d1.setTime(0, 0, 0, 0, 999); + EXPECT_FALSE(d1.add(0, 0, 0, 0, 0, 0, 0, INT64_MAX - 998)); + EXPECT_FALSE(d1.add(0, 0, 0, 0, 0, 0, INT64_MIN, INT64_MIN)); +} + +uint64 microSeconds(uint64 days, uint64 hours, uint64 minutes, uint64 seconds, uint64 milli, uint64 micro) +{ + return (((((((days * 24) + hours) * 60llu) + minutes) * 60 + seconds) * 1000) + milli) * 1000 + micro; +} + +TEST(DateAndTimeTest, Subtraction) +{ + DateAndTime d0; + DateAndTime d1(2030, 12, 31, 5, 4, 3, 2, 1); + EXPECT_EQ(d0.durationMicrosec(d0), UINT64_MAX); + EXPECT_EQ(d0.durationMicrosec(d1), UINT64_MAX); + EXPECT_EQ(d1.durationMicrosec(d0), UINT64_MAX); + + DateAndTime d2(2030, 12, 31, 0, 0, 0, 0, 0); + EXPECT_EQ(d1.durationMicrosec(d1), 0); + EXPECT_EQ(d1.durationMicrosec(d2), d2.durationMicrosec(d1)); + EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(0, 5, 4, 3, 2, 1)); + EXPECT_EQ(d1.durationDays(d2), 0); + + d1.set(2030, 12, 31, 23, 59, 59, 999, 999); + d2.set(2031, 1, 1, 0, 0, 0, 0, 0); + EXPECT_EQ(d1.durationMicrosec(d2), 1); + EXPECT_EQ(d1.durationDays(d2), 0); + + d1.set(2025, 1, 1, 0, 0, 0, 0, 0); + d2.set(2027, 1, 1, 0, 0, 0, 0, 0); + EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(2 * 365, 0, 0, 0, 0, 0)); + EXPECT_EQ(d1.durationDays(d2), 2 * 365); + + d1.set(2027, 1, 1, 12, 15, 43, 123, 456); + d2.set(2025, 1, 1, 11, 10, 34, 101, 412); + EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(2 * 365, 1, 5, 9, 22, 44)); + EXPECT_EQ(d1.durationDays(d2), 2 * 365); + + d1.set(2027, 1, 1, 12, 15, 43, 123, 456); + d2.set(2024, 1, 1, 11, 10, 34, 101, 412); + EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(366 + 2 * 365, 1, 5, 9, 22, 44)); + EXPECT_EQ(d2.durationMicrosec(d1), microSeconds(366 + 2 * 365, 1, 5, 9, 22, 44)); + EXPECT_EQ(d1.durationDays(d2), 366 + 2 * 365); + EXPECT_EQ(d2.durationDays(d1), 366 + 2 * 365); + + d2.set(2024, 1, 1, 0, 0, 0, 0, 0); + d1.set(2024, 4, 1, 0, 0, 0, 0, 42); + EXPECT_EQ(d1.durationMicrosec(d2), microSeconds(31 + 29 + 31, 0, 0, 0, 0, 42)); + EXPECT_EQ(d1.durationDays(d2), 31 + 29 + 31); + + std::mt19937_64 gen64(42); + for (int i = 0; i < 1000; ++i) + { + d1.set((gen64() % 3000) + 1500, (gen64() % 12) + 1, (gen64() % 28) + 1, gen64() % 24, + gen64() % 60, gen64() % 60, gen64() % 999, gen64() % 999); + EXPECT_TRUE(d1.isValid()); + + sint64 microsec = (sint64)gen64() % (1000llu * 365 * 24 * 60 * 60 * 1000 * 1000); + d2 = d1; + EXPECT_TRUE(d2.addMicrosec(microsec)); + EXPECT_TRUE(d2.isValid()); + + EXPECT_EQ(d2.durationMicrosec(d1), microsec); + } +} diff --git a/test/qpi_hash_map.cpp b/test/qpi_hash_map.cpp new file mode 100644 index 000000000..b28dc2e7d --- /dev/null +++ b/test/qpi_hash_map.cpp @@ -0,0 +1,1009 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#include "../src/contract_core/pre_qpi_def.h" +#include "../src/contracts/qpi.h" +#include "../src/common_buffers.h" +#include "../src/contract_core/qpi_hash_map_impl.h" +#include +#include +#include +#include +#include +#include + + +// New KeyT, ValueT combinations for testing need to implement the following functions: +template +struct HashMapTestData +{ + static std::array, 4> CreateKeyValueTestPairs(); + static inline KeyT GetKeyNotInTestPairs(); + static inline ValueT GetValueNotInTestPairs(); +}; + +// Test data for KeyT = QPI::id, ValueT = int. +template<> +std::array, 4> HashMapTestData::CreateKeyValueTestPairs() +{ + std::array, 4> res = { { + {{ 87, 456, 29, 823}, 17 }, + {{ 897, 23, 64, 90 }, 5467}, + {{5478, 908, 123, 45}, 8752}, + {{ 143, 746, 87, 6 }, 5348}, + } }; + return res; +} + +template<> +inline QPI::id HashMapTestData::GetKeyNotInTestPairs() +{ + return { 1,2,3,4 }; +} + +template<> +inline int HashMapTestData::GetValueNotInTestPairs() +{ + return 42; +} + +// Test data for KeyT = QPI::sint64, ValueT = char. +template<> +std::array, 4> HashMapTestData::CreateKeyValueTestPairs() +{ + std::array, 4> res = { { + { -564, 'a'}, + { 67, 'z'}, + { -783, 'f'}, + { 8924, 'h'}, + } }; + return res; +} + +template<> +inline QPI::sint64 HashMapTestData::GetKeyNotInTestPairs() +{ + return 1234; +} + +template<> +inline char HashMapTestData::GetValueNotInTestPairs() +{ + return '*'; +} + + +// testdata for KeyT = QPI::BitArray<1024>, ValueT = uint64 +template<> +std::array, 4> HashMapTestData::CreateKeyValueTestPairs() +{ + // Create a properly sized array to work with BitArray's setMem + alignas(32) unsigned char buffer[128] = {0}; // 1024 bits = 128 bytes + + // Helper lambda to set a string pattern in bit_1024 + auto setStringKey = [](QPI::bit_1024& bits, const std::string& str) { + bits.setAll(0); // Clear all bits first + // Each character becomes 8 bits + for(size_t i = 0; i < str.length() && i < 128; i++) // 128 is max bytes (1024/8) + { + unsigned char c = str[i]; + // Set 8 bits for this character + for(int bit = 0; bit < 8; bit++) + { + bits.set(i * 8 + bit, (c & (1 << bit)) != 0); + } + } + }; + + QPI::bit_1024 key1, key2, key3, key4; + setStringKey(key1, "TestString1"); + setStringKey(key2, "AnotherTest2"); + setStringKey(key3, "ThirdString3"); + setStringKey(key4, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_-+={}[]|;:\"\'<>,.?/~`abcdefghijklmnopqrstuvwxyzABCDEFGH"); + + std::array, 4> res; + res = { { + { key1, 12}, + { key2, 99}, + { key3, 723}, + { key4, 0}, + } }; + return res; +} + +template<> +inline QPI::bit_1024 HashMapTestData::GetKeyNotInTestPairs() +{ + alignas(32) unsigned char buffer[128] = {0}; + QPI::bit_1024 key; + std::string str = "NotInTestPairs"; + for(size_t i = 0; i < str.length() && i < 128; i++) + { + unsigned char c = str[i]; + for(int bit = 0; bit < 8; bit++) + { + key.set(i * 8 + bit, (c & (1 << bit)) != 0); + } + } + return key; +} + +template<> +inline QPI::uint64 HashMapTestData::GetValueNotInTestPairs() +{ + return 42; +} + +// Define the test fixture class with a single template parameter T because the type list +// for test instantiation will contain pair-types std::pair to use for T. +template +class QPIHashMapTest : public testing::Test {}; + +using testing::Types; + +TYPED_TEST_CASE_P(QPIHashMapTest); + + +TEST(NonTypedQPIHashMapTest, TestHashFunctionQPIID) +{ + auto randomId = QPI::id::randomValue(); + + // Check that the hash function returns the first 8 bytes as hash for QPI::id. + QPI::uint64 hashRes = QPI::HashFunction::hash(randomId); + EXPECT_EQ(hashRes, randomId.u64._0); +} + +TEST(NonTypedQPIHashMapTest, TestHashFunction) +{ + std::unordered_set hashesSoFar; + + for (int i = 0; i < 1000; ++i) + { + // We expect the hash function to produce different hashes for 0...N. + QPI::uint64 hashRes = QPI::HashFunction::hash(i); + EXPECT_FALSE(hashesSoFar.contains(hashRes)); + hashesSoFar.insert(hashRes); + } +} + +TYPED_TEST_P(QPIHashMapTest, TestCreation) +{ + constexpr QPI::uint64 capacity = 2; + QPI::HashMap hashMap; + + EXPECT_EQ(hashMap.capacity(), capacity); + EXPECT_EQ(hashMap.population(), 0); +} + +TYPED_TEST_P(QPIHashMapTest, TestGetters) +{ + constexpr QPI::uint64 capacity = 4; + QPI::HashMap hashMap; + + typedef HashMapTestData TestData; + + std::array keyValuePairs = TestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + QPI::sint64 returnedIndex; + + EXPECT_EQ(hashMap.getElementIndex(ids[3]), QPI::NULL_INDEX); + EXPECT_FALSE(hashMap.contains(ids[3])); + EXPECT_TRUE(hashMap.isEmptySlot(0)); + + for (int i = 0; i < 4; ++i) + { + returnedIndex = hashMap.set(ids[i], values[i]); + } + + EXPECT_EQ(hashMap.getElementIndex(ids[3]), returnedIndex); + EXPECT_TRUE(hashMap.contains(ids[3])); + EXPECT_EQ(hashMap.key(returnedIndex), ids[3]); + EXPECT_EQ(hashMap.value(returnedIndex), values[3]); + EXPECT_FALSE(hashMap.isEmptySlot(returnedIndex)); + + typename TypeParam::second_type valueAfter = {}; + typename TypeParam::second_type valueBefore = valueAfter; + + EXPECT_FALSE(hashMap.get(TestData::GetKeyNotInTestPairs(), valueAfter)); + EXPECT_EQ(valueAfter, valueBefore); + + EXPECT_TRUE(hashMap.get(ids[0], valueAfter)); + EXPECT_EQ(valueAfter, values[0]); + + // Test getElementIndex when all slots are marked for removal so it has to iterate through the whole hash map. + for (int i = 0; i < 4; ++i) + { + hashMap.removeByKey(ids[i]); + } + EXPECT_EQ(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); + + EXPECT_TRUE(hashMap.isEmptySlot(0)); + + hashMap.cleanupIfNeeded(); +} + +TYPED_TEST_P(QPIHashMapTest, TestSet) +{ + constexpr QPI::uint64 capacity = 2; + QPI::HashMap hashMap; + + std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + + QPI::sint64 returnedIndex = hashMap.set(ids[0], values[0]); + EXPECT_EQ(hashMap.population(), 1); + EXPECT_GE(returnedIndex, 0); + EXPECT_LT(QPI::uint64(returnedIndex), capacity); + + // Set with existing key should overwrite the value. + EXPECT_EQ(hashMap.set(ids[0], 42), returnedIndex); + EXPECT_EQ(hashMap.value(returnedIndex), 42); + + returnedIndex = hashMap.set(ids[1], values[1]); + EXPECT_EQ(hashMap.population(), hashMap.capacity()); + + // Set when full should return NULL_INDEX for new key. + EXPECT_EQ(hashMap.set(ids[2], values[2]), QPI::NULL_INDEX); + + // Set when full should still work for existing key and replace the value. + EXPECT_EQ(hashMap.set(ids[1], values[2]), returnedIndex); + EXPECT_EQ(hashMap.value(returnedIndex), values[2]); +} + +TYPED_TEST_P(QPIHashMapTest, TestRemove) +{ + constexpr QPI::uint64 capacity = 8; + QPI::HashMap hashMap; + + typedef HashMapTestData TestData; + + std::array keyValuePairs = TestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + QPI::sint64 returnedIndex; + + for (int i = 0; i < 3; ++i) + { + returnedIndex = hashMap.set(ids[i], values[i]); + } + EXPECT_EQ(hashMap.population(), 3); + + // Remove by element index. + hashMap.removeByIndex(returnedIndex); + + EXPECT_EQ(hashMap.population(), 2); + EXPECT_NE(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); + EXPECT_NE(hashMap.getElementIndex(ids[1]), QPI::NULL_INDEX); + EXPECT_EQ(hashMap.getElementIndex(ids[2]), QPI::NULL_INDEX); + EXPECT_TRUE(hashMap.contains(ids[0])); + EXPECT_TRUE(hashMap.contains(ids[1])); + EXPECT_FALSE(hashMap.contains(ids[2])); + + // Try to remove key not contained in the hash map. + returnedIndex = hashMap.removeByKey(TestData::GetKeyNotInTestPairs()); + EXPECT_EQ(returnedIndex, QPI::NULL_INDEX); + EXPECT_EQ(hashMap.population(), 2); + + // Remove by key that is contained in the hash map. + returnedIndex = hashMap.removeByKey(ids[0]); + EXPECT_NE(returnedIndex, QPI::NULL_INDEX); + EXPECT_EQ(hashMap.population(), 1); + EXPECT_EQ(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); + EXPECT_NE(hashMap.getElementIndex(ids[1]), QPI::NULL_INDEX); + EXPECT_EQ(hashMap.getElementIndex(ids[2]), QPI::NULL_INDEX); + EXPECT_FALSE(hashMap.contains(ids[0])); + EXPECT_TRUE(hashMap.contains(ids[1])); + EXPECT_FALSE(hashMap.contains(ids[2])); +} + +TYPED_TEST_P(QPIHashMapTest, TestRemoveReuse) +{ + constexpr QPI::uint64 capacity = 4; + QPI::HashMap hashMap; + hashMap.reset(); + + typedef HashMapTestData TestData; + + const auto keyValuePairs = TestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + + // add and remove same item multiple times for testing simple case of reusing slot + for (int i = 0; i < capacity; ++i) + { + for (int j = 0; j <= i; ++j) + { + QPI::sint64 elementIndex = hashMap.set(ids[j], values[j]); + EXPECT_NE(elementIndex, QPI::NULL_INDEX); + EXPECT_FALSE(hashMap.isEmptySlot(elementIndex)); + EXPECT_EQ(hashMap.key(elementIndex), ids[j]); + EXPECT_EQ(hashMap.value(elementIndex), values[j]); + EXPECT_EQ(hashMap.population(), 1); + + hashMap.removeByIndex(elementIndex); + EXPECT_TRUE(hashMap.isEmptySlot(elementIndex)); + EXPECT_EQ(hashMap.population(), 0); + } + } + + // fill to full capacity + for (int i = capacity - 1; i >= 0; --i) + { + QPI::sint64 elementIndex = hashMap.set(ids[i], values[i]); + EXPECT_NE(elementIndex, QPI::NULL_INDEX); + EXPECT_FALSE(hashMap.isEmptySlot(elementIndex)); + EXPECT_EQ(hashMap.key(elementIndex), ids[i]); + EXPECT_EQ(hashMap.value(elementIndex), values[i]); + EXPECT_EQ(hashMap.population(), capacity - i); + } + + // adding another item fails + EXPECT_EQ(hashMap.set(TestData::GetKeyNotInTestPairs(), TestData::GetValueNotInTestPairs()), QPI::NULL_INDEX); + + // mark all items for removal + for (int i = 0; i < capacity; ++i) + { + EXPECT_NE(hashMap.removeByKey(ids[i]), QPI::NULL_INDEX); + } + EXPECT_EQ(hashMap.population(), 0); + + // lookup of non-existing key will now need to iterate through the whole hash map until next cleanup + EXPECT_FALSE(hashMap.contains(ids[0])); + + // for adding an item, set() also needs to go through the whole hash map until next cleanup + EXPECT_NE(hashMap.set(ids[0], values[0]), QPI::NULL_INDEX); + EXPECT_EQ(hashMap.population(), 1); +} + +TYPED_TEST_P(QPIHashMapTest, TestCleanup) +{ + constexpr QPI::uint64 capacity = 4; + QPI::HashMap hashMap; + + commonBuffers.init(1, 2 * sizeof(hashMap)); + + std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + QPI::sint64 returnedIndex; + + for (int i = 0; i < 4; ++i) + { + hashMap.set(ids[i], values[i]); + } + + // This will mark for removal + hashMap.removeByKey(ids[3]); + EXPECT_EQ(hashMap.population(), 3); + + // Slots marked for removal will be reused, but performance may suffer with increasing number of removes without + // calling cleanup + EXPECT_NE(hashMap.set(ids[3], values[3]), QPI::NULL_INDEX); + EXPECT_EQ(hashMap.population(), 4); + + // Remove again + hashMap.removeByKey(ids[3]); + EXPECT_EQ(hashMap.population(), 3); + + // Cleanup will properly remove the element marked for removal. + hashMap.cleanup(); + EXPECT_EQ(hashMap.population(), 3); + + EXPECT_NE(hashMap.getElementIndex(ids[0]), QPI::NULL_INDEX); + EXPECT_NE(hashMap.getElementIndex(ids[1]), QPI::NULL_INDEX); + EXPECT_NE(hashMap.getElementIndex(ids[2]), QPI::NULL_INDEX); + EXPECT_EQ(hashMap.getElementIndex(ids[3]), QPI::NULL_INDEX); + + // Regular set() without reusing slot of element marked for removal (after cleanup, faster on average) + returnedIndex = hashMap.set(ids[3], values[3]); + EXPECT_NE(returnedIndex, QPI::NULL_INDEX); + EXPECT_EQ(hashMap.population(), 4); + + commonBuffers.deinit(); +} + +TYPED_TEST_P(QPIHashMapTest, TestCleanupPerformanceShortcuts) +{ + constexpr QPI::uint64 capacity = 4; + QPI::HashMap hashMap; + + std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + + for (int i = 0; i < 4; ++i) + { + hashMap.set(ids[i], values[i]); + } + + // Test cleanup when no elements have been marked for removal. + hashMap.cleanup(); + + for (int i = 0; i < 4; ++i) + { + hashMap.removeByKey(ids[i]); + } + EXPECT_EQ(hashMap.population(), 0); + + // Test cleanup when all elements have been marked for removal. + hashMap.cleanup(); +} + +// This test is not type-parameterized because QPI::id is the only type where we can easily create different keys with the same hashes. +TEST(NonTypedQPIHashMapTest, TestCleanupLargeMapSameHashes) +{ + constexpr QPI::uint64 capacity = 64; + QPI::HashMap hashMap; + + commonBuffers.init(1, 2 * sizeof(hashMap)); + + for (QPI::uint64 i = 0; i < 64; ++i) + { + // Add 64 elements with different keys but same hash. + // The hash for QPI::id is the first 8 bytes. + hashMap.set({ 3478, i, i + 1, i + 2 }, int(i * 2 + 7)); + } + hashMap.removeByKey({ 3478, 63, 64, 65 }); + + // Cleanup will have to iterate through the whole map to find an empty slot for the last element. + hashMap.cleanup(); + + commonBuffers.deinit(); +} + +TYPED_TEST_P(QPIHashMapTest, TestReplace) +{ + constexpr QPI::uint64 capacity = 8; + QPI::HashMap hashMap; + + typedef HashMapTestData TestData; + + std::array keyValuePairs = TestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + QPI::sint64 returnedIndex; + + for (int i = 0; i < 3; ++i) + { + returnedIndex = hashMap.set(ids[i], values[i]); + } + EXPECT_EQ(hashMap.population(), 3); + + typename TypeParam::first_type newKey = TestData::GetKeyNotInTestPairs(); + typename TypeParam::second_type newValue = TestData::GetValueNotInTestPairs(); + + EXPECT_FALSE(hashMap.replace(newKey, newValue)); + + EXPECT_EQ(hashMap.population(), 3); + EXPECT_EQ(hashMap.getElementIndex(newKey), QPI::NULL_INDEX); + + EXPECT_TRUE(hashMap.replace(ids[1], newValue)); + + EXPECT_EQ(hashMap.population(), 3); + typename TypeParam::second_type valueRead = {}; + EXPECT_TRUE(hashMap.get(ids[1], valueRead)); + EXPECT_EQ(valueRead, newValue); + EXPECT_TRUE(hashMap.get(ids[0], valueRead)); + EXPECT_EQ(valueRead, values[0]); + EXPECT_TRUE(hashMap.get(ids[2], valueRead)); + EXPECT_EQ(valueRead, values[2]); +} + +TYPED_TEST_P(QPIHashMapTest, TestReset) +{ + constexpr QPI::uint64 capacity = 4; + QPI::HashMap hashMap; + + std::array keyValuePairs = HashMapTestData::CreateKeyValueTestPairs(); + auto ids = std::views::keys(keyValuePairs); + auto values = std::views::values(keyValuePairs); + + for (int i = 0; i < 4; ++i) + { + hashMap.set(ids[i], values[i]); + } + + EXPECT_EQ(hashMap.population(), 4); + hashMap.reset(); + EXPECT_EQ(hashMap.population(), 0); +} + +REGISTER_TYPED_TEST_CASE_P(QPIHashMapTest, + TestCreation, + TestGetters, + TestSet, + TestRemove, + TestRemoveReuse, + TestCleanup, + TestCleanupPerformanceShortcuts, + TestReplace, + TestReset +); + +typedef Types, std::pair, std::pair> KeyValueTypesToTest; +INSTANTIATE_TYPED_TEST_CASE_P(TypedQPIHashMapTests, QPIHashMapTest, KeyValueTypesToTest); + +template +void hasSameContent(QPI::HashMap& map, const std::map& referenceMap) +{ + EXPECT_EQ(map.population(), referenceMap.size()); + for (const auto& item : referenceMap) + { + EXPECT_TRUE(map.contains(item.first)); + ValueT value; + EXPECT_TRUE(map.get(item.first, value)); + EXPECT_EQ(value, item.second); + } + for (unsigned int i = 0; i < capacity; ++i) + { + KeyT key = map.key(i); // returns key value at slot i (like 0), even if the value is not contained + if (!map.isEmptySlot(i)) + { + // key is valid, part of map, and exactly at this position + EXPECT_NE(referenceMap.find(key), referenceMap.end()); + EXPECT_EQ(map.getElementIndex(key), i); + } + else + { + // key is invalid and not at this slot + QPI::uint64 elementIndex = map.getElementIndex(key); + EXPECT_NE(elementIndex, i); + if (elementIndex == QPI::NULL_INDEX) + { + // not in map + EXPECT_EQ(referenceMap.find(key), referenceMap.end()); + } + else + { + // in map, but at different position + EXPECT_NE(referenceMap.find(key), referenceMap.end()); + } + } + } + + // test iterator + QPI::sint64 i = map.nextElementIndex(QPI::NULL_INDEX); + unsigned int cnt = 0; + while (i != QPI::NULL_INDEX) + { + cnt++; + EXPECT_FALSE(map.isEmptySlot(i)); + auto it = referenceMap.find(map.key(i)); + EXPECT_NE(it, referenceMap.end()); + EXPECT_EQ(it->second, map.value(i)); + i = map.nextElementIndex(i); + } + EXPECT_EQ(cnt, referenceMap.size()); +} + +template +void cleanupHashMap(QPI::HashMap& map, const std::map& referenceMap) +{ + hasSameContent(map, referenceMap); + map.cleanup(); + //map.cleanupIfNeeded(); + hasSameContent(map, referenceMap); +} + +void getValue(std::mt19937_64& gen64, QPI::id& value) +{ + value.u64._0 = gen64(); + value.u64._1 = gen64(); + value.u64._2 = gen64(); + value.u64._3 = gen64(); +} + +void getValue(std::mt19937_64& gen64, QPI::uint8& value) +{ + value = gen64() & 0xff; +} + +template +void testHashMapPseudoRandom(int seed, int cleanups, int percentAdd, int percentAddSecondHalf = -1) +{ + // add and remove entries with pseudo-random sequence + std::mt19937_64 gen64(seed); + + std::map referenceMap; + QPI::HashMap map; + + commonBuffers.init(1, 2 * sizeof(map)); + + map.reset(); + + // test cleanup of empty collection + cleanupHashMap(map, referenceMap); + + if (seed & 1) + { + // add default value of empty slot to set + referenceMap[map.key(0)] = map.value(0); + EXPECT_NE(map.set(map.key(0), map.value(0)), QPI::NULL_INDEX); + hasSameContent(map, referenceMap); + } + + // Randomly add, remove, and cleanup until 50 cleanups are reached + int cleanupCounter = 0; + while (cleanupCounter < cleanups) + { + int p = gen64() % 100; + + if (p == 0) + { + // cleanup (with 1% probability) + cleanupHashMap(map, referenceMap); + ++cleanupCounter; + + if (cleanupCounter == cleanups / 2 && percentAddSecondHalf >= 0) + percentAdd = percentAddSecondHalf; + } + + if (p < percentAdd) + { + // add to map (more probable than remove) + KeyT key; + ValueT value; + getValue(gen64, key); + getValue(gen64, value); + if (map.set(key, value) != QPI::NULL_INDEX) + referenceMap[key] = value; + hasSameContent(map, referenceMap); + } + else + { + // remove from map + QPI::sint64 removeIdx = gen64() % map.capacity(); + KeyT key = map.key(removeIdx); + if (!map.isEmptySlot(removeIdx)) + { + referenceMap.erase(key); + EXPECT_EQ(map.removeByKey(key), removeIdx); + } + else if (referenceMap.find(key) == referenceMap.end()) + { + EXPECT_EQ(map.removeByKey(key), QPI::NULL_INDEX); + } + hasSameContent(map, referenceMap); + } + + // std::cout << "capacity: " << set.capacity() << ", pupulation:" << set.population() << std::endl; + } + + commonBuffers.deinit(); +} + +TEST(QPIHashMapTest, HashMapPseudoRandom) +{ + constexpr unsigned int numCleanups = 5; + testHashMapPseudoRandom(42, numCleanups, 20); + testHashMapPseudoRandom(1337, numCleanups, 80); + testHashMapPseudoRandom(42, 4 * numCleanups, 30); + testHashMapPseudoRandom(1337, 4 * numCleanups, 50); + testHashMapPseudoRandom(123456789, 4 * numCleanups, 70); + testHashMapPseudoRandom(42, 10 * numCleanups, 30, 70); + testHashMapPseudoRandom(1337, 10 * numCleanups, 50, 10); + testHashMapPseudoRandom(123456789, 10 * numCleanups, 70, 10); + testHashMapPseudoRandom(42 + 1, 6 * numCleanups, 30); + testHashMapPseudoRandom(1337 + 1, 6 * numCleanups, 50); + testHashMapPseudoRandom(123456789 + 1, 6 * numCleanups, 70); + testHashMapPseudoRandom(42 + 2, 10 * numCleanups, 30); + testHashMapPseudoRandom(1337 + 2, 10 * numCleanups, 50); + testHashMapPseudoRandom(123456789 + 2, 10 * numCleanups, 70); + testHashMapPseudoRandom(42, numCleanups, 20); + testHashMapPseudoRandom(42 + 1, 10 * numCleanups, 30, 70); + testHashMapPseudoRandom(1337, 10 * numCleanups, 50, 10); + testHashMapPseudoRandom(123456789, 10 * numCleanups, 70, 10); + testHashMapPseudoRandom(1337 + 1, 6 * numCleanups, 50); + testHashMapPseudoRandom(123456789 + 2, 10 * numCleanups, 70); +} + +TEST(QPIHashMapTest, HashSet) +{ + constexpr QPI::uint64 capacity = 128; + QPI::HashSet hashSet; + commonBuffers.init(1, 2 * sizeof(hashSet)); + EXPECT_EQ(hashSet.capacity(), capacity); + + // Test add() and contains() + for (int i = 0; i < capacity; ++i) + { + const QPI::id newId(i / 3, i + 5, i * 3, i % 10); + EXPECT_EQ(hashSet.population(), i); + auto idx = hashSet.add(newId); + EXPECT_NE(idx, QPI::NULL_INDEX); + EXPECT_EQ(hashSet.key(idx), newId); + EXPECT_FALSE(hashSet.isEmptySlot(idx)); + EXPECT_EQ(idx, hashSet.add(newId)); // adding a second time just returns same index + EXPECT_TRUE(hashSet.contains(newId)); + } + EXPECT_EQ(hashSet.population(), capacity); + EXPECT_FALSE(hashSet.contains(QPI::NULL_ID)); + EXPECT_EQ(hashSet.add(QPI::NULL_ID), QPI::NULL_INDEX); // set is full + EXPECT_FALSE(hashSet.contains(QPI::NULL_ID)); + + // Test remove() + EXPECT_EQ(hashSet.remove(QPI::NULL_ID), QPI::NULL_INDEX); + for (int i = 0; i < capacity; i += 4) + { + const QPI::id theId(i / 3, i + 5, i * 3, i % 10); + EXPECT_EQ(hashSet.population(), capacity - i / 4); + EXPECT_TRUE(hashSet.contains(theId)); + EXPECT_NE(hashSet.remove(theId), QPI::NULL_INDEX); + EXPECT_FALSE(hashSet.contains(theId)); + } + + // Check consistency + for (int i = 0; i < capacity; i++) + { + const QPI::id theId(i / 3, i + 5, i * 3, i % 10); + if (i % 4 == 0) + EXPECT_FALSE(hashSet.contains(theId)); + else + EXPECT_TRUE(hashSet.contains(theId)); + } + + // Check that it works to reuse slots of removed entries + for (int i = 0; i < capacity / 4; ++i) + { + const QPI::id newId(capacity / 4 - 1 - i, 0, 0, 0); + EXPECT_EQ(hashSet.population(), capacity * 3 / 4 + i); + auto idx = hashSet.add(newId); + EXPECT_NE(idx, QPI::NULL_INDEX); + EXPECT_EQ(hashSet.key(idx), newId); + EXPECT_FALSE(hashSet.isEmptySlot(idx)); + EXPECT_EQ(idx, hashSet.add(newId)); // adding a second time just returns same index + EXPECT_TRUE(hashSet.contains(newId)); + } + EXPECT_TRUE(hashSet.contains(QPI::id(0, 0, 0, 0))); + + // Check consistency + for (int i = 0; i < capacity; i++) + { + const QPI::id theId(i / 3, i + 5, i * 3, i % 10); + if (i % 4 == 0) + EXPECT_FALSE(hashSet.contains(theId)); + else + EXPECT_TRUE(hashSet.contains(theId)); + if (i < capacity / 4) + { + const QPI::id theId(capacity / 4 - 1 - i, 0, 0, 0); + EXPECT_TRUE(hashSet.contains(theId)); + } + } + + // Remove entries added first + for (int i = 0; i < capacity; i++) + { + const QPI::id theId(i / 3, i + 5, i * 3, i % 10); + if (i % 4 == 0) + EXPECT_EQ(hashSet.remove(theId), QPI::NULL_INDEX); // already removed before + else + EXPECT_NE(hashSet.remove(theId), QPI::NULL_INDEX); + EXPECT_FALSE(hashSet.contains(theId)); + } + + // Reorganize hash map, speeding up access + hashSet.cleanup(); + + // Check consistency + EXPECT_EQ(hashSet.population(), capacity / 4); + for (int i = 0; i < capacity / 4; ++i) + { + EXPECT_TRUE(hashSet.contains(QPI::id(i, 0, 0, 0))); + } + for (int i = 0; i < capacity; i++) + { + EXPECT_FALSE(hashSet.contains(QPI::id(i / 3, i + 5, i * 3, i % 10))); + } + + hashSet.reset(); + EXPECT_EQ(hashSet.population(), 0); + + commonBuffers.deinit(); +} + +template +void hasSameContent(QPI::HashSet& set, const std::set& referenceSet) +{ + EXPECT_EQ(set.population(), referenceSet.size()); + for (const T& item: referenceSet) + { + EXPECT_TRUE(set.contains(item)); + } + for (unsigned int i = 0; i < capacity; ++i) + { + T key = set.key(i); // returns key value at slot i (like 0), even if the value is not contained + if (!set.isEmptySlot(i)) + { + // key is valid, part of set, and exactly at this position + EXPECT_NE(referenceSet.find(key), referenceSet.end()); + EXPECT_EQ(set.getElementIndex(key), i); + } + else + { + // key is invalid and not at this slot + QPI::uint64 elementIndex = set.getElementIndex(key); + EXPECT_NE(elementIndex, i); + if (elementIndex == QPI::NULL_INDEX) + { + // not in set + EXPECT_EQ(referenceSet.find(key), referenceSet.end()); + } + else + { + // in set, but at different position + EXPECT_NE(referenceSet.find(key), referenceSet.end()); + } + } + } + + // test iterator + QPI::sint64 i = set.nextElementIndex(QPI::NULL_INDEX); + unsigned int cnt = 0; + while (i != QPI::NULL_INDEX) + { + cnt++; + EXPECT_FALSE(set.isEmptySlot(i)); + EXPECT_NE(referenceSet.find(set.key(i)), referenceSet.end()); + i = set.nextElementIndex(i); + } + EXPECT_EQ(cnt, referenceSet.size()); +} + +template +void cleanupHashSet(QPI::HashSet& set, const std::set& referenceSet) +{ + hasSameContent(set, referenceSet); + set.cleanup(); + //set.cleanupIfNeeded(); + hasSameContent(set, referenceSet); +} + +template +void testHashSetPseudoRandom(int seed, int cleanups, int percentAdd, int percentAddSecondHalf = -1) +{ + // add and remove entries with pseudo-random sequence + std::mt19937_64 gen64(seed); + + std::set referenceSet; + QPI::HashSet set; + + commonBuffers.init(1, 2 * sizeof(set)); + + set.reset(); + + // test cleanup of empty collection + cleanupHashSet(set, referenceSet); + + if (seed & 1) + { + // add default value of empty slot to set + referenceSet.insert(set.key(0)); + EXPECT_NE(set.add(set.key(0)), QPI::NULL_INDEX); + hasSameContent(set, referenceSet); + } + + // Randomly add, remove, and cleanup until 50 cleanups are reached + int cleanupCounter = 0; + while (cleanupCounter < cleanups) + { + int p = gen64() % 100; + + if (p == 0) + { + // cleanup (with 1% probability) + cleanupHashSet(set, referenceSet); + ++cleanupCounter; + + if (cleanupCounter == cleanups / 2 && percentAddSecondHalf >= 0) + percentAdd = percentAddSecondHalf; + } + + if (p < percentAdd) + { + // add to set (more probable than remove) + T value; + getValue(gen64, value); + if (set.add(value) != QPI::NULL_INDEX) + referenceSet.insert(value); + hasSameContent(set, referenceSet); + } + else + { + // remove from set + QPI::sint64 removeIdx = gen64() % set.capacity(); + T key = set.key(removeIdx); + if (!set.isEmptySlot(removeIdx)) + { + referenceSet.erase(key); + EXPECT_EQ(set.remove(key), removeIdx); + } + else if (referenceSet.find(key) == referenceSet.end()) + { + EXPECT_EQ(set.remove(key), QPI::NULL_INDEX); + } + hasSameContent(set, referenceSet); + } + + // std::cout << "capacity: " << set.capacity() << ", pupulation:" << set.population() << std::endl; + } + + commonBuffers.deinit(); +} + +TEST(QPIHashMapTest, HashSetPseudoRandom) +{ + constexpr unsigned int numCleanups = 5; + testHashSetPseudoRandom(42, numCleanups, 20); + testHashSetPseudoRandom(1337, numCleanups, 80); + testHashSetPseudoRandom(42, 4 * numCleanups, 30); + testHashSetPseudoRandom(1337, 4 * numCleanups, 50); + testHashSetPseudoRandom(123456789, 4 * numCleanups, 70); + testHashSetPseudoRandom(42, 10 * numCleanups, 30, 70); + testHashSetPseudoRandom(1337, 10 * numCleanups, 50, 10); + testHashSetPseudoRandom(123456789, 10 * numCleanups, 70, 10); + testHashSetPseudoRandom(42 + 1, 6 * numCleanups, 30); + testHashSetPseudoRandom(1337 + 1, 6 * numCleanups, 50); + testHashSetPseudoRandom(123456789 + 1, 6 * numCleanups, 70); + testHashSetPseudoRandom(42 + 2, 10 * numCleanups, 30); + testHashSetPseudoRandom(1337 + 2, 10 * numCleanups, 50); + testHashSetPseudoRandom(123456789 + 2, 10 * numCleanups, 70); + testHashSetPseudoRandom(42, numCleanups, 20); + testHashSetPseudoRandom(42 + 1, 10 * numCleanups, 30, 70); + testHashSetPseudoRandom(1337, 10 * numCleanups, 50, 10); + testHashSetPseudoRandom(123456789, 10 * numCleanups, 70, 10); + testHashSetPseudoRandom(1337 + 1, 6 * numCleanups, 50); + testHashSetPseudoRandom(123456789 + 2, 10 * numCleanups, 70); +} + + + +template +static void perfTestCleanup(int seed) +{ + std::mt19937_64 gen64(seed); + + auto* set = new QPI::HashSet(); + commonBuffers.init(1, sizeof(*set)); + + for (QPI::uint64 i = 1; i <= 100; ++i) + { + QPI::uint64 population = capacity * i / 100; + set->reset(); + + // add random items + auto startTime1 = std::chrono::high_resolution_clock::now(); + for (QPI::uint64 j = 0; j < population; ++j) + { + EXPECT_NE(set->add(QPI::id(gen64(), 0, 0, 0)), QPI::NULL_INDEX); + } + auto addMilliseconds = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime1).count(); + + // measure run-time of cleanup + auto startTime2 = std::chrono::high_resolution_clock::now(); + set->cleanup(); + auto cleanupMilliseconds = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - startTime2).count(); + + std::cout << "HastSet::cleanup() with " << population * 100 / capacity << "% population takes " << cleanupMilliseconds << " ms (adding took " << addMilliseconds * 1000000 / population << " ms/million)" << std::endl; + } + + delete set; + commonBuffers.deinit(); +} + +TEST(QPIHashMapTest, HashSetPerfTest) +{ + // How often should cleanup() be run? + + // for a set of capacities: vary population + // keep sequence of add/remove + + // measure run-time of cleanups -> O(N^2) with N = population + //perfTestCleanup<2 * 1024 * 1024>(42); + //perfTestCleanup<2 * 1024 * 1024>(13); + + // measure lookups/seconds -> O(1) if population is sparse -> O(N) if population is high with N = max population since last cleanup +} diff --git a/test/quorum_value.cpp b/test/quorum_value.cpp new file mode 100644 index 000000000..3698e5d76 --- /dev/null +++ b/test/quorum_value.cpp @@ -0,0 +1,140 @@ +#define NO_UEFI + +#include +#include +#include + +#include "gtest/gtest.h" +#include "../src/platform/quorum_value.h" + +TEST(FixedTypeQuorumTest, CalculateAscendingQuorumSimple) +{ + long long values[10] = {10, 5, 8, 3, 7, 1, 9, 2, 6, 4}; + + long long result = calculateAscendingQuorumValue(values, 10); + + // (10 * 2) / 3 = 6, index 6 after sorting = 7 + EXPECT_EQ(result, 7); +} + +TEST(FixedTypeQuorumTest, Calculate676Quorum) +{ + long long values[676]; + for (int i = 0; i < 676; i++) + { + values[i] = i + 1; + } + + long long quorum = calculateAscendingQuorumValue(values, 676); + + // (676 * 2) / 3 = 450, value at index 450 = 451 + EXPECT_EQ(quorum, 451); +} + +TEST(FixedTypeQuorumTest, EmptyArray) +{ + long long values[1] = {0}; + long long result = calculateAscendingQuorumValue(values, 0); + EXPECT_EQ(result, 0); +} + +TEST(FixedTypeQuorumTest, SingleElement) +{ + long long values[1] = {42}; + long long result = calculateAscendingQuorumValue(values, 1); + EXPECT_EQ(result, 42); +} + +TEST(FixedTypeQuorumTest, PercentileDescending) +{ + int values[10] = {10, 5, 8, 3, 7, 1, 9, 2, 6, 4}; + + int result = calculatePercentileValue(values, 10); + + // (10 * 1) / 3 = 3, descending sort, index 3 = 7 + EXPECT_EQ(result, 7); +} + +TEST(FixedTypeQuorumTest, AllZeros) +{ + long long values[676] = {0}; + + long long quorum = calculateAscendingQuorumValue(values, 676); + + // All values are 0, quorum should be 0 + EXPECT_EQ(quorum, 0); +} + +TEST(FixedTypeQuorumTest, MostlyZeros) +{ + long long values[676] = {0}; + + // Set first 225 elements to non-zero values + for (int i = 0; i < 225; i++) + { + values[i] = i + 1; + } + + long long quorum = calculateAscendingQuorumValue(values, 676); + + // After sorting: 0,0,0,...,0 (451 zeros), 1,2,3,...,225 + // Index 450 will be 0 (since 676-225 = 451 zeros, and index 450 < 451) + EXPECT_EQ(quorum, 0); +} + +template +std::vector prepareData(unsigned int seed, unsigned int numElements) +{ + std::mt19937 gen32(seed); + + unsigned int numberOfBlocks = (sizeof(T) + 3) / 4; + + std::vector vec(numElements, 0); + for (unsigned int i = 0; i < numElements; ++i) + { + for (unsigned int b = 0; b < numberOfBlocks; ++b) + { + vec[i] |= (static_cast(gen32()) << (b * 32)); + } + } + + return vec; +} + +template +void testCalculatePercentile(unsigned int seed) +{ + std::vector vec = prepareData(seed, 676); + + std::vector referenceVec = vec; + std::sort(referenceVec.begin(), referenceVec.end()); + + T result = calculatePercentileValue(vec.data(), static_cast(vec.size())); + + // Calculate expected index: (676 * 2) / 3 = 450 + unsigned int expectedIndex = (676 * 2) / 3; + EXPECT_EQ(result, referenceVec[expectedIndex]); +} + +template +class QuorumValueTest : public testing::Test {}; + +using testing::Types; + +TYPED_TEST_CASE_P(QuorumValueTest); + +TYPED_TEST_P(QuorumValueTest, CalculatePercentile) +{ + unsigned int metaSeed = 98765; + std::mt19937 gen32(metaSeed); + + for (unsigned int t = 0; t < 10; ++t) + testCalculatePercentile(gen32()); +} + +REGISTER_TYPED_TEST_CASE_P(QuorumValueTest, + CalculatePercentile +); + +typedef Types TestTypes; +INSTANTIATE_TYPED_TEST_CASE_P(TypeParamQuorumValueTests, QuorumValueTest, TestTypes); diff --git a/test/revenue.cpp b/test/revenue.cpp new file mode 100644 index 000000000..48793d6fa --- /dev/null +++ b/test/revenue.cpp @@ -0,0 +1,235 @@ +#pragma once + +#define NO_UEFI + +#include "gtest/gtest.h" + +#include "../src/revenue.h" + +#include + +std::string TEST_DIR = "data/"; +std::vector REVENUE_FILES = { +"custom_revenue.eoe" +}; + +unsigned int random(const unsigned int range) +{ + unsigned int value; + _rdrand32_step(&value); + return value % range; +} + +TEST(TestCoreRevenue, GetQuorumScore) +{ + unsigned long long data[NUMBER_OF_COMPUTORS]; + + // Zeros data + setMem(data, sizeof(data), 0); + unsigned long long quorumScore = getQuorumScore(data); + EXPECT_EQ(quorumScore, 1); + + // Constant data + const unsigned long long CONSTANT_VALUE = random(0xFFFFFFFF); + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + data[i] = CONSTANT_VALUE; + } + quorumScore = getQuorumScore(data); + EXPECT_EQ(quorumScore, CONSTANT_VALUE); + + // Generate sort + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + data[i] = random(0xFFFFFFFF); + } + quorumScore = getQuorumScore(data); + std::sort(data, data + NUMBER_OF_COMPUTORS, std::greater()); + EXPECT_EQ(quorumScore, data[QUORUM - 1]); +} + +TEST(TestCoreRevenue, ComputeRevFactor) +{ + const unsigned long long scaleFactor = 1024; + unsigned long long data[NUMBER_OF_COMPUTORS]; + unsigned long long dataFactor[NUMBER_OF_COMPUTORS]; + + // All zeros. No reveue for alls + setMem(data, sizeof(data), 0); + computeRevFactor(data, scaleFactor, dataFactor); + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_EQ(dataFactor[i], 0); + } + + // Constant values. Max revenue for all + const unsigned long long CONSTANT_VALUE = random(0xFFFFFFFF); + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + data[i] = CONSTANT_VALUE; + } + computeRevFactor(data, scaleFactor, dataFactor); + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_EQ(dataFactor[i], scaleFactor); + } + + // General case + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + data[i] = random(0xFFFFFFFF); + } + computeRevFactor(data, scaleFactor, dataFactor); + // Data in range [0, scaleFactor] and quorum get max scale factor + unsigned long long quorumValue = getQuorumScore(data); + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_LE(dataFactor[i], scaleFactor); + EXPECT_GE(dataFactor[i], 0); + if (data[i] >= quorumValue) + { + EXPECT_EQ(dataFactor[i], scaleFactor); + } + else + { + EXPECT_LT(dataFactor[i], scaleFactor); + } + } + + // One zeros case + unsigned int zeroPositions = random(NUMBER_OF_COMPUTORS); + data[zeroPositions] = 0; + computeRevFactor(data, scaleFactor, dataFactor); + EXPECT_EQ(dataFactor[zeroPositions], 0); + + // Very small data + unsigned int smallPosition = random(NUMBER_OF_COMPUTORS); + data[smallPosition] = 1; + computeRevFactor(data, scaleFactor, dataFactor); + EXPECT_EQ(dataFactor[smallPosition], 0); +} + +TEST(TestCoreRevenue, GeneralTest) +{ + unsigned long long tx[NUMBER_OF_COMPUTORS]; + unsigned long long votes[NUMBER_OF_COMPUTORS]; + unsigned long long customMiningShares[NUMBER_OF_COMPUTORS]; + long long revenuePerComputors[NUMBER_OF_COMPUTORS]; + + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + tx[i] = random(0xFFFFFFFF); + } + computeRevFactor(tx, gTxScoreScalingThreshold, gTxScoreFactor); + + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + votes[i] = random(0xFFFFFFFF); + } + computeRevFactor(votes, gVoteScoreScalingThreshold, gVoteScoreFactor); + + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + customMiningShares[i] = random(0xFFFFFFFF); + } + computeRevFactor(customMiningShares, gCustomMiningScoreScalingThreshold, gCustomMiningScoreFactor); + + long long arbitratorRevenue = ISSUANCE_RATE; + constexpr long long issuancePerComputor = ISSUANCE_RATE / NUMBER_OF_COMPUTORS; + for (unsigned int computorIndex = 0; computorIndex < NUMBER_OF_COMPUTORS; computorIndex++) + { + // Compute initial computor revenue, reducing arbitrator revenue + unsigned long long combinedScoreFactor = gTxScoreFactor[computorIndex] * gVoteScoreFactor[computorIndex] * gCustomMiningScoreFactor[computorIndex]; + long long revenue = (long long)(combinedScoreFactor * issuancePerComputor / gTxScoreScalingThreshold / gVoteScoreScalingThreshold / gCustomMiningScoreScalingThreshold); + revenuePerComputors[computorIndex] = revenue; + } + + unsigned long long txQuorumScore = getQuorumScore(tx); + unsigned long long voteQuorumScore = getQuorumScore(votes); + unsigned long long customMiningQuorumScore = getQuorumScore(customMiningShares); + + // Checking score factor + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_LE(revenuePerComputors[i], arbitratorRevenue); + EXPECT_GE(revenuePerComputors[i], 0); + } +} + +// Simulate the revenue fomula from real data +TEST(TestCoreRevenue, ReadFile) +{ + unsigned long long tx[NUMBER_OF_COMPUTORS]; + unsigned long long votes[NUMBER_OF_COMPUTORS]; + unsigned long long customMining[NUMBER_OF_COMPUTORS]; + long long revenue[NUMBER_OF_COMPUTORS]; + constexpr long long issuancePerComputor = ISSUANCE_RATE / NUMBER_OF_COMPUTORS; + + for (size_t i = 0; i < REVENUE_FILES.size(); ++i) + { + // Open input file in binary mode + std::string input = TEST_DIR + REVENUE_FILES[i]; + std::ifstream infile(input, std::ios::binary); + if (!infile) + { + std::cerr << "Error opening file: " << input << "\n"; + std::exit(EXIT_FAILURE); + } + + // Read transaction, vote and custom mining share + infile.read(reinterpret_cast(&tx), sizeof(tx)); + if (!infile) + { + std::cerr << "Error reading tx score from file.\n"; + std::exit(EXIT_FAILURE); + } + + infile.read(reinterpret_cast(&votes), sizeof(votes)); + if (!infile) + { + std::cerr << "Error reading votes score from file.\n"; + std::exit(EXIT_FAILURE); + } + + infile.read(reinterpret_cast(&customMining), sizeof(customMining)); + if (!infile) + { + std::cerr << "Error reading custom mining score from file.\n"; + std::exit(EXIT_FAILURE); + } + + infile.close(); + + // Start to compute and write out data + computeRevenue(tx, votes, customMining, revenue); + + // Write the data out for investigation + // Open output file in text mode + std::string output = input + ".csv"; + std::ofstream outfile(output); + if (!outfile) + { + std::cerr << "Error opening output file: " << output << "\n"; + std::exit(EXIT_FAILURE); + } + + // Write CSV header + outfile << "Index,txScore,voteScore,customMiningScore,txScoreFactor,voteScoreFactor,customMiningScoreFactor,revenue,percentage\n"; + + // Write the content + for (int k = 0; k < NUMBER_OF_COMPUTORS; k++) + { + outfile << k << "," + << tx[k] << "," + << votes[k] << "," + << customMining[k] << "," + << gRevenueComponents.txScoreFactor[k] << "," + << gRevenueComponents.voteScoreFactor[k] << "," + << gRevenueComponents.customMiningScoreFactor[k] << "," + << revenue[k] << "," + << (double)revenue[k] * 100 / issuancePerComputor + << "\n"; + } + outfile.close(); + } +} diff --git a/test/score.cpp b/test/score.cpp new file mode 100644 index 000000000..58d1dba26 --- /dev/null +++ b/test/score.cpp @@ -0,0 +1,910 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#define ENABLE_PROFILING 0 + +// current optimized implementation +#include "../src/public_settings.h" +#include "../src/mining/score_engine.h" +#include "../src/score.h" + +// reference implementation +#include "score_reference.h" + +// params settting +#include "score_params.h" + +#include "utils.h" + +#include +#include +#include +#include + +using namespace score_params; +using namespace test_utils; + +// When algorithm change, belows need to do +// - score_params.h: adjust the number of ParamType and change the config of kSettings +// - Modify the score reference run with new setting +// - Need to verify about the idex of setting in the template of score/score_reference class +// - Re-run the test_score_generation for generating 2 csv files, scores and samples +// - Copy the files into test/data +// - Rename COMMON_TEST_SAMPLES_FILE_NAME and COMMON_TEST_SCORES_FILE_NAME if neccessary + + +static const std::string COMMON_TEST_SAMPLES_FILE_NAME = "data/samples_20240815.csv"; +static const std::string COMMON_TEST_SCORES_HYPERIDENTITY_FILE_NAME = "data/scores_hyperidentity.csv"; +static const std::string COMMON_TEST_SCORES_ADDITION_FILE_NAME = "data/scores_addition.csv"; + +static constexpr bool PRINT_DETAILED_INFO = false; +// Variable control the algo tested +// AllAlgo: run the score that alg is retermined by nonce +static std::vector> TEST_ALGOS = { + {score_engine::AlgoType::HyperIdentity, "HyperIdentity"}, + {score_engine::AlgoType::Addition, "Addition"}, + {score_engine::AlgoType::MaxAlgoCount, "Mixed"}, +}; + +// set to 0 for run all available samples +// For profiling enable, run all available samples +static constexpr unsigned long long COMMON_TEST_NUMBER_OF_SAMPLES = 16; +static constexpr unsigned long long PROFILING_NUMBER_OF_SAMPLES = 48; + + +// set 0 for run maximum number of threads of the computer. +// For profiling enable, set it equal to deployment setting +static constexpr int MAX_NUMBER_OF_THREADS = 0; +static constexpr int MAX_NUMBER_OF_PROFILING_THREADS = 12; +static bool gCompareReference = false; + +// Only run on specific index of samples and setting +std::vector filteredSamples;// = { 0 }; +std::vector filteredSettings;// = { 0 }; + +std::vector> gScoresHyperIdentityGroundTruth; +std::vector> gScoresHyperAdditionGroundTruth; +std::map gScoreProcessingTime; +std::map gScoreHyperIdentityIndexMap; +std::map gScoreAdditionIndexMap; + +struct ScoreResult +{ + unsigned int score; + long long elapsedMs; +}; + +template class ScoreType, typename CurrentConfig> +ScoreResult computeScore( + const unsigned char* miningSeed, + const unsigned char* publicKey, + const unsigned char* nonce, + score_engine::AlgoType algo, + unsigned char* externalRandomPool) +{ + std::unique_ptr> scoreEngine = + std::make_unique>(); + + scoreEngine->initMemory(); + if (nullptr == externalRandomPool) + { + scoreEngine->initMiningData(miningSeed); + } + + unsigned int scoreValue = 0; + + auto t0 = std::chrono::high_resolution_clock::now(); + + switch (algo) + { + case score_engine::AlgoType::HyperIdentity: + scoreValue = scoreEngine->computeHyperIdentityScore(publicKey, nonce, externalRandomPool); + break; + case score_engine::AlgoType::Addition: + scoreValue = scoreEngine->computeAdditionScore(publicKey, nonce, externalRandomPool); + break; + default: + scoreValue = scoreEngine->computeScore(publicKey, nonce, externalRandomPool); + break; + } + + auto t1 = std::chrono::high_resolution_clock::now(); + auto elapsedMs = std::chrono::duration_cast(t1 - t0).count(); + + return { scoreValue, elapsedMs }; + +} + +void processQubicScore(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, int sampleIndex) +{ + // Core use the external random pool + std::unique_ptr> pScore = std::make_unique>(); + pScore->initMemory(); + pScore->initMiningData(miningSeed); + + unsigned int scoreValue = (*pScore)(0, publicKey, miningSeed, nonce); + + // Determine which algo to use to get the correct ground truth + int gtIndex = -1; + unsigned int gtScore = 0; + score_engine::AlgoType effectiveAlgo = (nonce[0] & 1) == 0 ? score_engine::AlgoType::HyperIdentity : score_engine::AlgoType::Addition; + + std::vector state(score_engine::STATE_SIZE); + std::vector externalPoolVec(score_engine::POOL_VEC_PADDING_SIZE); + score_engine::generateRandom2Pool(miningSeed, state.data(), externalPoolVec.data()); + ScoreResult scoreResult = computeScore, + score_engine::AdditionParams< + ADDITION_NUMBER_OF_INPUT_NEURONS, + ADDITION_NUMBER_OF_OUTPUT_NEURONS, + ADDITION_NUMBER_OF_TICKS, + ADDITION_NUMBER_OF_NEIGHBORS, + ADDITION_POPULATION_THRESHOLD, + ADDITION_NUMBER_OF_MUTATIONS, + ADDITION_SOLUTION_THRESHOLD_DEFAULT> + >>( + miningSeed, publicKey, nonce, effectiveAlgo, externalPoolVec.data()); + unsigned int refScore = scoreResult.score; + +#pragma omp critical + { + EXPECT_EQ(refScore, scoreValue); + } +} + + +template +static void processElement(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, + int sampleIndex, score_engine::AlgoType algo) +{ + // Skip filter settings + if (!filteredSettings.empty() + && std::find(filteredSettings.begin(), filteredSettings.end(), i) == filteredSettings.end()) + { + return; + } + + // Get the current config + using CurrentConfig = std::tuple_element_t; + + // Core use the external random pool + std::vector state(score_engine::STATE_SIZE); + std::vector externalPoolVec(score_engine::POOL_VEC_PADDING_SIZE); + score_engine::generateRandom2Pool(miningSeed, state.data(), externalPoolVec.data()); + ScoreResult scoreResult = computeScore( + miningSeed, publicKey, nonce, algo, externalPoolVec.data()); + unsigned int scoreValue = scoreResult.score; + + // Determine which algo to use to get the correct ground truth + int gtIndex = -1; + unsigned int gtScore = 0; + score_engine::AlgoType effectiveAlgo = algo; + if (algo != score_engine::AlgoType::HyperIdentity && algo != score_engine::AlgoType::Addition) + { + // Default/Mixed mode: select based on nonce + effectiveAlgo = (nonce[0] & 1) == 0 ? score_engine::AlgoType::HyperIdentity : score_engine::AlgoType::Addition; + } + if (effectiveAlgo == score_engine::AlgoType::HyperIdentity) + { + if (gScoreHyperIdentityIndexMap.count(i) > 0) + { + gtIndex = gScoreHyperIdentityIndexMap[i]; + gtScore = gScoresHyperIdentityGroundTruth[sampleIndex][gtIndex]; + } + } + else if (effectiveAlgo == score_engine::AlgoType::Addition) + { + if (gScoreAdditionIndexMap.count(i) > 0) + { + gtIndex = gScoreAdditionIndexMap[i]; + gtScore = gScoresHyperAdditionGroundTruth[sampleIndex][gtIndex]; + } + } + + unsigned int refScore = 0; + if (gCompareReference) + { + // Reference score always re-compute the pools + ScoreResult scoreRefResult = computeScore( + miningSeed, publicKey, nonce, algo, nullptr); + refScore = scoreRefResult.score; + } + +#pragma omp critical + if (gCompareReference) + { + EXPECT_EQ(refScore, scoreValue); + } + else + { + if (PRINT_DETAILED_INFO || gtIndex < 0 || (scoreValue != gtScore)) + { + std::cout << " score " << scoreValue; + if (gtIndex >= 0) + { + std::cout << " vs gt " << gtScore << std::endl; + } + else // No mapping from ground truth + { + std::cout << " vs gt NA" << std::endl; + } + } + { + EXPECT_GE(gtIndex, 0); + if (gtIndex >= 0) + { + EXPECT_EQ(gtScore, scoreValue); + } + } + } +} + +// Recursive template to process each element in scoreSettings +template +static void processElementPerf(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, + int sampleIndex, score_engine::AlgoType algo) +{ + using CurrentConfig = std::tuple_element_t; + + std::vector state(score_engine::STATE_SIZE); + std::vector externalPoolVec(score_engine::POOL_VEC_PADDING_SIZE); + score_engine::generateRandom2Pool(miningSeed, state.data(), externalPoolVec.data()); + ScoreResult scoreRefResult = computeScore( + miningSeed, publicKey, nonce, algo, externalPoolVec.data()); + auto elapsed = scoreRefResult.elapsedMs; +#pragma omp critical + { + if (gScoreProcessingTime.count(i) == 0) + { + gScoreProcessingTime[i] = elapsed; + } + else + { + gScoreProcessingTime[i] += elapsed; + } + } +} + +// Main processing function +template +static void processHelper(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, + int sampleIndex, score_engine::AlgoType algo, std::index_sequence) +{ + if constexpr (profiling) + { + (processElementPerf(miningSeed, publicKey, nonce, sampleIndex, algo), ...); + } + else + { + (processElement(miningSeed, publicKey, nonce, sampleIndex, algo), ...); + } + +} + +// Recursive template to process each element in scoreSettings +template +static void process(const unsigned char* miningSeed, const unsigned char* publicKey, const unsigned char* nonce, + int sampleIndex = 0, score_engine::AlgoType algo = score_engine::AlgoType::AllAlgo) +{ + processHelper(miningSeed, publicKey, nonce, sampleIndex, algo, std::make_index_sequence{}); +} + +template +void writeParams(std::ostream& os, const std::string& sep = ",") +{ + // Because currently 2 params set shared the same things, incase of new algo have different params + // need to make a separate check + if constexpr (P::algoType == score_engine::AlgoType::HyperIdentity) + { + os << "InputNeurons: " << P::numberOfInputNeurons << sep + << " OutputNeurons: " << P::numberOfOutputNeurons << sep + << " Ticks: " << P::numberOfTicks << sep + << " Neighbor: " << P::numberOfNeighbors << sep + << " Population: " << P::populationThreshold << sep + << " Mutate: " << P::numberOfMutations << sep + << " Threshold: " << P::solutionThreshold; + } + else if constexpr (P::algoType == score_engine::AlgoType::Addition) + { + os << "InputNeurons: " << P::numberOfInputNeurons << sep + << " OutputNeurons: " << P::numberOfOutputNeurons << sep + << " Ticks: " << P::numberOfTicks << sep + << " Neighbor: " << P::numberOfNeighbors << sep + << " Population: " << P::populationThreshold << sep + << " Mutate: " << P::numberOfMutations << sep + << " Threshold: " << P::solutionThreshold; + } + else + { + std::cerr << "UNKNOWN ALGO !" << std::endl; + } +} + +template +void printConfigProfileImpl(score_engine::AlgoType algo) +{ + using CurrentConfig = std::tuple_element_t; + + if (algo & score_engine::AlgoType::HyperIdentity) + { + writeParams(std::cout); + } + else if (algo & score_engine::AlgoType::Addition) + { + writeParams(std::cout); + } +} + +template +void printConfigImpl(score_engine::AlgoType algo) +{ + using CurrentConfig = std::tuple_element_t; + + if (algo & score_engine::AlgoType::HyperIdentity) + { + writeParams(std::cout); + } + else if (algo & score_engine::AlgoType::Addition) + { + writeParams(std::cout); + } +} + +template +void printConfigByIndex(std::size_t index, score_engine::AlgoType algo, std::index_sequence) +{ + if constexpr (profiling) + { + ((Is == index ? (printConfigProfileImpl(algo), 0) : 0), ...); + } + else + { + ((Is == index ? (printConfigImpl(algo), 0) : 0), ...); + } +} + +template +void printConfig(std::size_t index, score_engine::AlgoType algo) +{ + if constexpr (profiling) + { + printConfigByIndex(index, algo, std::make_index_sequence>{}); + } + else + { + printConfigByIndex(index, algo, std::make_index_sequence>{}); + } +} + +template +bool compareParams(const std::vector& values) +{ + // Because currently 2 params set shared the same things, incase of new algo have different params + // need to make a separate check + if constexpr (P::algoType == score_engine::AlgoType::HyperIdentity) + { + return values[0] == P::numberOfInputNeurons + && values[1] == P::numberOfOutputNeurons + && values[2] == P::numberOfTicks + && values[3] == P::numberOfNeighbors + && values[4] == P::populationThreshold + && values[5] == P::numberOfMutations + && values[6] == P::solutionThreshold; + } + else if constexpr (P::algoType == score_engine::AlgoType::Addition) + { + return values[0] == P::numberOfInputNeurons + && values[1] == P::numberOfOutputNeurons + && values[2] == P::numberOfTicks + && values[3] == P::numberOfNeighbors + && values[4] == P::populationThreshold + && values[5] == P::numberOfMutations + && values[6] == P::solutionThreshold; + } + return false; +} + +template +bool checkConfig(const std::vector& values, score_engine::AlgoType algo) +{ + using CurrentConfig = std::tuple_element_t; + switch (algo) + { + case score_engine::AlgoType::HyperIdentity: + // HyperIdentity + return compareParams(values); + break; + case score_engine::AlgoType::Addition: + // Addition + return compareParams(values); + break; + default: + return false; + break; + } +} + +template +int findMatchingConfigImpl(const std::vector& values, score_engine::AlgoType algo, std::index_sequence) +{ + int result = -1; + ((checkConfig(values, algo) ? (result = Is, false) : true) && ...); + return result; +} + +int findMatchingConfig(const std::vector& values, score_engine::AlgoType algo) +{ + return findMatchingConfigImpl(values, algo, std::make_index_sequence>{}); +} + +void loadSamples( + const std::vector>& sampleString, + unsigned long long numberOfSamples, + unsigned long long numberOfSamplesReadFromFile, + std::vector& miningSeeds, + std::vector& publicKeys, + std::vector& nonces) +{ + miningSeeds.resize(numberOfSamples); + publicKeys.resize(numberOfSamples); + nonces.resize(numberOfSamples); + + // Reading the input samples + for (unsigned long long i = 0; i < numberOfSamples; ++i) + { + if (i < numberOfSamplesReadFromFile) + { + miningSeeds[i] = hexTo32Bytes(sampleString[i][0], 32); + publicKeys[i] = hexTo32Bytes(sampleString[i][1], 32); + nonces[i] = hexTo32Bytes(sampleString[i][2], 32); + } + else // Samples from files are not enough, randomly generate more + { + _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[0]); + _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[8]); + _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[16]); + _rdrand64_step((unsigned long long*) & miningSeeds[i].m256i_u8[24]); + + _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[0]); + _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[8]); + _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[16]); + _rdrand64_step((unsigned long long*) & publicKeys[i].m256i_u8[24]); + + _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[0]); + _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[8]); + _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[16]); + _rdrand64_step((unsigned long long*) & nonces[i].m256i_u8[24]); + + } + } +} + +template +void runTest( + const std::string& testName, + const std::vector& samples, + int numberOfThreads, + ProcessFunc processFunc) +{ + std::cout << "Test " << testName << " ..." << std::endl; + int proccessedSamples = 0; +#pragma omp parallel for num_threads(numberOfThreads) + for (int i = 0; i < static_cast(samples.size()); ++i) + { + int index = samples[i]; + processFunc(index); +#pragma omp critical + proccessedSamples++; + std::cout << "\r-Processed: " << proccessedSamples << " / " << static_cast(samples.size()) << " " << std::flush; + } + std::cout << std::endl; +} + +static std::vector> readSampleAsStr(const std::string& filename) +{ + std::vector> sampleString = readCSV(filename); + + // Remove header + sampleString.erase(sampleString.begin()); + + return sampleString; +} + +void runCommonTests() +{ + +#if defined (__AVX512F__) && !GENERIC_K12 + initAVX512KangarooTwelveConstants(); +#endif + constexpr unsigned long long numberOfGeneratedSetting = CONFIG_COUNT; + + // Read the parameters and results + auto sampleString = readSampleAsStr(COMMON_TEST_SAMPLES_FILE_NAME); + + // Convert the raw string and do the data verification + unsigned long long numberOfSamplesReadFromFile = sampleString.size(); + unsigned long long numberOfSamples = numberOfSamplesReadFromFile; + unsigned long long requestedNumberOfSamples = COMMON_TEST_NUMBER_OF_SAMPLES; + + if (requestedNumberOfSamples > 0) + { + std::cout << "Request testing with " << requestedNumberOfSamples << " samples." << std::endl; + + numberOfSamples = std::min(requestedNumberOfSamples, numberOfSamples); + if (requestedNumberOfSamples <= numberOfSamples) + { + numberOfSamples = requestedNumberOfSamples; + } + else // Request number of samples greater than existed. Only valid for reference score validation only + { + if (gCompareReference) + { + numberOfSamples = requestedNumberOfSamples; + std::cout << "Refenrece comparison mode: " << numberOfSamples << " samples are read from file for comparision." + << "Remained are generated randomly." + << std::endl; + } + else + { + std::cout << "Only " << numberOfSamples << " samples can be read from file for comparison" << std::endl; + } + } + } + + // Reading the input samples + std::vector miningSeeds(numberOfSamples); + std::vector publicKeys(numberOfSamples); + std::vector nonces(numberOfSamples); + loadSamples(sampleString, numberOfSamples, numberOfSamplesReadFromFile, miningSeeds, publicKeys, nonces); + + // Reading the header of score and verification + if (!gCompareReference) + { + auto scoresStringHyperidentity = readCSV(COMMON_TEST_SCORES_HYPERIDENTITY_FILE_NAME); + auto scoresStringAddition = readCSV(COMMON_TEST_SCORES_ADDITION_FILE_NAME); + + if (scoresStringAddition.size() == 0 || scoresStringAddition.size() == 0) + { + ASSERT_GT(scoresStringHyperidentity.size(), 0); + ASSERT_GT(scoresStringAddition.size(), 0); + std::cout << "Number of Hyperidentity and Addition settings must greater than zero." << std::endl; + return; + } + if (scoresStringAddition.size() != scoresStringAddition.size()) + { + ASSERT_EQ(scoresStringHyperidentity.size(), scoresStringAddition.size()); + std::cout << "Number of Hyperidentity and Addition settings must be equal." << std::endl; + return; + } + + // + auto buildIndexMap = []( + std::vector& header, + score_engine::AlgoType algo, + std::map& indexMap) + { + for (int gtIdx = 0; gtIdx < (int)header.size(); ++gtIdx) + { + auto scoresSettingHeader = convertULLFromString(header[gtIdx]); + int foundIndex = findMatchingConfig(scoresSettingHeader, algo); + if (foundIndex >= 0) + { + indexMap[foundIndex] = gtIdx; + } + } + }; + buildIndexMap(scoresStringHyperidentity[0], score_engine::AlgoType::HyperIdentity, gScoreHyperIdentityIndexMap); + buildIndexMap(scoresStringAddition[0], score_engine::AlgoType::Addition, gScoreAdditionIndexMap); + + if (gScoreHyperIdentityIndexMap.size() != gScoreHyperIdentityIndexMap.size()) + { + ASSERT_EQ(gScoreHyperIdentityIndexMap.size(), gScoreHyperIdentityIndexMap.size()); + std::cout << "Number of tested Hyperidentity and Addition must be equal." << std::endl; + return; + } + + std::cout << "Testing " << CONFIG_COUNT << " param combinations on " << scoresStringHyperidentity[0].size() << " Hyperidentity and Addition ground truth settings." << std::endl; + // In case of number of setting is lower than the ground truth. Consider we are in experiement, still run but expect the test failed + if (gScoreHyperIdentityIndexMap.size() < CONFIG_COUNT) + { + std::cout << "WARNING: Number of provided ground truth settings is lower than tested settings. Only test with available ones." + << std::endl; + EXPECT_EQ(gScoreHyperIdentityIndexMap.size(), CONFIG_COUNT); + } + + auto loadGroundTruth = []( + const std::vector>& scoresString, + std::vector>& groundTruth, + int numberOfSamples) -> int + { + int numberOfGTSetting = (int)scoresString.size() - 1; + numberOfSamples = std::min(numberOfSamples, numberOfGTSetting); + groundTruth.resize(numberOfSamples); + + for (size_t i = 0; i < numberOfSamples; ++i) + { + auto& scoresStr = scoresString[i + 1]; + for (const auto& str : scoresStr) + { + groundTruth[i].push_back(std::stoi(str)); + } + } + + return numberOfSamples; + }; + + int numHI = loadGroundTruth(scoresStringHyperidentity, gScoresHyperIdentityGroundTruth, (int)numberOfSamples); + int numAdd = loadGroundTruth(scoresStringAddition, gScoresHyperAdditionGroundTruth, (int)numberOfSamples); + + std::cout << "There are " << numHI << " Hyperidentity results and " << numAdd << " Addition results." << std::endl; + } + + + // Run the test + unsigned int numberOfThreads = std::thread::hardware_concurrency(); + if (MAX_NUMBER_OF_THREADS > 0) + { + numberOfThreads = numberOfThreads > MAX_NUMBER_OF_THREADS ? MAX_NUMBER_OF_THREADS : numberOfThreads; + } + + if (numberOfThreads > 1) + { + std::cout << "Compare score only. Lauching test with all available " << numberOfThreads << " threads." << std::endl; + } + else + { + std::cout << "Running one sample on one thread for collecting single thread performance." << std::endl; + } + + std::vector samples; + for (int i = 0; i < numberOfSamples; ++i) + { + if (!filteredSamples.empty() + && std::find(filteredSamples.begin(), filteredSamples.end(), i) == filteredSamples.end()) + { + continue; + } + samples.push_back(i); + } + + std::string compTerm = "and compare with groundtruths from file."; + if (gCompareReference) + { + compTerm = "and compare with reference code."; + } + + std::cout << "Processing " << samples.size() << " samples " << compTerm << "..." << std::endl; + + for (const auto& [algoType, algoName] : TEST_ALGOS) + { + runTest(algoName, samples, numberOfThreads, [&](int index) + { + process<0, CONFIG_COUNT>( + miningSeeds[index].m256i_u8, + publicKeys[index].m256i_u8, + nonces[index].m256i_u8, + index, + algoType); + }); + } + + // Test Qubic score vs internal engine (always runs) + runTest("Qubic's score vs internal score engine on active config", samples, numberOfThreads, [&](int index) + { + processQubicScore( + miningSeeds[index].m256i_u8, + publicKeys[index].m256i_u8, + nonces[index].m256i_u8, + index); + }); + +} + +template +void profileAlgo( + score_engine::AlgoType algo, + const std::string& algoName, + const std::vector& samples, + const std::vector& miningSeeds, + const std::vector& publicKeys, + const std::vector& nonces, + const std::vector& filteredSamples, + int numberOfThreads, + unsigned long long numberOfSamples) +{ + std::cout << "Profile " << algoName << " ... " << std::endl; + gScoreProcessingTime.clear(); + + int numSamples = static_cast(samples.size()); + int proccessedSamples = 0; +#pragma omp parallel for num_threads(numberOfThreads) + for (int i = 0; i < numSamples; ++i) + { + int index = samples[i]; + process( + miningSeeds[index].m256i_u8, + publicKeys[index].m256i_u8, + nonces[index].m256i_u8, + index, + algo); +#pragma omp critical + proccessedSamples++; + std::cout << "\r-Processed: " << proccessedSamples << " / " << numSamples << " " << std::flush; + } + std::cout << std::endl; + + std::size_t sampleCount = filteredSamples.empty() ? numberOfSamples : filteredSamples.size(); + for (const auto& [settingIndex, totalTime] : gScoreProcessingTime) + { + unsigned long long avgTime = totalTime / sampleCount; + std::cout << "Avg time [setting " << settingIndex << "]: "; + printConfig(settingIndex, algo); + std::cout << " - " << avgTime << " ms" << std::endl; + } +} + +void runPerformanceTests() +{ +#if defined (__AVX512F__) && !GENERIC_K12 + initAVX512KangarooTwelveConstants(); +#endif + constexpr unsigned long long numberOfGeneratedSetting = PROFILE_CONFIG_COUNT; + + // Read the parameters and results + auto sampleString = readSampleAsStr(COMMON_TEST_SAMPLES_FILE_NAME); + + // Convert the raw string and do the data verification + unsigned long long numberOfSamplesReadFromFile = sampleString.size(); + unsigned long long numberOfSamples = numberOfSamplesReadFromFile; + unsigned long long requestedNumberOfSamples = PROFILING_NUMBER_OF_SAMPLES; + + if (requestedNumberOfSamples > 0) + { + std::cout << "Request testing with " << requestedNumberOfSamples << " samples." << std::endl; + + numberOfSamples = std::min(requestedNumberOfSamples, numberOfSamples); + if (requestedNumberOfSamples <= numberOfSamples) + { + numberOfSamples = requestedNumberOfSamples; + } + } + + // Loading samples + std::vector miningSeeds(numberOfSamples); + std::vector publicKeys(numberOfSamples); + std::vector nonces(numberOfSamples); + loadSamples(sampleString, numberOfSamples, numberOfSamplesReadFromFile, miningSeeds, publicKeys, nonces); + + std::cout << "Profiling " << numberOfGeneratedSetting << " param combinations. " << std::endl; + + // Run the profiling + unsigned int numberOfThreads = std::thread::hardware_concurrency(); + if (MAX_NUMBER_OF_PROFILING_THREADS > 0) + { + numberOfThreads = numberOfThreads > MAX_NUMBER_OF_PROFILING_THREADS ? MAX_NUMBER_OF_PROFILING_THREADS : numberOfThreads; + } + std::cout << "Running " << numberOfThreads << " threads for collecting multiple threads performance" << std::endl; + + std::vector samples; + for (int i = 0; i < numberOfSamples; ++i) + { + if (!filteredSamples.empty() + && std::find(filteredSamples.begin(), filteredSamples.end(), i) == filteredSamples.end()) + { + continue; + } + samples.push_back(i); + } + + std::string compTerm = "for profiling, don't compare any result."; + + std::cout << "Processing " << samples.size() << " samples " << compTerm << "..." << std::endl; + + profileAlgo<1, PROFILE_CONFIG_COUNT>( + score_engine::AlgoType::HyperIdentity, "HyperIdentity", + samples, miningSeeds, publicKeys, nonces, filteredSamples, numberOfThreads, numberOfSamples); + + profileAlgo<1, PROFILE_CONFIG_COUNT>( + score_engine::AlgoType::Addition, "Addition", + samples, miningSeeds, publicKeys, nonces, filteredSamples, numberOfThreads, numberOfSamples); + + profileAlgo<1, PROFILE_CONFIG_COUNT>( + score_engine::AlgoType::MaxAlgoCount, "Mixed", + samples, miningSeeds, publicKeys, nonces, filteredSamples, numberOfThreads, numberOfSamples); + + gProfilingDataCollector.writeToFile(); +} + +#if ENABLE_PROFILING + +TEST(TestQubicScoreFunction, PerformanceTests) +{ + runPerformanceTests(); +} +#endif + +TEST(TestQubicScoreFunction, CommonTests) +{ + runCommonTests(); +} + +#if not ENABLE_PROFILING +TEST(TestQubicScoreFunction, TestDeterministic) +{ + constexpr int NUMBER_OF_THREADS = 4; + constexpr int NUMBER_OF_PHASES = 2; + constexpr int NUMBER_OF_SAMPLES = 4; + + // Read the parameters and results + auto sampleString = readSampleAsStr(COMMON_TEST_SAMPLES_FILE_NAME); + + // Convert the raw string and do the data verification + unsigned long long numberOfSamples = sampleString.size(); + if (COMMON_TEST_NUMBER_OF_SAMPLES > 0) + { + numberOfSamples = std::min(COMMON_TEST_NUMBER_OF_SAMPLES, numberOfSamples); + } + + std::vector miningSeeds(numberOfSamples); + std::vector publicKeys(numberOfSamples); + std::vector nonces(numberOfSamples); + + // Reading the input samples + for (unsigned long long i = 0; i < numberOfSamples; ++i) + { + miningSeeds[i] = hexTo32Bytes(sampleString[i][0], 32); + publicKeys[i] = hexTo32Bytes(sampleString[i][1], 32); + nonces[i] = hexTo32Bytes(sampleString[i][2], 32); + } + + std::unique_ptr> pScore = std::make_unique>(); + pScore->initMemory(); + + // Run with 4 mining seeds, each run 4 separate threads and the result need to matched + int scores[NUMBER_OF_PHASES][NUMBER_OF_THREADS * NUMBER_OF_SAMPLES] = { 0 }; + for (unsigned long long i = 0; i < NUMBER_OF_PHASES; ++i) + { + pScore->initMiningData(miningSeeds[i]); + +#pragma omp parallel for num_threads(NUMBER_OF_THREADS) + for (int threadId = 0; threadId < NUMBER_OF_THREADS; ++threadId) + { + if (threadId % 2 == 0) + { + for (int sampleId = 0; sampleId < NUMBER_OF_SAMPLES; ++sampleId) + { + scores[i][threadId * NUMBER_OF_SAMPLES + sampleId] = (*pScore)(threadId, publicKeys[sampleId], miningSeeds[i], nonces[sampleId]); + } + } + else + { + for (int sampleId = NUMBER_OF_SAMPLES - 1; sampleId >= 0; --sampleId) + { + scores[i][threadId * NUMBER_OF_SAMPLES + sampleId] = (*pScore)(threadId, publicKeys[sampleId], miningSeeds[i], nonces[sampleId]); + } + } + } + } + + // Each threads run with the same samples but the order is reversed. Expect the scores are matched. + for (unsigned long long i = 0; i < NUMBER_OF_PHASES; ++i) + { + for (int threadId = 0; threadId < NUMBER_OF_THREADS - 1; ++threadId) + { + for (int sampleId = 0; sampleId < NUMBER_OF_SAMPLES; ++sampleId) + { + EXPECT_EQ(scores[i][threadId * NUMBER_OF_SAMPLES + sampleId], scores[i][(threadId + 1) * NUMBER_OF_SAMPLES + sampleId]); + } + } + } +} +#endif diff --git a/test/score_addition_reference.h b/test/score_addition_reference.h new file mode 100644 index 000000000..8b95c17ed --- /dev/null +++ b/test/score_addition_reference.h @@ -0,0 +1,869 @@ +#pragma once + +#include "score_common_reference.h" + +#include +#include + +namespace score_addition_reference +{ + +template +struct Miner +{ + // Convert params for easier usage + static constexpr unsigned long long numberOfInputNeurons = Params::numberOfInputNeurons; + static constexpr unsigned long long numberOfOutputNeurons = Params::numberOfOutputNeurons; + static constexpr unsigned long long numberOfTicks = Params::numberOfTicks; + static constexpr unsigned long long maxNumberOfNeighbors = Params::numberOfNeighbors; + static constexpr unsigned long long populationThreshold = Params::populationThreshold; + static constexpr unsigned long long numberOfMutations = Params::numberOfMutations; + static constexpr unsigned int solutionThreshold = Params::solutionThreshold; + + static constexpr unsigned long long numberOfNeurons = + numberOfInputNeurons + numberOfOutputNeurons; + static constexpr unsigned long long maxNumberOfNeurons = populationThreshold; + static constexpr unsigned long long maxNumberOfSynapses = + populationThreshold * maxNumberOfNeighbors; + static constexpr unsigned long long trainingSetSize = 1ULL << numberOfInputNeurons; // 2^K + static constexpr unsigned long long paddingNumberOfSynapses = + (maxNumberOfSynapses + 31 ) / 32 * 32; // padding to multiple of 32 + + static_assert( + maxNumberOfSynapses <= (0xFFFFFFFFFFFFFFFF << 1ULL), + "maxNumberOfSynapses must less than or equal MAX_UINT64/2"); + static_assert(maxNumberOfNeighbors % 2 == 0, "maxNumberOfNeighbors must divided by 2"); + static_assert( + populationThreshold > numberOfNeurons, + "populationThreshold must be greater than numberOfNeurons"); + + std::vector poolVec; + + void initialize(const unsigned char miningSeed[32]) + { + // Init random2 pool with mining seed + poolVec.resize(score_reference::POOL_VEC_PADDING_SIZE); + score_reference::generateRandom2Pool(miningSeed, poolVec.data()); + } + + // Training set + struct TraningPair + { + char input[numberOfInputNeurons]; // numberOfInputNeurons / 2 bits of A , and B (values: -1 or +1) + char output[numberOfOutputNeurons]; // numberOfOutputNeurons bits of C (values: -1 or +1) + } trainingSet[trainingSetSize]; // training set size: 2^K + + struct Synapse + { + char weight; + }; + + // Data for running the ANN + struct Neuron + { + enum Type + { + kInput, + kOutput, + kEvolution, + }; + Type type; + char value; + bool markForRemoval; + }; + + // Data for roll back + struct ANN + { + Neuron neurons[maxNumberOfNeurons]; + Synapse synapses[maxNumberOfSynapses]; + unsigned long long population; + }; + ANN bestANN; + ANN currentANN; + + // Intermediate data + struct InitValue + { + unsigned long long outputNeuronPositions[numberOfOutputNeurons]; + unsigned long long synapseWeight[paddingNumberOfSynapses / 32]; // each 64bits elements will + // decide value of 32 synapses + unsigned long long synpaseMutation[numberOfMutations]; + } initValue; + + unsigned long long neuronIndices[numberOfNeurons]; + char previousNeuronValue[maxNumberOfNeurons]; + + unsigned long long outputNeuronIndices[numberOfOutputNeurons]; + char outputNeuronExpectedValue[numberOfOutputNeurons]; + + long long neuronValueBuffer[maxNumberOfNeurons]; + + unsigned long long getActualNeighborCount() const + { + unsigned long long population = currentANN.population; + unsigned long long maxNeighbors = population - 1; // Exclude self + unsigned long long actual = std::min(maxNumberOfNeighbors, maxNeighbors); + + return actual; + } + + unsigned long long getLeftNeighborCount() const + { + unsigned long long actual = getActualNeighborCount(); + // For odd number, we add extra for the left + return (actual + 1) / 2; + } + + unsigned long long getRightNeighborCount() const + { + return getActualNeighborCount() - getLeftNeighborCount(); + } + + // Get the starting index in synapse buffer (left side start) + unsigned long long getSynapseStartIndex() const + { + constexpr unsigned long long synapseBufferCenter = maxNumberOfNeighbors / 2; + return synapseBufferCenter - getLeftNeighborCount(); + } + + // Get the ending index in synapse buffer (exclusive) + unsigned long long getSynapseEndIndex() const + { + constexpr unsigned long long synapseBufferCenter = maxNumberOfNeighbors / 2; + return synapseBufferCenter + getRightNeighborCount(); + } + + // Convert buffer index to neighbor offset + long long bufferIndexToOffset(unsigned long long bufferIdx) const + { + constexpr long long synapseBufferCenter = maxNumberOfNeighbors / 2; + if (bufferIdx < synapseBufferCenter) + { + return (long long)bufferIdx - synapseBufferCenter; // Negative (left) + } + else + { + return (long long)bufferIdx - synapseBufferCenter + 1; // Positive (right), skip 0 + } + } + + // Convert neighbor offset to buffer index + long long offsetToBufferIndex(long long offset) const + { + constexpr long long synapseBufferCenter = maxNumberOfNeighbors / 2; + if (offset == 0) + { + return -1; // Invalid, exclude self + } + else if (offset < 0) + { + return synapseBufferCenter + offset; + } + else + { + return synapseBufferCenter + offset - 1; + } + } + + long long getIndexInSynapsesBuffer(long long neighborOffset) const + { + long long leftCount = (long long)getLeftNeighborCount(); + long long rightCount = (long long)getRightNeighborCount(); + + if (neighborOffset == 0 || + neighborOffset < -leftCount || + neighborOffset > rightCount) + { + return -1; + } + + return offsetToBufferIndex(neighborOffset); + } + + + + void mutate(unsigned long long mutateStep) + { + // Mutation + unsigned long long population = currentANN.population; + unsigned long long actualNeighbors = getActualNeighborCount(); + Synapse* synapses = currentANN.synapses; + + // Randomly pick a synapse, randomly increase or decrease its weight by 1 or -1 + unsigned long long synapseMutation = initValue.synpaseMutation[mutateStep]; + unsigned long long totalValidSynapses = population * actualNeighbors; + unsigned long long flatIdx = (synapseMutation >> 1) % totalValidSynapses; + + // Convert flat index to (neuronIdx, local synapse index within valid range) + unsigned long long neuronIdx = flatIdx / actualNeighbors; + unsigned long long localSynapseIdx = flatIdx % actualNeighbors; + + // Convert to synapse buffer index that have bigger range + unsigned long long synapseIndex = localSynapseIdx + getSynapseStartIndex(); + unsigned long long synapseFullBufferIdx = neuronIdx * maxNumberOfNeighbors + synapseIndex; + + // Randomly increase or decrease its value + char weightChange = 0; + if ((synapseMutation & 1ULL) == 0) + { + weightChange = -1; + } + else + { + weightChange = 1; + } + + char newWeight = synapses[synapseFullBufferIdx].weight + weightChange; + + // Valid weight. Update it + if (newWeight >= -1 && newWeight <= 1) + { + synapses[synapseFullBufferIdx].weight = newWeight; + } + else // Invalid weight. Insert a neuron + { + // Insert the neuron + insertNeuron(neuronIdx, synapseIndex); + } + + // Clean the ANN + while (scanRedundantNeurons() > 0) + { + cleanANN(); + } + } + + // Get the pointer to all outgoing synapse of a neurons + Synapse* getSynapses(unsigned long long neuronIndex) + { + return ¤tANN.synapses[neuronIndex * maxNumberOfNeighbors]; + } + + // Calculate the new neuron index that is reached by moving from the given `neuronIdx` `value` + // neurons to the right or left. Negative `value` moves to the left, positive `value` moves to + // the right. The return value is clamped in a ring buffer fashion, i.e. moving right of the + // rightmost neuron continues at the leftmost neuron. + unsigned long long clampNeuronIndex(long long neuronIdx, long long value) + { + unsigned long long population = currentANN.population; + assert(value > -(long long)population && value < (long long)population + && "clampNeuronIndex: |value| must be less than population"); + + long long nnIndex = 0; + // Calculate the neuron index (ring structure) + if (value >= 0) + { + nnIndex = neuronIdx + value; + } + else + { + nnIndex = neuronIdx + population + value; + } + nnIndex = nnIndex % population; + return (unsigned long long)nnIndex; + } + + + // Remove a neuron and all synapses relate to it + void removeNeuron(unsigned long long neuronIdx) + { + long long leftCount = (long long)getLeftNeighborCount(); + long long rightCount = (long long)getRightNeighborCount(); + unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); + unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); + + // Scan all its neighbor to remove their outgoing synapse point to the neuron + for (long long neighborOffset = -leftCount; neighborOffset <= rightCount; neighborOffset++) + { + if (neighborOffset == 0) continue; + + unsigned long long nnIdx = clampNeuronIndex(neuronIdx, neighborOffset); + Synapse* pNNSynapses = getSynapses(nnIdx); + + long long synapseIndexOfNN = getIndexInSynapsesBuffer(-neighborOffset); + if (synapseIndexOfNN < 0) + { + continue; + } + + // The synapse array need to be shifted regard to the remove neuron + // Also neuron need to have 2M neighbors, the addtional synapse will be set as zero + // weight Case1 [S0 S1 S2 - SR S5 S6]. SR is removed, [S0 S1 S2 S5 S6 0] Case2 [S0 S1 SR + // - S3 S4 S5]. SR is removed, [0 S0 S1 S3 S4 S5] + constexpr unsigned long long halfMax = maxNumberOfNeighbors / 2; + if (synapseIndexOfNN >= (long long)halfMax) + { + for (long long k = synapseIndexOfNN; k < (long long)endSynapseBufferIdx - 1; ++k) + { + pNNSynapses[k] = pNNSynapses[k + 1]; + } + pNNSynapses[endSynapseBufferIdx - 1].weight = 0; + } + else + { + for (long long k = synapseIndexOfNN; k > (long long)startSynapseBufferIdx; --k) + { + pNNSynapses[k] = pNNSynapses[k - 1]; + } + pNNSynapses[startSynapseBufferIdx].weight = 0; + } + } + + // Shift the synapse array and the neuron array + for (unsigned long long shiftIdx = neuronIdx; shiftIdx < currentANN.population - 1; shiftIdx++) + { + currentANN.neurons[shiftIdx] = currentANN.neurons[shiftIdx + 1]; + + // Also shift the synapses + memcpy( + getSynapses(shiftIdx), + getSynapses(shiftIdx + 1), + maxNumberOfNeighbors * sizeof(Synapse)); + } + currentANN.population--; + } + + unsigned long long + getNeighborNeuronIndex(unsigned long long neuronIndex, unsigned long long neighborOffset) + { + const unsigned long long leftNeighbors = getLeftNeighborCount(); + unsigned long long nnIndex = 0; + if (neighborOffset < leftNeighbors) + { + nnIndex = clampNeuronIndex( + neuronIndex + neighborOffset, -(long long)leftNeighbors); + } + else + { + nnIndex = clampNeuronIndex( + neuronIndex + neighborOffset + 1, -(long long)leftNeighbors); + } + return nnIndex; + } + + void insertNeuron(unsigned long long neuronIndex, unsigned long long synapseIndex) + { + unsigned long long synapseFullBufferIdx = neuronIndex * maxNumberOfNeighbors + synapseIndex; + // Old value before insert neuron + unsigned long long oldStartSynapseBufferIdx = getSynapseStartIndex(); + unsigned long long oldEndSynapseBufferIdx = getSynapseEndIndex(); + unsigned long long oldActualNeighbors = getActualNeighborCount(); + long long oldLeftCount = (long long)getLeftNeighborCount(); + long long oldRightCount = (long long)getRightNeighborCount(); + + constexpr unsigned long long halfMax = maxNumberOfNeighbors / 2; + + // Validate synapse index is within valid range + assert(synapseIndex >= oldStartSynapseBufferIdx && synapseIndex < oldEndSynapseBufferIdx); + + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + unsigned long long& population = currentANN.population; + + // Copy original neuron to the inserted one and set it as Neuron::kEvolution type + Neuron insertNeuron; + insertNeuron = neurons[neuronIndex]; + insertNeuron.type = Neuron::kEvolution; + unsigned long long insertedNeuronIdx = neuronIndex + 1; + + char originalWeight = synapses[synapseFullBufferIdx].weight; + + // Insert the neuron into array, population increased one, all neurons next to original one + // need to shift right + for (unsigned long long i = population; i > neuronIndex; --i) + { + neurons[i] = neurons[i - 1]; + + // Also shift the synapses to the right + memcpy(getSynapses(i), getSynapses(i - 1), maxNumberOfNeighbors * sizeof(Synapse)); + } + neurons[insertedNeuronIdx] = insertNeuron; + population++; + + // Recalculate after population change + unsigned long long newActualNeighbors = getActualNeighborCount(); + unsigned long long newStartSynapseBufferIdx = getSynapseStartIndex(); + unsigned long long newEndSynapseBufferIdx = getSynapseEndIndex(); + + // Try to update the synapse of inserted neuron. All outgoing synapse is init as zero weight + Synapse* pInsertNeuronSynapse = getSynapses(insertedNeuronIdx); + for (unsigned long long synIdx = 0; synIdx < maxNumberOfNeighbors; ++synIdx) + { + pInsertNeuronSynapse[synIdx].weight = 0; + } + + // Copy the outgoing synapse of original neuron + if (synapseIndex < halfMax) + { + // The synapse is going to a neuron to the left of the original neuron. + // Check if the incoming neuron is still contained in the neighbors of the inserted + // neuron. This is the case if the original `synapseIndex` is > 0, i.e. + // the original synapse if not going to the leftmost neighbor of the original neuron. + if (synapseIndex > newStartSynapseBufferIdx) + { + // Decrease idx by one because the new neuron is inserted directly to the right of + // the original one. + pInsertNeuronSynapse[synapseIndex - 1].weight = originalWeight; + } + // If the incoming neuron of the original synapse if not contained in the neighbors of + // the inserted neuron, don't add the synapse. + } + else + { + // The synapse is going to a neuron to the right of the original neuron. + // In this case, the incoming neuron of the synapse is for sure contained in the + // neighbors of the inserted neuron and has the same idx (right side neighbors of + // inserted neuron = right side neighbors of original neuron before insertion). + pInsertNeuronSynapse[synapseIndex].weight = originalWeight; + } + + // The change of synapse only impact neuron in [originalNeuronIdx - actualNeighbors / 2 + // + 1, originalNeuronIdx + actualNeighbors / 2] In the new index, it will be + // [originalNeuronIdx + 1 - actualNeighbors / 2, originalNeuronIdx + 1 + + // actualNeighbors / 2] [N0 N1 N2 original inserted N4 N5 N6], M = 2. + for (long long delta = -oldLeftCount; delta <= oldRightCount; ++delta) + { + // Only process the neighbors + if (delta == 0) + { + continue; + } + unsigned long long updatedNeuronIdx = clampNeuronIndex(insertedNeuronIdx, delta); + + // Generate a list of neighbor index of current updated neuron NN + // Find the location of the inserted neuron in the list of neighbors + long long insertedNeuronIdxInNeigborList = -1; + for (unsigned long long k = 0; k < newActualNeighbors; k++) + { + unsigned long long nnIndex = getNeighborNeuronIndex(updatedNeuronIdx, k); + if (nnIndex == insertedNeuronIdx) + { + insertedNeuronIdxInNeigborList = (long long)(newStartSynapseBufferIdx + k); + } + } + + assert(insertedNeuronIdxInNeigborList >= 0); + + Synapse* pUpdatedSynapses = getSynapses(updatedNeuronIdx); + // [N0 N1 N2 original inserted N4 N5 N6], M = 2. + // Case: neurons in range [N0 N1 N2 original], right synapses will be affected + if (delta < 0) + { + // Left side is kept as it is, only need to shift to the right side + for (long long k = (long long)newEndSynapseBufferIdx - 1; k >= insertedNeuronIdxInNeigborList; --k) + { + // Updated synapse + pUpdatedSynapses[k] = pUpdatedSynapses[k - 1]; + } + + // Incomming synapse from original neuron -> inserted neuron must be zero + if (delta == -1) + { + pUpdatedSynapses[insertedNeuronIdxInNeigborList].weight = 0; + } + } + else // Case: neurons in range [inserted N4 N5 N6], left synapses will be affected + { + // Right side is kept as it is, only need to shift to the left side + for (long long k = (long long)newStartSynapseBufferIdx; k < insertedNeuronIdxInNeigborList; ++k) + { + // Updated synapse + pUpdatedSynapses[k] = pUpdatedSynapses[k + 1]; + } + } + } + } + + + // Check which neurons/synapse need to be removed after mutation + unsigned long long scanRedundantNeurons() + { + unsigned long long population = currentANN.population; + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + + unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); + unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); + long long leftCount = (long long)getLeftNeighborCount(); + long long rightCount = (long long)getRightNeighborCount(); + + unsigned long long numberOfRedundantNeurons = 0; + // After each mutation, we must verify if there are neurons that do not affect the ANN + // output. These are neurons that either have all incoming synapse weights as 0, or all + // outgoing synapse weights as 0. Such neurons must be removed. + for (unsigned long long i = 0; i < population; i++) + { + neurons[i].markForRemoval = false; + if (neurons[i].type == Neuron::kEvolution) + { + bool allOutGoingZeros = true; + bool allIncommingZeros = true; + + // Loop though its synapses for checkout outgoing synapses + for (unsigned long long m = startSynapseBufferIdx; m < endSynapseBufferIdx; m++) + { + char synapseW = synapses[i * maxNumberOfNeighbors + m].weight; + if (synapseW != 0) + { + allOutGoingZeros = false; + break; + } + } + + // Loop through the neighbor neurons to check all incoming synapses + for (long long offset = -leftCount; offset <= rightCount; offset++) + { + if (offset == 0) continue; + + unsigned long long nnIdx = clampNeuronIndex(i, offset); + long long synapseIdx = getIndexInSynapsesBuffer(-offset); + if (synapseIdx < 0) + { + continue; + } + char synapseW = getSynapses(nnIdx)[synapseIdx].weight; + + if (synapseW != 0) + { + allIncommingZeros = false; + break; + } + } + if (allOutGoingZeros || allIncommingZeros) + { + neurons[i].markForRemoval = true; + numberOfRedundantNeurons++; + } + } + } + return numberOfRedundantNeurons; + } + + // Remove neurons and synapses that do not affect the ANN + void cleanANN() + { + Neuron* neurons = currentANN.neurons; + unsigned long long& population = currentANN.population; + + // Scan and remove neurons/synapses + unsigned long long neuronIdx = 0; + while (neuronIdx < population) + { + if (neurons[neuronIdx].markForRemoval) + { + // Remove it from the neuron list. Overwrite data + // Remove its synapses in the synapses array + removeNeuron(neuronIdx); + } + else + { + neuronIdx++; + } + } + } + + void processTick() + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + + // Memset value of current one + memset(neuronValueBuffer, 0, sizeof(neuronValueBuffer)); + + // Loop though all neurons + unsigned long long startSynapseBufferIdx = getSynapseStartIndex(); + unsigned long long endSynapseBufferIdx = getSynapseEndIndex(); + + for (unsigned long long n = 0; n < population; ++n) + { + const Synapse* kSynapses = getSynapses(n); + long long neuronValue = neurons[n].value; + // Scan through all neighbor neurons and sum all connected neurons. + for (unsigned long long m = startSynapseBufferIdx; m < endSynapseBufferIdx; m++) + { + char synapseWeight = kSynapses[m].weight; + long long offset = bufferIndexToOffset(m); + unsigned long long nnIndex = clampNeuronIndex(static_cast(n), offset); + + // Weight-sum + neuronValueBuffer[nnIndex] += synapseWeight * neuronValue; + } + } + + // Clamp the neuron value + for (unsigned long long n = 0; n < population; ++n) + { + // Only non input neurons are updated + if (Neuron::kInput != neurons[n].type) + { + char neuronValue = score_reference::clampNeuron(neuronValueBuffer[n]); + neurons[n].value = neuronValue; + } + } + } + void loadTrainingData(unsigned long long trainingIndex) + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + + const auto& data = trainingSet[trainingIndex]; + // Load the input neuron value + unsigned long long inputIndex = 0; + for (unsigned long long n = 0; n < population; ++n) + { + // Init as zeros + neurons[n].value = 0; + if (Neuron::kInput == neurons[n].type) + { + neurons[n].value = data.input[inputIndex]; + inputIndex++; + } + } + + // Load the expected output value + memcpy(outputNeuronExpectedValue, data.output, sizeof(outputNeuronExpectedValue[0]) * numberOfOutputNeurons); + } + // Tick simulation only runs on one ANN + void runTickSimulation(unsigned long long trainingIndex) + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + + // Load the training set and fill ANN value + loadTrainingData(trainingIndex); + + // Save the neuron value for comparison + for (unsigned long long i = 0; i < population; ++i) + { + // Backup the neuron value + previousNeuronValue[i] = neurons[i].value; + } + + for (unsigned long long tick = 0; tick < numberOfTicks; ++tick) + { + processTick(); + // Check exit conditions: + // - N ticks have passed (already in for loop) + // - All neuron values are unchanged + // - All output neurons have non-zero values + bool allNeuronsUnchanged = true; + bool allOutputNeuronsIsNonZeros = true; + for (unsigned long long n = 0; n < population; ++n) + { + // Neuron unchanged check + if (previousNeuronValue[n] != neurons[n].value) + { + allNeuronsUnchanged = false; + } + + // Ouput neuron value check + if (neurons[n].type == Neuron::kOutput && neurons[n].value == 0) + { + allOutputNeuronsIsNonZeros = false; + } + } + + if (allOutputNeuronsIsNonZeros || allNeuronsUnchanged) + { + break; + } + + // Copy the neuron value + for (unsigned long long n = 0; n < population; ++n) + { + previousNeuronValue[n] = neurons[n].value; + } + } + } + + unsigned int computeMatchingOutput() + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + + // Compute the non-matching value R between output neuron value and initial value + // Because the output neuron order never changes, the order is preserved + unsigned int R = 0; + unsigned long long outputIdx = 0; + for (unsigned long long i = 0; i < population; i++) + { + if (neurons[i].type == Neuron::kOutput) + { + if (neurons[i].value == outputNeuronExpectedValue[outputIdx]) + { + R++; + } + outputIdx++; + } + } + return R; + } + + // Generate all 2^K possible (A, B, C) pairs + void generateTrainingSet() + { + static constexpr long long boundValue = (1LL << (numberOfInputNeurons / 2)) / 2; + unsigned long long index = 0; + for (long long A = -boundValue; A < boundValue; A++) + { + for (long long B = -boundValue; B < boundValue; B++) + { + long long C = A + B; + + score_reference::toTenaryBits(A, trainingSet[index].input); + score_reference::toTenaryBits( + B, trainingSet[index].input + numberOfInputNeurons / 2); + score_reference::toTenaryBits(C, trainingSet[index].output); + index++; + } + } + } + + unsigned int inferANN() + { + unsigned int score = 0; + for (unsigned long long i = 0; i < trainingSetSize; ++i) + { + // Ticks simulation + runTickSimulation(i); + + // Compute R + unsigned int R = computeMatchingOutput(); + score += R; + } + return score; + } + + unsigned int initializeANN(const unsigned char* publicKey, const unsigned char* nonce) + { + unsigned char hash[32]; + unsigned char combined[64]; + memcpy(combined, publicKey, 32); + memcpy(combined + 32, nonce, 32); + KangarooTwelve(combined, 64, hash, 32); + + unsigned long long& population = currentANN.population; + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + + // Initialization + population = numberOfNeurons; + + // Generate all 2^K possible (A, B, C) pairs + generateTrainingSet(); + + // Initalize with nonce and public key + score_reference::random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); + + // Randomly choose the positions of neurons types + for (unsigned long long i = 0; i < population; ++i) + { + neuronIndices[i] = i; + neurons[i].type = Neuron::kInput; + } + unsigned long long neuronCount = population; + for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) + { + unsigned long long outputNeuronIdx = initValue.outputNeuronPositions[i] % neuronCount; + + // Fill the neuron type + neurons[neuronIndices[outputNeuronIdx]].type = Neuron::kOutput; + outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; + + // This index is used, copy the end of indices array to current position and decrease + // the number of picking neurons + neuronCount = neuronCount - 1; + neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; + } + + // Synapse weight initialization + auto extractWeight = [](unsigned long long packedValue, unsigned long long position) -> char { + unsigned char extractValue = static_cast((packedValue >> (position * 2)) & 0b11); + switch (extractValue) + { + case 2: + return -1; + case 3: + return 1; + default: + return 0; + } + }; + for (unsigned long long i = 0; i < (maxNumberOfSynapses / 32); ++i) + { + for (unsigned long long j = 0; j < 32; ++j) + { + synapses[32 * i + j].weight = extractWeight(initValue.synapseWeight[i], j); + } + } + + // Handle remaining synapses (if maxNumberOfSynapses not divisible by 32) + unsigned long long remainder = maxNumberOfSynapses % 32; + if (remainder > 0) + { + unsigned long long lastBlock = maxNumberOfSynapses / 32; + for (unsigned long long j = 0; j < remainder; ++j) + { + synapses[32 * lastBlock + j].weight = extractWeight(initValue.synapseWeight[lastBlock], j); + } + } + + // Run the first inference to get starting point before mutation + unsigned int score = inferANN(); + + return score; + } + + // Main function for mining + unsigned int computeScore(const unsigned char* publicKey, const unsigned char* nonce) + { + // Initialize + unsigned int bestR = initializeANN(publicKey, nonce); + memcpy(&bestANN, ¤tANN, sizeof(bestANN)); + + for (unsigned long long s = 0; s < numberOfMutations; ++s) + { + // Do the mutation + mutate(s); + + // Exit if the number of population reaches the maximum allowed + if (currentANN.population >= populationThreshold) + { + break; + } + + // Ticks simulation + unsigned int R = inferANN(); + + // Roll back if neccessary + if (R >= bestR) + { + bestR = R; + // Better R. Save the state + memcpy(&bestANN, ¤tANN, sizeof(bestANN)); + } + else + { + // Roll back + memcpy(¤tANN, &bestANN, sizeof(bestANN)); + } + + assert(bestANN.population <= populationThreshold); + } + return bestR; + } + + bool findSolution(const unsigned char* publicKey, const unsigned char* nonce) + { + unsigned int score = computeScore(publicKey, nonce); + if (score >= solutionThreshold) + { + return true; + } + + return false; + } +}; + +} // namespace score_addition diff --git a/test/score_cache.cpp b/test/score_cache.cpp new file mode 100644 index 000000000..24678308c --- /dev/null +++ b/test/score_cache.cpp @@ -0,0 +1,201 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#include "../src/score_cache.h" + +#include + + +template +void expectEmptyCache(ScoreCache& cache) +{ + EXPECT_EQ(cache.hitCount(), 0); + EXPECT_EQ(cache.collisionCount(), 0); + EXPECT_EQ(cache.missCount(), 0); + + // test that all is empty and access out of bounds is no error + for (unsigned int i = 0; i < cache.capacity() + 100; ++i) + { + // test with arbitrary publicKey and nonce (real and pseudo-random is slow, so use a fast quite random pattern) + unsigned long long a = i * 123456789ull; + unsigned long long b = 0xbca326450256c63eull - i * 759037ull; + unsigned long long c = 2345932453043560ull << (i & 63); + m256i publicKey(a ^ b, a ^ c, b ^ c, a ^ b ^ c); + m256i miningSeed = m256i(1, 1, 1, 1); + m256i nonce((a << 2) ^ b, (a << 2) ^ c, (b << 1) ^ c, (b >> 1) ^ c); + + unsigned int ioIdx = i; + EXPECT_EQ(cache.tryFetching(publicKey, miningSeed, nonce, ioIdx), cache.SCORE_CACHE_MISS); + EXPECT_TRUE(ioIdx == i || (i >= cache.capacity() && ioIdx == i % cache.capacity())); + } +} + +template +unsigned int pseudoRandomCacheTest(ScoreCache & cache, unsigned long long seed, unsigned int entryCount, bool overwrite) +{ + cache.reset(); + + // add entries with pseudo-random data + std::mt19937_64 gen64; + gen64.seed(seed); + for (unsigned int i = 0; i < entryCount; ++i) + { + m256i publicKey(gen64(), gen64(), gen64(), gen64()); + m256i miningSeed(gen64(), gen64(), gen64(), gen64()); + m256i nonce(gen64(), gen64(), gen64(), gen64()); + int score = gen64() % std::numeric_limits::max(); + assert(score >= 0); + unsigned int idx = cache.getCacheIndex(publicKey, miningSeed, nonce); + int fetchedScore = cache.tryFetching(publicKey, miningSeed, nonce, idx); + + // assume that we will not get the same publicKey and nonce twice in random entry generation + EXPECT_TRUE(fetchedScore == cache.SCORE_CACHE_MISS || fetchedScore == cache.SCORE_CACHE_COLLISION); + + if (fetchedScore != cache.SCORE_CACHE_COLLISION || overwrite) + { + cache.addEntry(publicKey, miningSeed, nonce, idx, score); + } + } + EXPECT_EQ(entryCount, cache.missCount() + cache.collisionCount()); + EXPECT_EQ(cache.hitCount(), 0); + + //std::cout << "randomCacheTest: capacity " << cacheCapacity << ", filled " << 100.0 * entryCount / cacheCapacity << "%, overwrite=" << overwrite + // << ", collisions " << cache.collisionCount() << ", misses " << cache.missCount() << ", hits " << cache.hitCount() << " (SEED " << seed << ")" << std::endl; + + int collisionCount = cache.collisionCount(); + + // test entries with pseudo-random data + gen64.seed(seed); + for (unsigned int i = 0; i < entryCount; ++i) + { + m256i publicKey(gen64(), gen64(), gen64(), gen64()); + m256i miningSeed(gen64(), gen64(), gen64(), gen64()); + m256i nonce(gen64(), gen64(), gen64(), gen64()); + int expectedScore = gen64() % std::numeric_limits::max(); + + unsigned int idx = cache.getCacheIndex(publicKey, miningSeed, nonce); + int fetchedScore = cache.tryFetching(publicKey, miningSeed, nonce, idx); + + EXPECT_TRUE(fetchedScore == cache.SCORE_CACHE_COLLISION || fetchedScore >= cache.MIN_VALID_SCORE); + if (fetchedScore >= cache.MIN_VALID_SCORE) + { + EXPECT_EQ(fetchedScore, expectedScore); + } + } + + EXPECT_EQ(entryCount * 2, cache.missCount() + cache.collisionCount() + cache.hitCount()); + + + return collisionCount; +} + +template +void testCacheSameSeeds(unsigned int fillPercent) +{ + typedef ScoreCache CacheType; + CacheType* cache = new CacheType(); + + expectEmptyCache(*cache); + + bool overwrite = true; + const int entryCount = (unsigned long long)cacheCapacity * fillPercent / 100; + unsigned int collisionCount = 0; + collisionCount += pseudoRandomCacheTest(*cache, 0, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 1234, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 42, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 987654321, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 1234573574564560925, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 234563875344, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 3245789, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 9357637, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 23648682, entryCount, overwrite); + collisionCount += pseudoRandomCacheTest(*cache, 347692997236, entryCount, overwrite); + std::cout << "Total collision count with capacity " << cacheCapacity << " (10 tests): " << collisionCount << std::endl; + + cache->reset(); + expectEmptyCache(*cache); + + delete cache; +} + +template +void testCacheRandomSeeds(unsigned int fillPercent) +{ + typedef ScoreCache CacheType; + CacheType* cache = new CacheType(); + + expectEmptyCache(*cache); + + bool overwrite = true; + const int entryCount = (unsigned long long)cacheCapacity * fillPercent / 100; + unsigned int collisionCount = 0; + for (int i = 0; i < 10; ++i) + { + unsigned long long seed; + _rdrand64_step(&seed); + collisionCount += pseudoRandomCacheTest(*cache, seed, entryCount, overwrite); + } + std::cout << "Total collision count with capacity " << cacheCapacity << " (10 tests): " << collisionCount << std::endl; + + cache->reset(); + expectEmptyCache(*cache); + + delete cache; +} + + +// Some people claimed that the hash table will have less collisions if the capacity +// is a prime number. The experiments with our table do not support this claim. +// A search in the internet showed that you only need prime number capacity +// if your hash function is not good. +// http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html +// So it seems like our hash function is good and we do not need prime numbers. + +TEST(TestQubicScoreCache, FixedSeeds20pctFilled) { + testCacheSameSeeds<1000000>(20); // non-prime number as cache size + testCacheSameSeeds<1000003>(20); // prime number as cache size + + testCacheSameSeeds<200000>(20); // non-prime number as cache size + testCacheSameSeeds<199999>(20); // prime number as cache size +} + +TEST(TestQubicScoreCache, FixedSeeds50pctFilled) { + testCacheSameSeeds<1000000>(50); // non-prime number as cache size + testCacheSameSeeds<1000003>(50); // prime number as cache size + + testCacheSameSeeds<200000>(50); // non-prime number as cache size + testCacheSameSeeds<199999>(50); // prime number as cache size +} + +TEST(TestQubicScoreCache, FixedSeeds80pctFilled) { + testCacheSameSeeds<1000000>(80); // non-prime number as cache size + testCacheSameSeeds<1000003>(80); // prime number as cache size + + testCacheSameSeeds<200000>(80); // non-prime number as cache size + testCacheSameSeeds<199999>(80); // prime number as cache size +} + +TEST(TestQubicScoreCache, RandomSeeds20pctFilled) { + testCacheRandomSeeds<1000000>(20); // non-prime number as cache size + testCacheRandomSeeds<1000003>(20); // prime number as cache size + + testCacheRandomSeeds<200000>(20); // non-prime number as cache size + testCacheRandomSeeds<199999>(20); // prime number as cache size +} + +TEST(TestQubicScoreCache, RandomSeeds50pctFilled) { + testCacheRandomSeeds<1000000>(50); // non-prime number as cache size + testCacheRandomSeeds<1000003>(50); // prime number as cache size + + testCacheRandomSeeds<200000>(50); // non-prime number as cache size + testCacheRandomSeeds<199999>(50); // prime number as cache size +} + +TEST(TestQubicScoreCache, RandomSeeds80pctFilled) { + testCacheRandomSeeds<1000000>(80); // non-prime number as cache size + testCacheRandomSeeds<1000003>(80); // prime number as cache size + + testCacheRandomSeeds<200000>(80); // non-prime number as cache size + testCacheRandomSeeds<199999>(80); // prime number as cache size +} diff --git a/test/score_common_reference.h b/test/score_common_reference.h new file mode 100644 index 000000000..62edf5d1b --- /dev/null +++ b/test/score_common_reference.h @@ -0,0 +1,108 @@ +#pragma once + +#include "kangaroo_twelve.h" + +#include + +namespace score_reference +{ + +constexpr unsigned long long POOL_VEC_SIZE = (((1ULL << 32) + 64)) >> 3; // 2^32+64 bits ~ 512MB +constexpr unsigned long long POOL_VEC_PADDING_SIZE = (POOL_VEC_SIZE + 200 - 1) / 200 * 200; // padding for multiple of 200 + +void generateRandom2Pool(const unsigned char miningSeed[32], unsigned char* pool) +{ + unsigned char state[200]; + // same pool to be used by all computors/candidates and pool content changing each phase + memcpy(&state[0], miningSeed, 32); + memset(&state[32], 0, sizeof(state) - 32); + + for (unsigned int i = 0; i < POOL_VEC_PADDING_SIZE; i += sizeof(state)) + { + KeccakP1600_Permute_12rounds(state); + memcpy(&pool[i], state, sizeof(state)); + } +} + +void random2( + unsigned char seed[32], + const unsigned char* pool, + unsigned char* output, + unsigned long long outputSizeInByte) +{ + unsigned long long paddingOutputSize = (outputSizeInByte + 64 - 1) / 64; + paddingOutputSize = paddingOutputSize * 64; + std::vector paddingOutputVec(paddingOutputSize); + unsigned char* paddingOutput = paddingOutputVec.data(); + + unsigned long long segments = paddingOutputSize / 64; + unsigned int x[8] = { 0 }; + for (int i = 0; i < 8; i++) + { + x[i] = ((unsigned int*)seed)[i]; + } + + for (int j = 0; j < segments; j++) + { + // Each segment will have 8 elements. Each element have 8 bytes + for (int i = 0; i < 8; i++) + { + unsigned int base = (x[i] >> 3) >> 3; + unsigned int m = x[i] & 63; + + unsigned long long u64_0 = ((unsigned long long*)pool)[base]; + unsigned long long u64_1 = ((unsigned long long*)pool)[base + 1]; + + // Move 8 * 8 * j to the current segment. 8 * i to current 8 bytes element + if (m == 0) + { + // some compiler doesn't work with bit shift 64 + *((unsigned long long*) & paddingOutput[j * 8 * 8 + i * 8]) = u64_0; + } + else + { + *((unsigned long long*) & paddingOutput[j * 8 * 8 + i * 8]) = (u64_0 >> m) | (u64_1 << (64 - m)); + } + + // Increase the positions in the pool for each element. + x[i] = x[i] * 1664525 + 1013904223; // https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use + } + } + + memcpy(output, paddingOutput, outputSizeInByte); +} + +// Clamp the neuron value +template +char clampNeuron(T neuronValue) +{ + if (neuronValue > 1) + { + return 1; + } + + if (neuronValue < -1) + { + return -1; + } + return static_cast(neuronValue); +} + +void extract64Bits(unsigned long long number, char* output) +{ + for (int i = 0; i < 64; ++i) + { + output[i] = ((number >> i) & 1); + } +} + +template +void toTenaryBits(long long A, char* bits) +{ + for (unsigned long long i = 0; i < bitCount; ++i) + { + char bitValue = static_cast((A >> i) & 1); + bits[i] = (bitValue == 0) ? -1 : bitValue; + } +} +} diff --git a/test/score_hyperidentity_reference.h b/test/score_hyperidentity_reference.h new file mode 100644 index 000000000..3be6f924f --- /dev/null +++ b/test/score_hyperidentity_reference.h @@ -0,0 +1,785 @@ +#pragma once + +#include "../src/mining/score_common.h" +#include "score_common_reference.h" + +namespace score_hyberidentity_reference +{ +template +struct Miner +{ + // Convert params for easier usage + static constexpr unsigned long long numberOfInputNeurons = Params::numberOfInputNeurons; + static constexpr unsigned long long numberOfOutputNeurons = Params::numberOfOutputNeurons; + static constexpr unsigned long long numberOfTicks = Params::numberOfTicks; + static constexpr unsigned long long numberOfNeighbors = Params::numberOfNeighbors; + static constexpr unsigned long long populationThreshold = Params::populationThreshold; + static constexpr unsigned long long numberOfMutations = Params::numberOfMutations; + static constexpr unsigned int solutionThreshold = Params::solutionThreshold; + + // Condition and predifined + static constexpr unsigned long long numberOfNeurons = + numberOfInputNeurons + numberOfOutputNeurons; + static constexpr unsigned long long maxNumberOfNeurons = populationThreshold; + static constexpr unsigned long long maxNumberOfSynapses = + populationThreshold * numberOfNeighbors; + static constexpr unsigned long long initNumberOfSynapses = numberOfNeurons * numberOfNeighbors; + + static_assert(numberOfInputNeurons % 64 == 0, "numberOfInputNeurons must be divided by 64"); + static_assert(numberOfOutputNeurons % 64 == 0, "numberOfOutputNeurons must be divided by 64"); + static_assert( + maxNumberOfSynapses <= (0xFFFFFFFFFFFFFFFF << 1ULL), + "maxNumberOfSynapses must less than or equal MAX_UINT64/2"); + static_assert(initNumberOfSynapses % 32 == 0, "initNumberOfSynapses must be divided by 32"); + static_assert(numberOfNeighbors % 2 == 0, "numberOfNeighbors must divided by 2"); + static_assert( + populationThreshold > numberOfNeurons, + "populationThreshold must be greater than numberOfNeurons"); + static_assert( + numberOfNeurons > numberOfNeighbors, + "Number of neurons must be greater than the number of neighbors"); + + std::vector poolVec; + + void initialize(const unsigned char miningSeed[32]) + { + // Init random2 pool with mining seed + poolVec.resize(score_reference::POOL_VEC_PADDING_SIZE); + score_reference::generateRandom2Pool(miningSeed, poolVec.data()); + } + + struct Synapse + { + char weight; + }; + + // Data for running the ANN + struct Neuron + { + enum Type + { + kInput, + kOutput, + kEvolution, + }; + Type type; + char value; + bool markForRemoval; + }; + + // Data for roll back + struct ANN + { + Neuron neurons[maxNumberOfNeurons]; + Synapse synapses[maxNumberOfSynapses]; + unsigned long long population; + }; + ANN bestANN; + ANN currentANN; + + // Intermediate data + struct InitValue + { + unsigned long long outputNeuronPositions[numberOfOutputNeurons]; + unsigned long long synapseWeight[initNumberOfSynapses / 32]; // each 64bits elements will + // decide value of 32 synapses + unsigned long long synpaseMutation[numberOfMutations]; + } initValue; + + struct MiningData + { + unsigned long long + inputNeuronRandomNumber[numberOfInputNeurons / 64]; // each bit will use for generate + // input neuron value + unsigned long long + outputNeuronRandomNumber[numberOfOutputNeurons / 64]; // each bit will use for generate + // expected output neuron value + } miningData; + + unsigned long long neuronIndices[numberOfNeurons]; + char previousNeuronValue[maxNumberOfNeurons]; + + unsigned long long outputNeuronIndices[numberOfOutputNeurons]; + char outputNeuronExpectedValue[numberOfOutputNeurons]; + + long long neuronValueBuffer[maxNumberOfNeurons]; + + void mutate(const unsigned char nonce[32], unsigned long long mutateStep) + { + // Mutation + unsigned long long population = currentANN.population; + unsigned long long synapseCount = population * numberOfNeighbors; + Synapse* synapses = currentANN.synapses; + + // Randomly pick a synapse, randomly increase or decrease its weight by 1 or -1 + unsigned long long synapseMutation = initValue.synpaseMutation[mutateStep]; + unsigned long long synapseIdx = (synapseMutation >> 1) % synapseCount; + // Randomly increase or decrease its value + char weightChange = 0; + if ((synapseMutation & 1ULL) == 0) + { + weightChange = -1; + } + else + { + weightChange = 1; + } + + char newWeight = synapses[synapseIdx].weight + weightChange; + + // Valid weight. Update it + if (newWeight >= -1 && newWeight <= 1) + { + synapses[synapseIdx].weight = newWeight; + } + else // Invalid weight. Insert a neuron + { + // Insert the neuron + insertNeuron(synapseIdx); + } + + // Clean the ANN + while (scanRedundantNeurons() > 0) + { + cleanANN(); + } + } + + // Get the pointer to all outgoing synapse of a neurons + Synapse* getSynapses(unsigned long long neuronIndex) + { + return ¤tANN.synapses[neuronIndex * numberOfNeighbors]; + } + + // Calculate the new neuron index that is reached by moving from the given `neuronIdx` `value` + // neurons to the right or left. Negative `value` moves to the left, positive `value` moves to + // the right. The return value is clamped in a ring buffer fashion, i.e. moving right of the + // rightmost neuron continues at the leftmost neuron. + unsigned long long clampNeuronIndex(long long neuronIdx, long long value) + { + unsigned long long population = currentANN.population; + long long nnIndex = 0; + // Calculate the neuron index (ring structure) + if (value >= 0) + { + nnIndex = neuronIdx + value; + } + else + { + nnIndex = neuronIdx + population + value; + } + nnIndex = nnIndex % population; + return (unsigned long long)nnIndex; + } + + // Remove a neuron and all synapses relate to it + void removeNeuron(unsigned long long neuronIdx) + { + // Scan all its neigbor to remove their outgoing synapse point to the neuron + for (long long neighborOffset = -(long long)numberOfNeighbors / 2; + neighborOffset <= (long long)numberOfNeighbors / 2; + neighborOffset++) + { + unsigned long long nnIdx = clampNeuronIndex(neuronIdx, neighborOffset); + Synapse* pNNSynapses = getSynapses(nnIdx); + + long long synapseIndexOfNN = getIndexInSynapsesBuffer(nnIdx, -neighborOffset); + if (synapseIndexOfNN < 0) + { + continue; + } + + // The synapse array need to be shifted regard to the remove neuron + // Also neuron need to have 2M neighbors, the addtional synapse will be set as zero + // weight Case1 [S0 S1 S2 - SR S5 S6]. SR is removed, [S0 S1 S2 S5 S6 0] Case2 [S0 S1 SR + // - S3 S4 S5]. SR is removed, [0 S0 S1 S3 S4 S5] + if (synapseIndexOfNN >= numberOfNeighbors / 2) + { + for (long long k = synapseIndexOfNN; k < numberOfNeighbors - 1; ++k) + { + pNNSynapses[k] = pNNSynapses[k + 1]; + } + pNNSynapses[numberOfNeighbors - 1].weight = 0; + } + else + { + for (long long k = synapseIndexOfNN; k > 0; --k) + { + pNNSynapses[k] = pNNSynapses[k - 1]; + } + pNNSynapses[0].weight = 0; + } + } + + // Shift the synapse array and the neuron array + for (unsigned long long shiftIdx = neuronIdx; shiftIdx < currentANN.population; shiftIdx++) + { + currentANN.neurons[shiftIdx] = currentANN.neurons[shiftIdx + 1]; + + // Also shift the synapses + memcpy( + getSynapses(shiftIdx), + getSynapses(shiftIdx + 1), + numberOfNeighbors * sizeof(Synapse)); + } + currentANN.population--; + } + + unsigned long long + getNeighborNeuronIndex(unsigned long long neuronIndex, unsigned long long neighborOffset) + { + unsigned long long nnIndex = 0; + if (neighborOffset < (numberOfNeighbors / 2)) + { + nnIndex = + clampNeuronIndex(neuronIndex + neighborOffset, -(long long)numberOfNeighbors / 2); + } + else + { + nnIndex = clampNeuronIndex( + neuronIndex + neighborOffset + 1, -(long long)numberOfNeighbors / 2); + } + return nnIndex; + } + + void insertNeuron(unsigned long long synapseIdx) + { + // A synapse have incomingNeighbor and outgoingNeuron, direction incomingNeuron -> + // outgoingNeuron + unsigned long long incomingNeighborSynapseIdx = synapseIdx % numberOfNeighbors; + unsigned long long outgoingNeuron = synapseIdx / numberOfNeighbors; + + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + unsigned long long& population = currentANN.population; + + // Copy original neuron to the inserted one and set it as Neuron::kEvolution type + Neuron insertNeuron; + insertNeuron = neurons[outgoingNeuron]; + insertNeuron.type = Neuron::kEvolution; + unsigned long long insertedNeuronIdx = outgoingNeuron + 1; + + char originalWeight = synapses[synapseIdx].weight; + + // Insert the neuron into array, population increased one, all neurons next to original one + // need to shift right + for (unsigned long long i = population; i > outgoingNeuron; --i) + { + neurons[i] = neurons[i - 1]; + + // Also shift the synapses to the right + memcpy(getSynapses(i), getSynapses(i - 1), numberOfNeighbors * sizeof(Synapse)); + } + neurons[insertedNeuronIdx] = insertNeuron; + population++; + + // Try to update the synapse of inserted neuron. All outgoing synapse is init as zero weight + Synapse* pInsertNeuronSynapse = getSynapses(insertedNeuronIdx); + for (unsigned long long synIdx = 0; synIdx < numberOfNeighbors; ++synIdx) + { + pInsertNeuronSynapse[synIdx].weight = 0; + } + + // Copy the outgoing synapse of original neuron + if (incomingNeighborSynapseIdx < numberOfNeighbors / 2) + { + // The synapse is going to a neuron to the left of the original neuron. + // Check if the incoming neuron is still contained in the neighbors of the inserted + // neuron. This is the case if the original `incomingNeighborSynapseIdx` is > 0, i.e. + // the original synapse if not going to the leftmost neighbor of the original neuron. + if (incomingNeighborSynapseIdx > 0) + { + // Decrease idx by one because the new neuron is inserted directly to the right of + // the original one. + pInsertNeuronSynapse[incomingNeighborSynapseIdx - 1].weight = originalWeight; + } + // If the incoming neuron of the original synapse if not contained in the neighbors of + // the inserted neuron, don't add the synapse. + } + else + { + // The synapse is going to a neuron to the right of the original neuron. + // In this case, the incoming neuron of the synapse is for sure contained in the + // neighbors of the inserted neuron and has the same idx (right side neighbors of + // inserted neuron = right side neighbors of original neuron before insertion). + pInsertNeuronSynapse[incomingNeighborSynapseIdx].weight = originalWeight; + } + + // The change of synapse only impact neuron in [originalNeuronIdx - numberOfNeighbors / 2 + + // 1, originalNeuronIdx + numberOfNeighbors / 2] In the new index, it will be + // [originalNeuronIdx + 1 - numberOfNeighbors / 2, originalNeuronIdx + 1 + numberOfNeighbors + // / 2] [N0 N1 N2 original inserted N4 N5 N6], M = 2. + for (long long delta = -(long long)numberOfNeighbors / 2; + delta <= (long long)numberOfNeighbors / 2; + ++delta) + { + // Only process the neigbors + if (delta == 0) + { + continue; + } + unsigned long long updatedNeuronIdx = clampNeuronIndex(insertedNeuronIdx, delta); + + // Generate a list of neighbor index of current updated neuron NN + // Find the location of the inserted neuron in the list of neighbors + long long insertedNeuronIdxInNeigborList = -1; + for (long long k = 0; k < numberOfNeighbors; k++) + { + unsigned long long nnIndex = getNeighborNeuronIndex(updatedNeuronIdx, k); + if (nnIndex == insertedNeuronIdx) + { + insertedNeuronIdxInNeigborList = k; + } + } + + assert(insertedNeuronIdxInNeigborList >= 0); + + Synapse* pUpdatedSynapses = getSynapses(updatedNeuronIdx); + // [N0 N1 N2 original inserted N4 N5 N6], M = 2. + // Case: neurons in range [N0 N1 N2 original], right synapses will be affected + if (delta < 0) + { + // Left side is kept as it is, only need to shift to the right side + for (long long k = numberOfNeighbors - 1; k >= insertedNeuronIdxInNeigborList; --k) + { + // Updated synapse + pUpdatedSynapses[k] = pUpdatedSynapses[k - 1]; + } + + // Incomming synapse from original neuron -> inserted neuron must be zero + if (delta == -1) + { + pUpdatedSynapses[insertedNeuronIdxInNeigborList].weight = 0; + } + } + else // Case: neurons in range [inserted N4 N5 N6], left synapses will be affected + { + // Right side is kept as it is, only need to shift to the left side + for (long long k = 0; k < insertedNeuronIdxInNeigborList; ++k) + { + // Updated synapse + pUpdatedSynapses[k] = pUpdatedSynapses[k + 1]; + } + } + } + } + + long long getIndexInSynapsesBuffer(unsigned long long neuronIdx, long long neighborOffset) + { + // Skip the case neuron point to itself and too far neighbor + if (neighborOffset == 0 || neighborOffset < -(long long)numberOfNeighbors / 2 || + neighborOffset > (long long)numberOfNeighbors / 2) + { + return -1; + } + + long long synapseIdx = (long long)numberOfNeighbors / 2 + neighborOffset; + if (neighborOffset >= 0) + { + synapseIdx = synapseIdx - 1; + } + + return synapseIdx; + } + + // Check which neurons/synapse need to be removed after mutation + unsigned long long scanRedundantNeurons() + { + unsigned long long population = currentANN.population; + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + + unsigned long long numberOfRedundantNeurons = 0; + // After each mutation, we must verify if there are neurons that do not affect the ANN + // output. These are neurons that either have all incoming synapse weights as 0, or all + // outgoing synapse weights as 0. Such neurons must be removed. + for (unsigned long long i = 0; i < population; i++) + { + neurons[i].markForRemoval = false; + if (neurons[i].type == Neuron::kEvolution) + { + bool allOutGoingZeros = true; + bool allIncommingZeros = true; + + // Loop though its synapses for checkout outgoing synapses + for (unsigned long long n = 0; n < numberOfNeighbors; n++) + { + char synapseW = synapses[i * numberOfNeighbors + n].weight; + if (synapseW != 0) + { + allOutGoingZeros = false; + break; + } + } + + // Loop through the neighbor neurons to check all incoming synapses + for (long long neighborOffset = -(long long)numberOfNeighbors / 2; + neighborOffset <= (long long)numberOfNeighbors / 2; + neighborOffset++) + { + unsigned long long nnIdx = clampNeuronIndex(i, neighborOffset); + Synapse* nnSynapses = getSynapses(nnIdx); + + long long synapseIdx = getIndexInSynapsesBuffer(nnIdx, -neighborOffset); + if (synapseIdx < 0) + { + continue; + } + char synapseW = nnSynapses[synapseIdx].weight; + + if (synapseW != 0) + { + allIncommingZeros = false; + break; + } + } + if (allOutGoingZeros || allIncommingZeros) + { + neurons[i].markForRemoval = true; + numberOfRedundantNeurons++; + } + } + } + return numberOfRedundantNeurons; + } + + // Remove neurons and synapses that do not affect the ANN + void cleanANN() + { + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + unsigned long long& population = currentANN.population; + + // Scan and remove neurons/synapses + unsigned long long neuronIdx = 0; + while (neuronIdx < population) + { + if (neurons[neuronIdx].markForRemoval) + { + // Remove it from the neuron list. Overwrite data + // Remove its synapses in the synapses array + removeNeuron(neuronIdx); + } + else + { + neuronIdx++; + } + } + } + + void processTick() + { + unsigned long long population = currentANN.population; + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + + // Memset value of current one + memset(neuronValueBuffer, 0, sizeof(neuronValueBuffer)); + + // Loop though all neurons + for (unsigned long long n = 0; n < population; ++n) + { + const Synapse* kSynapses = getSynapses(n); + long long neuronValue = neurons[n].value; + // Scan through all neighbor neurons and sum all connected neurons. + // The synapses are arranged as neuronIndex * numberOfNeighbors + for (long long m = 0; m < numberOfNeighbors; m++) + { + char synapseWeight = kSynapses[m].weight; + unsigned long long nnIndex = 0; + if (m < numberOfNeighbors / 2) + { + nnIndex = clampNeuronIndex(static_cast(n + m), -static_cast(numberOfNeighbors / 2)); + } + else + { + nnIndex = clampNeuronIndex(static_cast(n + m + 1), -static_cast(numberOfNeighbors / 2)); + } + + // Weight-sum + neuronValueBuffer[nnIndex] += synapseWeight * neuronValue; + } + } + + // Clamp the neuron value + for (unsigned long long n = 0; n < population; ++n) + { + char neuronValue = score_reference::clampNeuron(neuronValueBuffer[n]); + neurons[n].value = neuronValue; + } + } + + void runTickSimulation() + { + unsigned long long population = currentANN.population; + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + + // Save the neuron value for comparison + for (unsigned long long i = 0; i < population; ++i) + { + // Backup the neuron value + previousNeuronValue[i] = neurons[i].value; + } + + for (unsigned long long tick = 0; tick < numberOfTicks; ++tick) + { + processTick(); + // Check exit conditions: + // - N ticks have passed (already in for loop) + // - All neuron values are unchanged + // - All output neurons have non-zero values + bool shouldExit = true; + bool allNeuronsUnchanged = true; + bool allOutputNeuronsIsNonZeros = true; + for (unsigned long long n = 0; n < population; ++n) + { + // Neuron unchanged check + if (previousNeuronValue[n] != neurons[n].value) + { + allNeuronsUnchanged = false; + } + + // Ouput neuron value check + if (neurons[n].type == Neuron::kOutput && neurons[n].value == 0) + { + allOutputNeuronsIsNonZeros = false; + } + } + + if (allOutputNeuronsIsNonZeros || allNeuronsUnchanged) + { + break; + } + + // Copy the neuron value + for (unsigned long long n = 0; n < population; ++n) + { + previousNeuronValue[n] = neurons[n].value; + } + } + } + + unsigned int computeNonMatchingOutput() + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + + // Compute the non-matching value R between output neuron value and initial value + // Because the output neuron order never changes, the order is preserved + unsigned int R = 0; + unsigned long long outputIdx = 0; + for (unsigned long long i = 0; i < population; i++) + { + if (neurons[i].type == Neuron::kOutput) + { + if (neurons[i].value != outputNeuronExpectedValue[outputIdx]) + { + R++; + } + outputIdx++; + } + } + return R; + } + + void initInputNeuron() + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + unsigned long long inputNeuronInitIndex = 0; + + char neuronArray[64] = {0}; + for (unsigned long long i = 0; i < population; ++i) + { + // Input will use the init value + if (neurons[i].type == Neuron::kInput) + { + // Prepare new pack + if (inputNeuronInitIndex % 64 == 0) + { + score_reference::extract64Bits( + miningData.inputNeuronRandomNumber[inputNeuronInitIndex / 64], neuronArray); + } + char neuronValue = neuronArray[inputNeuronInitIndex % 64]; + + // Convert value of neuron to trits (keeping 1 as 1, and changing 0 to -1.). + neurons[i].value = (neuronValue == 0) ? -1 : neuronValue; + + inputNeuronInitIndex++; + } + } + } + + void initOutputNeuron() + { + unsigned long long population = currentANN.population; + Neuron* neurons = currentANN.neurons; + for (unsigned long long i = 0; i < population; ++i) + { + if (neurons[i].type == Neuron::kOutput) + { + neurons[i].value = 0; + } + } + } + + void initExpectedOutputNeuron() + { + char neuronArray[64] = {0}; + for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) + { + // Prepare new pack + if (i % 64 == 0) + { + score_reference::extract64Bits(miningData.outputNeuronRandomNumber[i / 64], neuronArray); + } + char neuronValue = neuronArray[i % 64]; + // Convert value of neuron (keeping 1 as 1, and changing 0 to -1.). + outputNeuronExpectedValue[i] = (neuronValue == 0) ? -1 : neuronValue; + } + } + + unsigned int initializeANN(const unsigned char* publicKey, const unsigned char* nonce) + { + unsigned char hash[32]; + unsigned char combined[64]; + memcpy(combined, publicKey, 32); + memcpy(combined + 32, nonce, 32); + KangarooTwelve(combined, 64, hash, 32); + + unsigned long long& population = currentANN.population; + Synapse* synapses = currentANN.synapses; + Neuron* neurons = currentANN.neurons; + + // Initialization + population = numberOfNeurons; + + // Initalize with nonce and public key + score_reference::random2(hash, poolVec.data(), (unsigned char*)&initValue, sizeof(InitValue)); + + // Randomly choose the positions of neurons types + for (unsigned long long i = 0; i < population; ++i) + { + neuronIndices[i] = i; + neurons[i].type = Neuron::kInput; + } + unsigned long long neuronCount = population; + for (unsigned long long i = 0; i < numberOfOutputNeurons; ++i) + { + unsigned long long outputNeuronIdx = initValue.outputNeuronPositions[i] % neuronCount; + + // Fill the neuron type + neurons[neuronIndices[outputNeuronIdx]].type = Neuron::kOutput; + outputNeuronIndices[i] = neuronIndices[outputNeuronIdx]; + + // This index is used, copy the end of indices array to current position and decrease + // the number of picking neurons + neuronCount = neuronCount - 1; + neuronIndices[outputNeuronIdx] = neuronIndices[neuronCount]; + } + + // Synapse weight initialization + for (unsigned long long i = 0; i < (initNumberOfSynapses / 32); ++i) + { + const unsigned long long mask = 0b11; + + for (int j = 0; j < 32; ++j) + { + int shiftVal = j * 2; + unsigned char extractValue = + (unsigned char)((initValue.synapseWeight[i] >> shiftVal) & mask); + switch (extractValue) + { + case 2: + synapses[32 * i + j].weight = -1; + break; + case 3: + synapses[32 * i + j].weight = 1; + break; + default: + synapses[32 * i + j].weight = 0; + } + } + } + + // Init the neuron input and expected output value + memcpy((unsigned char*)&miningData, poolVec.data(), sizeof(miningData)); + + // Init input neuron value and output neuron + initInputNeuron(); + initOutputNeuron(); + + // Init expected output neuron + initExpectedOutputNeuron(); + + // Ticks simulation + runTickSimulation(); + + // Copy the state for rollback later + memcpy(&bestANN, ¤tANN, sizeof(ANN)); + + // Compute R and roll back if neccessary + unsigned int R = computeNonMatchingOutput(); + + return R; + } + + unsigned int computeScore(const unsigned char* publicKey, const unsigned char* nonce) + { + // Initialize + unsigned int bestR = initializeANN(publicKey, nonce); + + for (unsigned long long s = 0; s < numberOfMutations; ++s) + { + + // Do the mutation + mutate(nonce, s); + + // Exit if the number of population reaches the maximum allowed + if (currentANN.population >= populationThreshold) + { + break; + } + + // Ticks simulation + runTickSimulation(); + + // Compute R and roll back if neccessary + unsigned int R = computeNonMatchingOutput(); + if (R > bestR) + { + // Roll back + memcpy(¤tANN, &bestANN, sizeof(bestANN)); + } + else + { + bestR = R; + + // Better R. Save the state + memcpy(&bestANN, ¤tANN, sizeof(bestANN)); + } + + assert(bestANN.population <= populationThreshold); + } + + // Compute score + unsigned int score = numberOfOutputNeurons - bestR; + return score; + } + + // Main function for mining + bool findSolution(const unsigned char* publicKey, const unsigned char* nonce) + { + // Check score + unsigned int score = computeScore(publicKey, nonce); + if (score >= solutionThreshold) + { + return true; + } + + return false; + } +}; + +} // namespace score_hyberidentity \ No newline at end of file diff --git a/test/score_params.h b/test/score_params.h new file mode 100644 index 000000000..1b7eb564d --- /dev/null +++ b/test/score_params.h @@ -0,0 +1,65 @@ +#pragma once + +#include "../src/mining/score_common.h" + +namespace score_params +{ + +static constexpr unsigned int MAX_PARAM_TYPE = 7; + +template +struct ConfigPair +{ + using HyperIdentity = HI; + using Addition = ADD; +}; + +// All configurations +using Config0 = ConfigPair< + score_engine::HyperIdentityParams<64, 64, 50, 64, 178, 50, 36>, + score_engine::AdditionParams<2 * 2, 3, 50, 64, 100, 50, 36> +>; + +using Config1 = ConfigPair< + score_engine::HyperIdentityParams<256, 256, 120, 256, 612, 100, 171>, + score_engine::AdditionParams<4 * 2, 5, 120, 256, 100 + 8 + 5, 100, 171> +>; + +using Config2 = ConfigPair< + score_engine::HyperIdentityParams<512, 512, 150, 512, 1174, 150, 300>, + score_engine::AdditionParams<7 * 2, 8, 150, 512, 150 + 14 + 8, 150, 600> +>; + +using Config3 = ConfigPair< + score_engine::HyperIdentityParams<1024, 1024, 200, 1024, 3000, 200, 600>, + score_engine::AdditionParams<9 * 2, 10, 200, 1024, 200 + 18 + 10, 200, 600> +>; + +using ConfigProfile = ConfigPair< + score_engine::HyperIdentityParams< + HYPERIDENTITY_NUMBER_OF_INPUT_NEURONS, + HYPERIDENTITY_NUMBER_OF_OUTPUT_NEURONS, + HYPERIDENTITY_NUMBER_OF_TICKS, + HYPERIDENTITY_NUMBER_OF_NEIGHBORS, + HYPERIDENTITY_POPULATION_THRESHOLD, + HYPERIDENTITY_NUMBER_OF_MUTATIONS, + HYPERIDENTITY_SOLUTION_THRESHOLD_DEFAULT>, + score_engine::AdditionParams< + ADDITION_NUMBER_OF_INPUT_NEURONS, + ADDITION_NUMBER_OF_OUTPUT_NEURONS, + ADDITION_NUMBER_OF_TICKS, + ADDITION_NUMBER_OF_NEIGHBORS, + ADDITION_POPULATION_THRESHOLD, + ADDITION_NUMBER_OF_MUTATIONS, + ADDITION_SOLUTION_THRESHOLD_DEFAULT> +>; + +using ConfigList = std::tuple; + +static constexpr std::size_t CONFIG_COUNT = std::tuple_size_v; + +using ProfileConfigList = std::tuple; +static constexpr std::size_t PROFILE_CONFIG_COUNT = std::tuple_size_v; + + +} diff --git a/test/score_reference.h b/test/score_reference.h new file mode 100644 index 000000000..6d55c3f32 --- /dev/null +++ b/test/score_reference.h @@ -0,0 +1,60 @@ +#pragma once + +#include "../src/platform/memory_util.h" +#include "../src/four_q.h" +#include "score_hyperidentity_reference.h" +#include "score_addition_reference.h" +#include + +////////// Original (reference) scoring algorithm \\\\\\\\\\ + +namespace score_reference +{ + +template +struct ScoreReferenceImplementation +{ + using HyperIdentityScore = ::score_hyberidentity_reference::Miner; + using AdditionScore = ::score_addition_reference::Miner; + + std::unique_ptr _hyperIdentityScore; + std::unique_ptr _additionScore; + + void initMemory() + { + _hyperIdentityScore = std::make_unique(); + _additionScore = std::make_unique(); + } + void initMiningData(const unsigned char* miningSeed) + { + _hyperIdentityScore->initialize(miningSeed); + _additionScore->initialize(miningSeed); + } + + unsigned int computeHyperIdentityScore(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool = nullptr) + { + return _hyperIdentityScore->computeScore(publicKey, nonce); + } + + unsigned int computeAdditionScore(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool = nullptr) + { + return _additionScore->computeScore(publicKey, nonce); + } + + // Return score depend on the nonce + unsigned int computeScore(const unsigned char* publicKey, const unsigned char* nonce, const unsigned char* randomPool = nullptr) + { + if ((nonce[0] & 1) == 0) + { + return computeHyperIdentityScore(publicKey, nonce); + } + else + { + return computeAdditionScore(publicKey, nonce); + } + return 0; + } + +}; + +} diff --git a/test/sorting.cpp b/test/sorting.cpp new file mode 100644 index 000000000..b6baf77b0 --- /dev/null +++ b/test/sorting.cpp @@ -0,0 +1,118 @@ +#define NO_UEFI + +#include +#include +#include + +#include "gtest/gtest.h" +#include "lib/platform_common/sorting.h" + +constexpr unsigned int MAX_NUM_ELEMENTS = 10000U; +constexpr unsigned int MAX_NUM_TESTS_PER_TYPE = 100U; + +TEST(FixedTypeSortingTest, SortAscendingSimple) +{ + int arr[5] = { 3, 6, 1, 9, 2 }; + + quickSort(arr, 0, 4, SortingOrder::SortAscending); + + EXPECT_EQ(arr[0], 1); + EXPECT_EQ(arr[1], 2); + EXPECT_EQ(arr[2], 3); + EXPECT_EQ(arr[3], 6); + EXPECT_EQ(arr[4], 9); +} + +TEST(FixedTypeSortingTest, SortDescendingSimple) +{ + int arr[5] = { 3, 6, 1, 9, 2 }; + + quickSort(arr, 0, 4, SortingOrder::SortDescending); + + EXPECT_EQ(arr[0], 9); + EXPECT_EQ(arr[1], 6); + EXPECT_EQ(arr[2], 3); + EXPECT_EQ(arr[3], 2); + EXPECT_EQ(arr[4], 1); +} + +template +std::vector prepareData(unsigned int seed) +{ + std::mt19937 gen32(seed); + + unsigned int numberOfBlocks = (sizeof(T) + 3) / 4; + + unsigned int numElements = (gen32() % MAX_NUM_ELEMENTS) + 1; + std::vector vec(numElements, 0); + for (unsigned int i = 0; i < numElements; ++i) + { + // generate 4 bytes at a time until the whole number is generated + for (unsigned int b = 0; b < numberOfBlocks; ++b) + { + vec[i] |= (static_cast(gen32()) << (b * 32)); + } + } + + return vec; +} + +template +void testSortAscending(unsigned int seed) +{ + std::vector vec = prepareData(seed); + + std::vector referenceVec = vec; + std::sort(referenceVec.begin(), referenceVec.end(), std::less<>()); + + quickSort(vec.data(), 0, static_cast(vec.size() - 1), SortingOrder::SortAscending); + + EXPECT_EQ(vec, referenceVec); +} + +template +void testSortDescending(unsigned int seed) +{ + std::vector vec = prepareData(seed); + + std::vector referenceVec = vec; + std::sort(referenceVec.begin(), referenceVec.end(), std::greater<>()); + + quickSort(vec.data(), 0, static_cast(vec.size() - 1), SortingOrder::SortDescending); + + EXPECT_EQ(vec, referenceVec); +} + +template +class SortingTest : public testing::Test {}; + +using testing::Types; + +TYPED_TEST_CASE_P(SortingTest); + +TYPED_TEST_P(SortingTest, SortAscending) +{ + unsigned int metaSeed = 32467; + std::mt19937 gen32(metaSeed); + + for (unsigned int t = 0; t < MAX_NUM_TESTS_PER_TYPE; ++t) + testSortAscending(/*seed=*/gen32()); +} + +TYPED_TEST_P(SortingTest, SortDescending) +{ + unsigned int metaSeed = 45787; + std::mt19937 gen32(metaSeed); + + for (unsigned int t = 0; t < MAX_NUM_TESTS_PER_TYPE; ++t) + testSortDescending(/*seed=*/gen32()); +} + +REGISTER_TYPED_TEST_CASE_P(SortingTest, + SortAscending, + SortDescending +); + +// GTest produces a linker error when using `unsigned short` as test type due to unresolved print function - skip for now. +typedef Types TestTypes; +INSTANTIATE_TYPED_TEST_CASE_P(TypeParamSortingTests, SortingTest, TestTypes); \ No newline at end of file diff --git a/test/spectrum.cpp b/test/spectrum.cpp new file mode 100644 index 000000000..dca62d896 --- /dev/null +++ b/test/spectrum.cpp @@ -0,0 +1,409 @@ +#define NO_UEFI + +#define PRINT_TEST_INFO 0 + +#include "gtest/gtest.h" + +#include +#include + +#include "logging_test.h" +#include "spectrum/spectrum.h" + +static bool transfer(const m256i& src, const m256i& dst, long long amount) +{ + if (isZero(src) || isZero(dst)) + return false; + + if (amount < 0 || amount > MAX_AMOUNT) + return false; + + const int index = spectrumIndex(src); + if (index < 0) + return false; + + if (!decreaseEnergy(index, amount)) + return false; + + increaseEnergy(dst, amount); + return true; +} + +static m256i getRichestEntity() +{ + m256i pubKey(0, 0, 0, 0); + long long maxBalance = 0; + for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) + { + long long balance = spectrum[i].incomingAmount - spectrum[i].outgoingAmount; + if (balance > maxBalance) + { + maxBalance = balance; + pubKey = spectrum[i].publicKey; + } + } + return pubKey; +} + +static m256i getAnyEntity() +{ + m256i pubKey(0, 0, 0, 0); + for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) + { + long long balance = spectrum[i].incomingAmount - spectrum[i].outgoingAmount; + if (balance > 0) + { + pubKey = spectrum[i].publicKey; + break; + } + } + return pubKey; +} + +static SpectrumInfo checkAndGetInfo() +{ + // Total amount <= total supply + SpectrumInfo si{ 0, 0 }; + for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) + { + long long balance = spectrum[i].incomingAmount - spectrum[i].outgoingAmount; + if (!balance && isZero(spectrum[i].publicKey)) + continue; + EXPECT_GE(balance, 0); + EXPECT_LE(spectrum[i].latestIncomingTransferTick, system.tick); + EXPECT_LE(spectrum[i].latestOutgoingTransferTick, system.tick); + si.totalAmount += balance; + si.numberOfEntities++; + } + EXPECT_LE((unsigned long long)si.totalAmount, MAX_SUPPLY); + EXPECT_EQ(si.totalAmount, spectrumInfo.totalAmount); + EXPECT_EQ(si.numberOfEntities, spectrumInfo.numberOfEntities); + return si; +} + +static void updateAndPrintEntityCategoryPopulations() +{ + updateAndAnalzeEntityCategoryPopulations(); + + // Compute number of entities with 0 balance + unsigned int sumEntityCategoryPopulations = 0; + for (int i = 0; i < entityCategoryCount; ++i) + sumEntityCategoryPopulations += entityCategoryPopulations[i]; + EXPECT_GE(spectrumInfo.numberOfEntities, sumEntityCategoryPopulations); + +#if PRINT_TEST_INFO + unsigned int zeroBalanceEntities = spectrumInfo.numberOfEntities - sumEntityCategoryPopulations; + if (zeroBalanceEntities > 0) + std::cout << " - bin -1: " << zeroBalanceEntities << " entities with zero balance\n"; + + static constexpr int entityCategoryCount = sizeof(entityCategoryPopulations) / sizeof(entityCategoryPopulations[0]); + for (int i = 0; i < entityCategoryCount; ++i) + { + if (entityCategoryPopulations[i]) + { + unsigned long long lowerBound = (1llu << i), upperBound = (1llu << (i + 1)) - 1; + const char* burnIndicator = " + bin "; + if (lowerBound <= dustThresholdBurnAll) + burnIndicator = " - bin "; + else if (lowerBound <= dustThresholdBurnHalf) + burnIndicator = " * bin "; + std::cout << burnIndicator << i << ": " << entityCategoryPopulations[i] << " entities with amount "; + if (i == 0) + std::cout << lowerBound; + else + std::cout << "between " << lowerBound << " and " << upperBound; + std::cout << std::endl; + } + } +#endif +} + +// Spectrum test class for proper init, cleanup, and other repeated tasks +struct SpectrumTest : public LoggingTest +{ + SpectrumInfo beforeAntiDustSpectrumInfo; + std::chrono::steady_clock::time_point beforeAntiDustTimestamp; + bool antiDustCornerCase; + std::mt19937_64 rnd64; + + SpectrumTest(unsigned long long seed = 0) + { + if (!seed) + _rdrand64_step(&seed); + rnd64.seed(seed); + EXPECT_TRUE(initSpectrum()); + EXPECT_TRUE(commonBuffers.init(1)); + system.tick = 15700000; + clearSpectrum(); + antiDustCornerCase = false; + } + + ~SpectrumTest() + { + deinitSpectrum(); + commonBuffers.deinit(); + } + + void clearSpectrum() + { + memset(spectrum, 0, spectrumSizeInBytes); + updateSpectrumInfo(); + } + + void beforeAntiDust() + { + // Check and get current spectrum state + beforeAntiDustSpectrumInfo = checkAndGetInfo(); + + // Print distribution of entity balances +#if PRINT_TEST_INFO + std::cout << "Entity balance distribution before anti-dust:" << std::endl; +#endif + updateAndPrintEntityCategoryPopulations(); + + // Start measuring run-time + beforeAntiDustTimestamp = std::chrono::high_resolution_clock::now(); + } + + void afterAntiDust() + { + checkAndGetInfo(); + + // Print anti-dust info + auto duration_ms = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - beforeAntiDustTimestamp); + std::cout << "Transfer with anti-dust took " << duration_ms << " ms: entities " + << beforeAntiDustSpectrumInfo.numberOfEntities << " -> " << spectrumInfo.numberOfEntities + << " (to " << spectrumInfo.numberOfEntities * 100llu / SPECTRUM_CAPACITY + << "% of capacity); total amount " << beforeAntiDustSpectrumInfo.totalAmount << " -> " << spectrumInfo.totalAmount + << " (" << float(((long long)spectrumInfo.totalAmount - (long long)beforeAntiDustSpectrumInfo.totalAmount) * 10000ll / (long long)beforeAntiDustSpectrumInfo.totalAmount) / 100.0f << "% reduction)" << std::endl; + + // Print distribution of entity balances +#if PRINT_TEST_INFO + std::cout << "Entity balance distribution after anti-dust:" << std::endl; +#endif + updateAndPrintEntityCategoryPopulations(); + + // Anti-dust always cleans up to at least half of the spectrum + EXPECT_LE(spectrumInfo.numberOfEntities, (SPECTRUM_CAPACITY / 2)); + + // Except for improbably corner cases, never burn more than 10% of the spectrum (quite arbitrary factor, just meaning no huge amount) + if (!antiDustCornerCase) + EXPECT_GT(spectrumInfo.totalAmount, beforeAntiDustSpectrumInfo.totalAmount * 9 / 10); + } + + void dust_attack(unsigned int transferMinAmount, unsigned int transferMaxAmount, unsigned int repetitions); +}; + +void SpectrumTest::dust_attack(unsigned int transferMinAmount, unsigned int transferMaxAmount, unsigned int repetitions) +{ + std::cout << "------------------ Dust attack with transfers between " << transferMinAmount << " and " << transferMaxAmount << " qu ------------------\n"; + for (unsigned int rep = 0; rep < repetitions; ++rep) + { + m256i richId = getRichestEntity(); + m256i randomId(rnd64(), rnd64(), rnd64(), rnd64()); + + // Check current spectrum state + checkAndGetInfo(); + + // Fill spectrum with dust attack (until next transfer should trigger anti-dust) + while (spectrumInfo.numberOfEntities < (SPECTRUM_CAPACITY / 2) + (SPECTRUM_CAPACITY / 4)) + { + unsigned int transferAmount = transferMinAmount; + if (transferMinAmount < transferMaxAmount) + transferAmount = (spectrumInfo.numberOfEntities % (transferMaxAmount - transferMinAmount)) + transferMinAmount; + + if (!transfer(richId, randomId, transferAmount)) + richId = getRichestEntity(); + randomId = m256i(rnd64(), rnd64(), rnd64(), rnd64()); + } + + // Should trigger anti-dust + beforeAntiDust(); + ASSERT_TRUE(transfer(richId, randomId, transferMinAmount)); + afterAntiDust(); + } +} + +TEST(TestCoreSpectrum, AntiDustFile) +{ + SpectrumTest test; + if (loadSpectrum(L"spectrum.000")) + { + std::cout << "Spectrum file state before dust attack:" << std::endl; + updateAndPrintEntityCategoryPopulations(); + + SpectrumInfo si1 = checkAndGetInfo(); + test.dust_attack(1, 10, 3); + } + else + { + std::cout << "Spectrum file not found. Skipping file test..." << std::endl; + } +} + +TEST(TestCoreSpectrum, AntiDustOneRichRandomDust) +{ + // Create spectrum with one rich ID + SpectrumTest test; + increaseEnergy(m256i::randomValue(), 1000000000000llu); + + test.dust_attack(1, 1, 1); + test.dust_attack(100, 100, 1); + test.dust_attack(1, 10000, 1); +} + +TEST(TestCoreSpectrum, AntiDustManyRichRandomDust) +{ + // Create spectrum with many rich IDs + SpectrumTest test; + for (int i = 0; i < 10000; i++) + { + increaseEnergy(m256i::randomValue(), i * 100000llu); + } + + test.dust_attack(1, 1000, 1); + test.dust_attack(1, 50, 1); + test.dust_attack(1, 1, 1); +} + +TEST(TestCoreSpectrum, AntiDustEdgeCaseAllInSameBin) +{ + SpectrumTest test; + test.antiDustCornerCase = true; + for (unsigned long long i = 0; i < (SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4); ++i) + { + increaseEnergy(m256i(i, 1, 2, 3), 100llu); + } + + test.beforeAntiDust(); + increaseEnergy(m256i::randomValue(), 100llu); + test.afterAntiDust(); +} + +SpectrumStats getSpectrumStatsLog(long long id) +{ + SpectrumStats res; + qLogger::BlobInfo bi = logger.logBuf.getBlobInfo(id); + EXPECT_EQ(bi.length, LOG_HEADER_SIZE + sizeof(SpectrumStats)); + logger.logBuf.getMany((char*)&res, bi.startIndex + LOG_HEADER_SIZE, sizeof(SpectrumStats)); + return res; +} + +void getDustBurningLog(long long id, char* ptr) +{ + DustBurning res; + qLogger::BlobInfo bi = logger.logBuf.getBlobInfo(id); + logger.logBuf.getMany((char*)&res, bi.startIndex + LOG_HEADER_SIZE, sizeof(DustBurning)); + EXPECT_EQ(bi.length, LOG_HEADER_SIZE + res.messageSize()); + copyMem(ptr, &res, sizeof(DustBurning)); + logger.logBuf.getMany(ptr + sizeof(DustBurning), bi.startIndex + LOG_HEADER_SIZE + sizeof(DustBurning), res.messageSize()); +} + +TEST(TestCoreSpectrum, AntiDustEdgeCaseHugeBinsAndLogging) +{ + SpectrumTest test; + test.antiDustCornerCase = true; + + // build-up spectrum + for (unsigned long long i = 0; i < (SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4); ++i) + { + unsigned long long amount; + if (i < SPECTRUM_CAPACITY / 4) + amount = 100; + else if (i < SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4) + amount = 10000; + increaseEnergy(m256i(i, 1, 2, 3), amount); + } + + // test anti-dust + test.beforeAntiDust(); + increaseEnergy(m256i(SPECTRUM_CAPACITY - 1, 1, 2, 3), 1000llu); + test.afterAntiDust(); + + // check logs: + // first 24 are from building up spectrum + SpectrumStats statData; + SpectrumStats* stats = &statData; + for (int i = 0; i < 24; ++i) + { + statData = getSpectrumStatsLog(i); + EXPECT_EQ(stats->numberOfEntities, i * 524288 + 1); + EXPECT_EQ(stats->entityCategoryPopulations[6], std::min(i * 524288 + 1, int(SPECTRUM_CAPACITY / 4))); + EXPECT_EQ(stats->entityCategoryPopulations[13], (i < 8) ? 0 : (i - 8) * 524288 + 1); + EXPECT_EQ(stats->totalAmount, stats->entityCategoryPopulations[6] * 100llu + stats->entityCategoryPopulations[13] * 10000llu); + + if (i < 16) + { + EXPECT_EQ(stats->dustThresholdBurnAll, 0); + EXPECT_EQ(stats->dustThresholdBurnHalf, 0); + } + else + { + EXPECT_EQ(stats->dustThresholdBurnAll, (2 << 6) - 1); + EXPECT_EQ(stats->dustThresholdBurnHalf, 0); + } + } + + // Check state before anti-dust + statData = getSpectrumStatsLog(24); + SpectrumStats* beforeAntidustStats = &statData; + EXPECT_EQ(beforeAntidustStats->numberOfEntities, 24 * 524288); + EXPECT_EQ(beforeAntidustStats->entityCategoryPopulations[6], SPECTRUM_CAPACITY / 4); + EXPECT_EQ(beforeAntidustStats->entityCategoryPopulations[13], SPECTRUM_CAPACITY / 2); + EXPECT_EQ(beforeAntidustStats->totalAmount, beforeAntidustStats->entityCategoryPopulations[6] * 100llu + beforeAntidustStats->entityCategoryPopulations[13] * 10000llu); + EXPECT_EQ(beforeAntidustStats->dustThresholdBurnAll, (2 << 12) - 1); + EXPECT_EQ(beforeAntidustStats->dustThresholdBurnHalf, (2 << 13) - 1); + + // Check dust burning log messages + int balancesBurned = 0; + int logId = 25; + std::vector buffer; + buffer.resize(1024 * 1024 * 1024); // mimic the scratchpad, allocated 1GiB here + while (balancesBurned < 8 * 1048576) + { + DustBurning* db = (DustBurning*) (buffer.data()); + getDustBurningLog(logId, buffer.data()); + for (int i = 0; i < db->numberOfBurns; ++i) + { + // Of the first 4M entities, all are burned (amount 100), of the following every second is burned. + unsigned long long expectedSpectrumIndex = balancesBurned; + if (balancesBurned >= 4194304) + expectedSpectrumIndex = (balancesBurned - 4194304) * 2 + 4194304; + + DustBurning::Entity& e = db->entity(i); + EXPECT_EQ(e.publicKey, m256i(expectedSpectrumIndex, 1, 2, 3)); + EXPECT_EQ(e.amount, (balancesBurned < 4194304) ? 100 : 10000); + ++balancesBurned; + } + ++logId; + } + + // Finally, check state logged after dust burning (logged before increaing energy / adding new entity) + statData = getSpectrumStatsLog(logId);; + SpectrumStats* afterAntidustStats = &statData; + EXPECT_EQ(afterAntidustStats->numberOfEntities, 4194304); + EXPECT_EQ(afterAntidustStats->entityCategoryPopulations[9], 0); + EXPECT_EQ(afterAntidustStats->entityCategoryPopulations[13], 4 * 1048576); + EXPECT_EQ(afterAntidustStats->totalAmount, afterAntidustStats->entityCategoryPopulations[13] * 10000llu); + EXPECT_EQ(afterAntidustStats->dustThresholdBurnAll, 0); + EXPECT_EQ(afterAntidustStats->dustThresholdBurnHalf, 0); +} + +TEST(TestCoreSpectrum, AntiDustEdgeCaseHugeBinZeroBalance) +{ + SpectrumTest test; + m256i richId(123, 4, 5, 6); + unsigned long long amount = 1000; + increaseEnergy(richId, 100 * amount); + unsigned int spectrum75pct = (SPECTRUM_CAPACITY / 2 + SPECTRUM_CAPACITY / 4); + for (unsigned long long i = 0; i < spectrum75pct - 1; ++i) + { + m256i id(i, 1, 2, 3); + increaseEnergy(id, amount); + decreaseEnergy(spectrumIndex(id), amount); + } + test.beforeAntiDust(); + transfer(richId, m256i(1234, 4, 5, 6), 100 * amount); + test.afterAntiDust(); +} + diff --git a/test/stable_computor_index.cpp b/test/stable_computor_index.cpp new file mode 100644 index 000000000..3eaab702c --- /dev/null +++ b/test/stable_computor_index.cpp @@ -0,0 +1,214 @@ +#define NO_UEFI + +#include "gtest/gtest.h" +#include "../src/ticking/stable_computor_index.h" + +class StableComputorIndexTest : public ::testing::Test +{ +protected: + m256i futureComputors[NUMBER_OF_COMPUTORS]; + m256i currentComputors[NUMBER_OF_COMPUTORS]; + unsigned char tempBuffer[stableComputorIndexBufferSize()]; + + void SetUp() override + { + memset(futureComputors, 0, sizeof(futureComputors)); + memset(currentComputors, 0, sizeof(currentComputors)); + memset(tempBuffer, 0, sizeof(tempBuffer)); + } + + m256i makeId(int n) + { + m256i id = m256i::zero(); + id.m256i_u64[1] = n; + return id; + } +}; + +// Test: All computors requalify - all should keep their indices +TEST_F(StableComputorIndexTest, AllRequalify) +{ + // Set up current computors with IDs 1 to NUMBER_OF_COMPUTORS + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + currentComputors[i] = makeId(i + 1); + } + + // Future: Same IDs but reversed order (simulating score reordering) + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + futureComputors[i] = makeId(NUMBER_OF_COMPUTORS - i); + } + + bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); + ASSERT_TRUE(result); + + // All should be back to their original indices + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_EQ(futureComputors[i], makeId(i + 1)) << "Index " << i << " mismatch"; + } +} + +// Test: Half computors replaced - requalifying keep index, new fill gaps +TEST_F(StableComputorIndexTest, PartialRequalify) +{ + // Current: ID 1 at idx 0, ID 2 at idx 1, ..., ID 676 at idx 675 + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + currentComputors[i] = makeId(i + 1); + } + + // Future input (scrambled): odd IDs requalify, even IDs replaced by new (1001+) + unsigned int requalifyingId = 1; + unsigned int newId = 1001; + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + if (i % 2 == 0 && requalifyingId <= NUMBER_OF_COMPUTORS) + { + futureComputors[i] = makeId(requalifyingId); + requalifyingId += 2; + } + else + { + futureComputors[i] = makeId(newId++); + } + } + + bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); + ASSERT_TRUE(result); + + // Odd IDs (1,3,5,...) should be at original indices (0,2,4,...) + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i += 2) + { + EXPECT_EQ(futureComputors[i], makeId(i + 1)); + } + + // New IDs (1001,1002,...) should fill gaps at indices (1,3,5,...) + unsigned int expectedNewId = 1001; + for (unsigned int i = 1; i < NUMBER_OF_COMPUTORS; i += 2) + { + EXPECT_EQ(futureComputors[i], makeId(expectedNewId++)); + } +} + +// Test: All computors are new - order preserved +TEST_F(StableComputorIndexTest, AllNew) +{ + // Current: IDs 1 to 676 + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + currentComputors[i] = makeId(i + 1); + } + + // Future: Completely new set (IDs 1000 to 1675) + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + futureComputors[i] = makeId(i + 1000); + } + + bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); + ASSERT_TRUE(result); + + // New computors should fill slots in order + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_EQ(futureComputors[i], makeId(i + 1000)); + } +} + +// Test: Single computor requalifies +TEST_F(StableComputorIndexTest, SingleRequalify) +{ + // Current: IDs 1 to 676 + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + currentComputors[i] = makeId(i + 1); + } + + // Future: Only ID 100 requalifies (at position 0), rest are new + futureComputors[0] = makeId(100); // Requalifying, was at idx 99 + for (unsigned int i = 1; i < NUMBER_OF_COMPUTORS; i++) + { + futureComputors[i] = makeId(i + 1000); // New IDs + } + + bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); + ASSERT_TRUE(result); + + // ID 100 should be at its original index 99 + EXPECT_EQ(futureComputors[99], makeId(100)); + + // New computors fill remaining slots (0-98, 100-675) + unsigned int newIdx = 0; + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + if (i == 99) continue; // Skip the requalifying slot + EXPECT_EQ(futureComputors[i], makeId(newIdx + 1001)) << "New computor at index " << i; + newIdx++; + } +} + + +// Test: First and last computor swap positions in input +TEST_F(StableComputorIndexTest, FirstLastSwap) +{ + // Current: IDs 1 to 676 + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + currentComputors[i] = makeId(i + 1); + } + + // Future: All same IDs, but first and last swapped in input order + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + futureComputors[i] = makeId(i + 1); + } + futureComputors[0] = makeId(NUMBER_OF_COMPUTORS); // Last ID at first position + futureComputors[NUMBER_OF_COMPUTORS - 1] = makeId(1); // First ID at last position + + bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); + ASSERT_TRUE(result); + + // All should be at their original indices regardless of input order + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_EQ(futureComputors[i], makeId(i + 1)) << "Index " << i << " mismatch"; + } +} + +// Test: Realistic scenario - 225 computors change (max allowed) +TEST_F(StableComputorIndexTest, MaxChange225) +{ + // Current: IDs 1 to 676 + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + currentComputors[i] = makeId(i + 1); + } + + // Future: First 451 (QUORUM) stay, last 225 are replaced with new IDs + for (unsigned int i = 0; i < 451; i++) + { + futureComputors[i] = makeId(i + 1); // Same IDs, possibly different order + } + for (unsigned int i = 451; i < NUMBER_OF_COMPUTORS; i++) + { + futureComputors[i] = makeId(i + 1000); // New IDs + } + + bool result = calculateStableComputorIndex(futureComputors, currentComputors, tempBuffer); + ASSERT_TRUE(result); + + // First 451 should keep their indices + for (unsigned int i = 0; i < 451; i++) + { + EXPECT_EQ(futureComputors[i], makeId(i + 1)); + } + + // Last 225 slots should have the new IDs + for (unsigned int i = 451; i < NUMBER_OF_COMPUTORS; i++) + { + EXPECT_EQ(futureComputors[i], makeId(i + 1000)); + } +} + diff --git a/test/stdlib_impl.cpp b/test/stdlib_impl.cpp new file mode 100644 index 000000000..47c64bd20 --- /dev/null +++ b/test/stdlib_impl.cpp @@ -0,0 +1,59 @@ +// Implements NO_UEFI versions of several functions that require cstdlib functions. +// Having them in a separate cpp file avoids the name clash between cstdlib's system and Qubic's system. + +#include +#include +#include + +#define NO_UEFI + +#include "platform/time.h" + + +void setMem(void* buffer, unsigned long long size, unsigned char value) +{ + memset(buffer, value, size); +} + +void copyMem(void* destination, const void* source, unsigned long long length) +{ + memcpy(destination, source, length); +} + +bool allocatePool(unsigned long long size, void** buffer) +{ + void* ptr = malloc(size); + if (ptr) + { + *buffer = ptr; + return true; + } + return false; +} + +void freePool(void* buffer) +{ + free(buffer); +} + +void updateTime() +{ + std::time_t t = std::time(nullptr); + std::tm* tm = std::gmtime(&t); + utcTime.Year = tm->tm_year + 1900; + utcTime.Month = tm->tm_mon + 1; + utcTime.Day = tm->tm_mday; + utcTime.Hour = tm->tm_hour; + utcTime.Minute = tm->tm_min; + utcTime.Second = tm->tm_sec; + utcTime.Nanosecond = 0; + utcTime.TimeZone = 0; + utcTime.Daylight = 0; +} + +unsigned long long now_ms() +{ + std::time_t t = std::time(nullptr); + std::tm* tm = std::gmtime(&t); + return ms(unsigned char(tm->tm_year % 100), tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, 0); +} \ No newline at end of file diff --git a/test/test.vcxproj b/test/test.vcxproj new file mode 100644 index 000000000..4b74f2e39 --- /dev/null +++ b/test/test.vcxproj @@ -0,0 +1,203 @@ + + + + + Debug + x64 + + + ReleaseAVX512 + x64 + + + Release + x64 + + + + {30e8e249-6b00-4575-bcdf-be2445d5e099} + Win32Proj + 10.0 + Application + v143 + Unicode + + + + + + + + + + + + + Disabled + X64;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + Level3 + false + stdcpp20 + ../src;$(MSBuildProjectDirectory);$(MSBuildProjectDirectory)\..\;%(AdditionalIncludeDirectories) + AdvancedVectorExtensions512 + true + true + + + true + Console + + + xcopy /E /I /Y "$(ProjectDir)data" "$(TargetDir)data" + + + + + X64;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + false + stdcpp20 + AdvancedVectorExtensions2 + AnySuitable + true + Speed + true + ../src;$(MSBuildProjectDirectory);$(MSBuildProjectDirectory)\..\;%(AdditionalIncludeDirectories) + false + true + true + + + true + Console + true + true + Default + + + xcopy /E /I /Y "$(ProjectDir)data" "$(TargetDir)data" + + + + + X64;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + false + stdcpp20 + AdvancedVectorExtensions512 + AnySuitable + true + Speed + true + ../src;$(MSBuildProjectDirectory);$(MSBuildProjectDirectory)\..\;%(AdditionalIncludeDirectories) + false + true + true + + + true + Console + true + true + Default + + + xcopy /E /I /Y "$(ProjectDir)data" "$(TargetDir)data" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {61270221-bd41-438e-8f74-48aec8c3f9a5} + + + {88b4cda8-8248-44d0-848e-0e938a2aad6d} + + + + + + + + + + Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}". + + + + \ No newline at end of file diff --git a/test/test.vcxproj.filters b/test/test.vcxproj.filters new file mode 100644 index 000000000..6e49bac25 --- /dev/null +++ b/test/test.vcxproj.filters @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {b544a744-99a4-4a9d-b445-211aabef63f2} + + + + + core + + + diff --git a/test/test.vcxproj.user b/test/test.vcxproj.user new file mode 100644 index 000000000..d3c65a501 --- /dev/null +++ b/test/test.vcxproj.user @@ -0,0 +1,12 @@ + + + + --gtest_break_on_failure + WindowsLocalDebugger + + + + + WindowsLocalDebugger + + \ No newline at end of file diff --git a/test/test_util.h b/test/test_util.h new file mode 100644 index 000000000..c55e9d118 --- /dev/null +++ b/test/test_util.h @@ -0,0 +1,34 @@ +#pragma once + +#include "gtest/gtest.h" + +#include "four_q.h" + + +static std::ostream& operator<<(std::ostream& s, const m256i& v) +{ + CHAR16 identityWchar[61]; + char identityChar[61]; + getIdentity(v.m256i_u8, identityWchar, false); + size_t size; + wcstombs_s(&size, identityChar, identityWchar, 61); + s << identityChar; + return s; +} + +static unsigned long long assetNameFromString(const char* assetName) +{ + size_t n = strlen(assetName); + EXPECT_LE(n, 7); + unsigned long long integer = 0; + copyMem(&integer, assetName, n); + return integer; +} + +static std::string assetNameFromInt64(unsigned long long assetName) +{ + char buffer[8]; + copyMem(&buffer, &assetName, sizeof(assetName)); + buffer[7] = 0; + return buffer; +} \ No newline at end of file diff --git a/test/tick_storage.cpp b/test/tick_storage.cpp new file mode 100644 index 000000000..103390c99 --- /dev/null +++ b/test/tick_storage.cpp @@ -0,0 +1,209 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#include "../src/public_settings.h" +#undef MAX_NUMBER_OF_TICKS_PER_EPOCH +#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 +#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH +#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 5 +#include "../src/ticking/tick_storage.h" + +#include + + +class TestTickStorage : public TickStorage +{ + unsigned char transactionBuffer[MAX_TRANSACTION_SIZE]; +public: + + void addTransaction(unsigned int tick, unsigned int transactionIdx, unsigned int inputSize) + { + ASSERT_TRUE(inputSize <= MAX_INPUT_SIZE); + Transaction* transaction = (Transaction*)transactionBuffer; + transaction->amount = 10; + transaction->destinationPublicKey.setRandomValue(); + transaction->sourcePublicKey.setRandomValue(); + transaction->inputSize = inputSize; + transaction->inputType = 0; + transaction->tick = tick; + + unsigned int transactionSize = transaction->totalSize(); + + auto* offsets = tickTransactionOffsets.getByTickInCurrentEpoch(tick); + if (nextTickTransactionOffset + transactionSize <= tickTransactions.storageSpaceCurrentEpoch) + { + EXPECT_EQ(offsets[transactionIdx], 0); + offsets[transactionIdx] = nextTickTransactionOffset; + copyMem(tickTransactions(nextTickTransactionOffset), transaction, transactionSize); + nextTickTransactionOffset += transactionSize; + } + } + + void addTick(unsigned int tick, unsigned int seed, unsigned short maxTransactions) + { + // use pseudo-random sequence + std::mt19937 gen32(seed); + + // add tick data + TickData& td = tickData.getByTickInCurrentEpoch(tick); + td.epoch = 1234; + td.tick = tick; + + // add computor ticks + Tick* computorTicks = ticks.getByTickInCurrentEpoch(tick); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + computorTicks[i].epoch = 1234; + computorTicks[i].computorIndex = i; + computorTicks[i].tick = tick; + computorTicks[i].prevResourceTestingDigest = gen32(); + } + + // add transactions of tick + unsigned int transactionNum = gen32() % (maxTransactions + 1); + unsigned int orderMode = gen32() % 2; + unsigned int transactionSlot; + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + if (orderMode == 0) + transactionSlot = transaction; // standard order + else if (orderMode == 1) + transactionSlot = transactionNum - 1 - transaction; // backward order + addTransaction(tick, transactionSlot, gen32() % MAX_INPUT_SIZE); + } + checkStateConsistencyWithAssert(); + } + + void checkTick(unsigned int tick, unsigned int seed, unsigned short maxTransactions, bool previousEpoch = false) + { + // only last ticks of previous epoch are kept in storage -> check okay + if (previousEpoch && !tickInPreviousEpochStorage(tick)) + return; + + // use pseudo-random sequence + std::mt19937 gen32(seed); + + // check tick data + TickData& td = previousEpoch ? tickData.getByTickInPreviousEpoch(tick) : tickData.getByTickInCurrentEpoch(tick); + EXPECT_EQ((int)td.epoch, (int)1234); + EXPECT_EQ(td.tick, tick); + + // check computor ticks + Tick* computorTicks = previousEpoch ? ticks.getByTickInPreviousEpoch(tick) : ticks.getByTickInCurrentEpoch(tick); + for (int i = 0; i < NUMBER_OF_COMPUTORS; ++i) + { + EXPECT_EQ((int)computorTicks[i].epoch, (int)1234); + EXPECT_EQ((int)computorTicks[i].computorIndex, (int)i); + EXPECT_EQ(computorTicks[i].tick, tick); + EXPECT_EQ(computorTicks[i].prevResourceTestingDigest, gen32()); + } + + // check transactions of tick + { + const auto* offsets = previousEpoch ? tickTransactionOffsets.getByTickInPreviousEpoch(tick) : tickTransactionOffsets.getByTickInCurrentEpoch(tick); + unsigned int transactionNum = gen32() % (maxTransactions + 1); + unsigned int orderMode = gen32() % 2; + unsigned int transactionSlot; + + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + int expectedInputSize = (int)(gen32() % MAX_INPUT_SIZE); + + if (orderMode == 0) + transactionSlot = transaction; // standard order + else if (orderMode == 1) + transactionSlot = transactionNum - 1 - transaction; // backward order + + // If previousEpoch, some transactions at the beginning may not have fit into the storage and are missing -> check okay + // If current epoch, some may be missing at he end due to limited storage -> check okay + if (!offsets[transactionSlot]) + continue; + + Transaction* tp = tickTransactions(offsets[transactionSlot]); + EXPECT_TRUE(tp->checkValidity()); + EXPECT_EQ(tp->tick, tick); + EXPECT_EQ((int)tp->inputSize, expectedInputSize); + } + } + } +}; + + +TEST(TestCoreTickStorage, EpochTransition) +{ + TestTickStorage ts; + unsigned int seed = 42; + + // use pseudo-random sequence + std::mt19937 gen32(seed); + + // 5x test with running 2 epoch transitions + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + // first, test case of having no transactions + unsigned short maxTransactions = (testIdx == 0) ? 0 : NUMBER_OF_TRANSACTIONS_PER_TICK; + + ts.init(); + ts.checkStateConsistencyWithAssert(); + + const int firstEpochTicks = gen32() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const int secondEpochTicks = gen32() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const int thirdEpochTicks = gen32() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const unsigned int firstEpochTick0 = gen32() % 10000000; + const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; + const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; + unsigned int firstEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + unsigned int secondEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + unsigned int thirdEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen32(); + for (int i = 0; i < secondEpochTicks; ++i) + secondEpochSeeds[i] = gen32(); + for (int i = 0; i < thirdEpochTicks; ++i) + thirdEpochSeeds[i] = gen32(); + + // first epoch + ts.beginEpoch(firstEpochTick0); + ts.checkStateConsistencyWithAssert(); + + // add ticks + for (int i = 0; i < firstEpochTicks; ++i) + ts.addTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + + // check ticks + for (int i = 0; i < firstEpochTicks; ++i) + ts.checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions); + + // Epoch transistion + ts.beginEpoch(secondEpochTick0); + ts.checkStateConsistencyWithAssert(); + + // add ticks + for (int i = 0; i < secondEpochTicks; ++i) + ts.addTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); + + // check ticks + for (int i = 0; i < secondEpochTicks; ++i) + ts.checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions); + bool previousEpoch = true; + for (int i = 0; i < firstEpochTicks; ++i) + ts.checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, previousEpoch); + + // Epoch transistion + ts.beginEpoch(thirdEpochTick0); + ts.checkStateConsistencyWithAssert(); + + // add ticks + for (int i = 0; i < thirdEpochTicks; ++i) + ts.addTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); + + // check ticks + for (int i = 0; i < thirdEpochTicks; ++i) + ts.checkTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions); + for (int i = 0; i < secondEpochTicks; ++i) + ts.checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, previousEpoch); + + ts.deinit(); + } +} diff --git a/test/time.cpp b/test/time.cpp new file mode 100644 index 000000000..e99ad2a0e --- /dev/null +++ b/test/time.cpp @@ -0,0 +1,313 @@ +#include "gtest/gtest.h" +#define NO_UEFI + +#include "platform/time.h" + +#include +#include + +// Convert TimeDate to std::tm +std::tm toTm(const TimeDate& t) +{ + std::tm tmTime{}; + tmTime.tm_sec = t.second; + tmTime.tm_min = t.minute; + tmTime.tm_hour = t.hour; + tmTime.tm_mday = t.day; + tmTime.tm_mon = t.month - 1; // tm_mon is 0-based + tmTime.tm_year = t.year + 100; // tm_year is years since 1900 + return tmTime; +} + +// Compute difference in seconds between two TimeDate values +long long stdSecondsBetween(const TimeDate& a, const TimeDate& b) +{ + std::tm tmA = toTm(a); + std::tm tmB = toTm(b); + std::time_t timeA = std::mktime(&tmA); + std::time_t timeB = std::mktime(&tmB); + return static_cast(std::difftime(timeB, timeA)); +} + +TEST(TestTime, DiffDateSecond) +{ + TimeDate A; + TimeDate B; + + // Normal + A.second = 56; + A.minute = 12; + A.hour = 4; + A.day = 7; + A.month = 5; + A.year = 1; + + B.second = 56; + B.minute = 12; + B.hour = 4; + B.day = 7; + B.month = 5; + B.year = 11; + + EXPECT_EQ(stdSecondsBetween(A, B), diffDateSecond(A, B)); + + // A is gap, B is not + A.second = 56; + A.minute = 12; + A.hour = 4; + A.day = 7; + A.month = 3; + A.year = 12; + + B.second = 56; + B.minute = 12; + B.hour = 4; + B.day = 7; + B.month = 5; + B.year = 21; + + // A is not gap, B is + A.second = 56; + A.minute = 12; + A.hour = 4; + A.day = 21; + A.month = 3; + A.year = 13; + + B.second = 56; + B.minute = 12; + B.hour = 4; + B.day = 1; + B.month = 3; + B.year = 24; + EXPECT_EQ(stdSecondsBetween(A, B), diffDateSecond(A, B)); + + // A,B is gap + A.second = 56; + A.minute = 12; + A.hour = 4; + A.day = 21; + A.month = 3; + A.year = 12; + + B.second = 56; + B.minute = 12; + B.hour = 4; + B.day = 1; + B.month = 3; + B.year = 24; + + EXPECT_EQ(stdSecondsBetween(A, B), diffDateSecond(A, B)); + +} + +TEST(TestTime, Unpack) +{ + unsigned int packedWeekTime; + WeekDay wd; + + // Thurs 15:20:30 + packedWeekTime = 0x040F141E; + wd = convertWeekTimeFromPackedData(packedWeekTime); + EXPECT_EQ(wd.dayOfWeek, 4); + EXPECT_EQ(wd.hour, 15); + EXPECT_EQ(wd.minute, 20); + EXPECT_EQ(wd.second, 30); + + // Sat 12:00:00 + packedWeekTime = 0x060C0000; + wd = convertWeekTimeFromPackedData(packedWeekTime); + EXPECT_EQ(wd.dayOfWeek, 6); + EXPECT_EQ(wd.hour, 12); + EXPECT_EQ(wd.minute, 0); + EXPECT_EQ(wd.second, 0); + + // Sun 12:00:00 + packedWeekTime = 0x000C0000; + wd = convertWeekTimeFromPackedData(packedWeekTime); + EXPECT_EQ(wd.dayOfWeek, 0); + EXPECT_EQ(wd.hour, 12); + EXPECT_EQ(wd.minute, 0); + EXPECT_EQ(wd.second, 0); +} + +TEST(TestTime, WeekDay) +{ + TimeDate A; + + // Normal + A.second = 56; + A.minute = 12; + A.hour = 4; + A.day = 7; + A.month = 5; + A.year = 1; + + std::tm tmA = toTm(A); + std::mktime(&tmA); + + EXPECT_EQ(getDayOfWeek(A.day, A.month, A.year), tmA.tm_wday); + +} + +TEST(TestTime, Comparison) +{ + TimeDate A; + TimeDate B; + + // Normal + A.second = 56; + A.minute = 12; + A.hour = 4; + A.day = 7; + A.month = 5; + A.year = 1; + + B.second = 56; + B.minute = 12; + B.hour = 4; + B.day = 7; + B.month = 5; + B.year = 11; + + EXPECT_EQ(compareTimeDate(A, A), 0); + EXPECT_EQ(compareTimeDate(B, A), 1); + EXPECT_EQ(compareTimeDate(A, B), -1); + + // Test week day in range + // + // Monday in [Sunday, Tuesday] + EXPECT_TRUE(isWeekDayInRange(1, 0, 2)); + + // Sunday is in [Saturday, Tuesday] + EXPECT_TRUE(isWeekDayInRange(0, 6, 2)); + + // Wednesday is in [Saturday, Thursday] + EXPECT_TRUE(isWeekDayInRange(3, 6, 4)); + + // Wednesday is in [Saturday, Wednesday] + EXPECT_TRUE(isWeekDayInRange(3, 6, 3)); + + // Wednesday is in [Wednesday, Saturday] + EXPECT_TRUE(isWeekDayInRange(3, 3, 6)); + + // Wednesday is not in [Sunday, Tuesday] + EXPECT_FALSE(isWeekDayInRange(3, 0, 2)); + + // Thursday is not in [Saturday, Tuesday] + EXPECT_FALSE(isWeekDayInRange(4, 6, 2)); + + WeekDay weekDay; + WeekDay startWeekDay, endWeekDay; + + // [Saturday 12:50:20] + startWeekDay.second = 20; + startWeekDay.minute = 50; + startWeekDay.hour = 12; + startWeekDay.dayOfWeek = 6; + + // [Monday 08:05:15] + endWeekDay.second = 15; + endWeekDay.minute = 5; + endWeekDay.hour = 8; + endWeekDay.dayOfWeek = 1; + + // [Sunday 04:12:56] + weekDay.second = 56; + weekDay.minute = 12; + weekDay.hour = 4; + weekDay.dayOfWeek = 0; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Tuesday 04:12:56] + weekDay.dayOfWeek = 2; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + //**** Test lower bound of week day + // [Saturday 12:50:21] + weekDay.dayOfWeek = 6; + weekDay.second = 21; + weekDay.minute = 50; + weekDay.hour = 12; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Saturday 12:51:20] + weekDay.dayOfWeek = 6; + weekDay.second = 20; + weekDay.minute = 51; + weekDay.hour = 12; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Saturday 13:50:20] + weekDay.dayOfWeek = 6; + weekDay.second = 20; + weekDay.minute = 50; + weekDay.hour = 13; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Saturday 12:50:19] + weekDay.dayOfWeek = 6; + weekDay.second = 19; + weekDay.minute = 50; + weekDay.hour = 12; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Saturday 12:49:20] + weekDay.dayOfWeek = 6; + weekDay.second = 20; + weekDay.minute = 49; + weekDay.hour = 12; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Saturday 11:50:20] + weekDay.dayOfWeek = 6; + weekDay.second = 20; + weekDay.minute = 50; + weekDay.hour = 11; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + //**** Test upper bound of week day + // [Monday 08:05:14] + weekDay.second = 14; + weekDay.minute = 5; + weekDay.hour = 8; + weekDay.dayOfWeek = 1; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Monday 08:04:15] + weekDay.second = 15; + weekDay.minute = 04; + weekDay.hour = 8; + weekDay.dayOfWeek = 1; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Monday 07:05:15] + weekDay.second = 15; + weekDay.minute = 5; + weekDay.hour = 7; + weekDay.dayOfWeek = 1; + EXPECT_TRUE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Monday 08:05:16] + weekDay.second = 16; + weekDay.minute = 5; + weekDay.hour = 8; + weekDay.dayOfWeek = 1; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Monday 08:06:15] + weekDay.second = 15; + weekDay.minute = 6; + weekDay.hour = 8; + weekDay.dayOfWeek = 1; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + + // [Monday 09:06:15] + weekDay.second = 15; + weekDay.minute = 5; + weekDay.hour = 9; + weekDay.dayOfWeek = 1; + EXPECT_FALSE(isWeekDayInRange(weekDay, startWeekDay, endWeekDay)); + +} + diff --git a/test/tx_status_request.cpp b/test/tx_status_request.cpp new file mode 100644 index 000000000..b72084010 --- /dev/null +++ b/test/tx_status_request.cpp @@ -0,0 +1,224 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#define system qubicSystemStruct +#define Peer void +void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data); + +#include "../src/public_settings.h" +#undef MAX_NUMBER_OF_TICKS_PER_EPOCH +#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 +#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH +#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 32 +#undef ADDON_TX_STATUS_REQUEST +#define ADDON_TX_STATUS_REQUEST 1 +#include "../src/addons/tx_status_request.h" + +#include + +unsigned int numberOfTransactions = 0; + + +// Add tick with random transactions, return whether all transactions could be stored +static bool addTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions) +{ + system.tick = tick; + + // set start index of tick transactions + txStatusData.tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; + + // use pseudo-random sequence for generating test data + std::mt19937_64 gen64(seed); + + // add transactions of tick + unsigned int transactionNum = gen64() % (maxTransactions + 1); + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + ++numberOfTransactions; + m256i digest(gen64(), gen64(), gen64(), gen64()); + if (!saveConfirmedTx(numberOfTransactions - 1, gen64() % 2, system.tick, digest)) + return false; + } + + return true; +} + + +struct { + RequestResponseHeader header; + RequestTxStatus payload; +} requestMessage; + +RespondTxStatus responseMessage; + + +static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data) +{ + const RespondTxStatus* txStatus = (const RespondTxStatus*)data; + + EXPECT_EQ(type, RespondTxStatus::type()); + EXPECT_EQ(dejavu, requestMessage.header.dejavu()); + EXPECT_EQ(dataSize, txStatus->size()); + + copyMem(&responseMessage, txStatus, txStatus->size()); +} + +static void checkTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions, bool fullyStoredTick, bool previousEpoch) +{ + // Ensure that we do not skip processRequestConfirmedTx() + if (system.tick <= tick) + system.tick = tick + 1; + + // check tick number dependent on if it is previous epoch + if (previousEpoch) + { + ASSERT(txStatusData.confirmedTxPreviousEpochBeginTick != 0); + EXPECT_LT(tick, txStatusData.confirmedTxCurrentEpochBeginTick); + if (tick < txStatusData.confirmedTxPreviousEpochBeginTick) + { + // tick not available -> okay + return; + } + } + else + { + ASSERT(tick >= txStatusData.confirmedTxCurrentEpochBeginTick && tick < txStatusData.confirmedTxCurrentEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH); + } + + // prepare request message + requestMessage.header.checkAndSetSize(sizeof(requestMessage)); + requestMessage.header.setType(RequestTxStatus::type()); + requestMessage.header.setDejavu(seed % UINT_MAX); + requestMessage.payload.tick = tick; + + // get status of tick transactions + responseMessage.tick = responseMessage.currentTickOfNode = 0; + processRequestConfirmedTx(0, nullptr, &requestMessage.header); + + // check that we received right data + EXPECT_EQ(responseMessage.tick, tick); + EXPECT_EQ(responseMessage.currentTickOfNode, system.tick); + + // use pseudo-random sequence for generating test data + std::mt19937_64 gen64(seed); + + // check number of transactions + unsigned int transactionNum = gen64() % (maxTransactions + 1); + if (fullyStoredTick) + { + EXPECT_EQ(responseMessage.txCount, transactionNum); + } + else + { + EXPECT_LE(responseMessage.txCount, transactionNum); + } + + // check tick's transaction digests and money flows + for (unsigned int transaction = 0; transaction < responseMessage.txCount; ++transaction) + { + m256i digest(gen64(), gen64(), gen64(), gen64()); + EXPECT_EQ(responseMessage.txDigests[transaction], digest); // CAUTION: responseMessage.txDigests only available up to responseMessage.txCount + + unsigned char receivedMoneyFlow = (responseMessage.moneyFlew[transaction / 8] >> (transaction % 8)) & 1; + unsigned char expectedMoneyFlew = gen64() % 2; + EXPECT_EQ(receivedMoneyFlow, expectedMoneyFlew); + } +} + + +TEST(TestCoreTxStatusRequestAddOn, EpochTransition) +{ + unsigned long long seed = 42; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // 5x test with running 2 epoch transitions + for (int testIdx = 0; testIdx < 20; ++testIdx) + { + // first, test case of having no transactions, then of having few transaction, later of having many transactions + unsigned short maxTransactions = 0; + if (testIdx == 1) + maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK / 4; + else if (testIdx > 1) + maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK; + + initTxStatusRequestAddOn(); + + const int firstEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const int secondEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const int thirdEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const unsigned int firstEpochTick0 = gen64() % 10000000; + const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; + const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; + unsigned long long firstEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + unsigned long long secondEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + unsigned long long thirdEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen64(); + for (int i = 0; i < secondEpochTicks; ++i) + secondEpochSeeds[i] = gen64(); + for (int i = 0; i < thirdEpochTicks; ++i) + thirdEpochSeeds[i] = gen64(); + + // first epoch + numberOfTransactions = 0; + system.initialTick = firstEpochTick0; + beginEpochTxStatusRequestAddOn(firstEpochTick0); + + // add ticks + int firstEpochLastFullyStoredTick = -1; + for (int i = 0; i < firstEpochTicks; ++i) + { + if (addTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions)) + firstEpochLastFullyStoredTick = i; + } + + // check ticks + bool previousEpoch = true; + for (int i = 0; i < firstEpochTicks; ++i) + checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, i < firstEpochLastFullyStoredTick, !previousEpoch); + + // Epoch transistion + numberOfTransactions = 0; + system.initialTick = secondEpochTick0; + beginEpochTxStatusRequestAddOn(secondEpochTick0); + + // add ticks + int secondEpochLastFullyStoredTick = -1; + for (int i = 0; i < secondEpochTicks; ++i) + { + if (addTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions)) + secondEpochLastFullyStoredTick = i; + } + + // check ticks + for (int i = 0; i < secondEpochTicks; ++i) + checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, !previousEpoch); + for (int i = 0; i < firstEpochTicks; ++i) + checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, i < firstEpochLastFullyStoredTick, previousEpoch); + + // Epoch transistion + numberOfTransactions = 0; + system.initialTick = thirdEpochTick0; + beginEpochTxStatusRequestAddOn(thirdEpochTick0); + + // add ticks + int thirdEpochLastFullyStoredTick = -1; + for (int i = 0; i < thirdEpochTicks; ++i) + { + if (addTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions)) + thirdEpochLastFullyStoredTick = i; + } + + // check ticks + for (int i = 0; i < thirdEpochTicks; ++i) + checkTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions, i < thirdEpochLastFullyStoredTick, !previousEpoch); + for (int i = 0; i < secondEpochTicks; ++i) + checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, previousEpoch); + + deinitTxStatusRequestAddOn(); + } +} + diff --git a/test/uint128.cpp b/test/uint128.cpp new file mode 100644 index 000000000..a2b273e58 --- /dev/null +++ b/test/uint128.cpp @@ -0,0 +1,199 @@ +// Copyright (c) 2013 - 2018 Jason Lee @ calccrypto at gmail.com +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// https://github.com/calccrypto/uint128_t/blob/master/uint128_t.cpp + +#define NO_UEFI + +#include "gtest/gtest.h" +#include "../src/platform/uint128.h" + + +TEST(Uint128Arithmetic, add){ + uint128_t low (0, 1); + uint128_t high(1, 0); + + EXPECT_EQ(low + low, uint128_t(0, 2)); + EXPECT_EQ(low + high, uint128_t(1, 1)); + EXPECT_EQ(high + high, uint128_t(2, 0)); + + EXPECT_EQ(low += low, uint128_t(0, 2)); + EXPECT_EQ(low += high, uint128_t(1, 2)); + EXPECT_EQ(high += low, uint128_t(2, 2)); +} + +// https://github.com/calccrypto/uint128_t/blob/master/tests/testcases/sub.cpp +TEST(Uint128Arithmetic, subtract){ + uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); + uint128_t small(1); + + EXPECT_EQ(small - small, uint128_t(0, 0)); + EXPECT_EQ(small - big, uint128_t(0, 2)); + EXPECT_EQ(big - small, uint128_t(0xffffffffffffffffULL, 0xfffffffffffffffeULL)); + EXPECT_EQ(big - big, uint128_t(0, 0)); +} + +// https://github.com/calccrypto/uint128_t/blob/master/tests/testcases/div.cpp +TEST(Uint128Arithmetic, divide){ + const uint128_t big_val (0xfedbca9876543210ULL); + const uint128_t small_val(0xffffULL); + const uint128_t res_val (0xfedcc9753fc9ULL); + + EXPECT_EQ(small_val / small_val, uint128_t(0, 1)); + EXPECT_EQ(small_val / big_val, uint128_t(0, 0)); + + EXPECT_EQ(big_val / big_val, uint128_t(0, 1)); + + // EXPECT_THROW(uint128_t(1) / uint128_t(0), std::domain_error); +} + +TEST(Uint128Arithmetic, multiply){ + uint128_t val(0xfedbca9876543210ULL); + + EXPECT_EQ(val * val, uint128_t(0xfdb8e2bacbfe7cefULL, 0x010e6cd7a44a4100ULL)); + + const uint128_t zero = 0; + EXPECT_EQ(val * zero, zero); + EXPECT_EQ(zero * val, zero); + + const uint128_t one = 1; + EXPECT_EQ(val * one, val); + EXPECT_EQ(one * val, val); +} + +TEST(Uint128Comparison, equals){ + EXPECT_EQ( (uint128_t(0xdeadbeefULL) == uint128_t(0xdeadbeefULL)), true); + EXPECT_EQ(!(uint128_t(0xdeadbeefULL) == uint128_t(0xfee1baadULL)), true); +} + +TEST(Uint128Comparison, less_than){ + const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); + const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); + + EXPECT_EQ(small < small, false); + EXPECT_EQ(small < big, true); + + EXPECT_EQ(big < small, false); + EXPECT_EQ(big < big, false); +} + + +TEST(Uint128Comparison, greater_than){ + const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); + const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); + + EXPECT_EQ(small > small, false); + EXPECT_EQ(small > big, false); + + EXPECT_EQ(big > small, true); + EXPECT_EQ(big > big, false); +} + +TEST(Uint128Comparison, greater_than_or_equals){ + const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); + const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); + + EXPECT_EQ(small >= small, true); + EXPECT_EQ(small >= big, false); + + EXPECT_EQ(big >= small, true); + EXPECT_EQ(big >= big, true); +} + +TEST(Uint128Comparison, less_than_or_equals){ + const uint128_t big (0xffffffffffffffffULL, 0xffffffffffffffffULL); + const uint128_t small(0x0000000000000000ULL, 0x0000000000000000ULL); + + EXPECT_EQ(small <= small, true); + EXPECT_EQ(small <= big, true); + + EXPECT_EQ(big <= small, false); + EXPECT_EQ(big <= big, true); +} + +TEST(Uint128BitShift, left){ + // operator<< + uint128_t val(0x1); + uint64_t exp_val = 1; + for(uint8_t i = 0; i < 64; i++){ + EXPECT_EQ(val << uint128_t(i), uint128_t(exp_val << i)); + } + + uint128_t zero(0); + for(uint8_t i = 0; i < 64; i++){ + EXPECT_EQ(zero << uint128_t(i), uint128_t(0)); + } + + // operator<<= + for(uint8_t i = 0; i < 63; i++){ // 1 is already a bit + EXPECT_EQ(val <<= uint128_t(1), uint128_t(exp_val <<= 1)); + } + + for(uint8_t i = 0; i < 63; i++){ + EXPECT_EQ(zero <<= uint128_t(1), uint128_t(0)); + } +} + +TEST(Uint128BitShift, right){ + // operator>> + uint128_t val(0xffffffffffffffffULL); + uint64_t exp = 0xffffffffffffffffULL; + for(uint8_t i = 0; i < 64; i++){ + EXPECT_EQ(val >> uint128_t(i), uint128_t(exp >> i)); + } + + uint128_t zero(0); + for(uint8_t i = 0; i < 64; i++){ + EXPECT_EQ(zero >> uint128_t(i), uint128_t(0)); + } + + // operator>>= + for(uint8_t i = 0; i < 64; i++){ + EXPECT_EQ(val >>= uint128_t(1), uint128_t(exp >>= 1)); + } + + for(uint8_t i = 0; i < 64; i++){ + EXPECT_EQ(zero >>= uint128_t(1), uint128_t(0)); + } +} + +TEST(Uint128BitWise, and){ + uint128_t t ((bool) true); + uint128_t f ((bool) false); + uint128_t u8 ((uint8_t) 0xaaULL); + uint128_t u16((uint16_t) 0xaaaaULL); + uint128_t u32((uint32_t) 0xaaaaaaaaULL); + uint128_t u64((uint64_t) 0xaaaaaaaaaaaaaaaaULL); + + const uint128_t val(0xf0f0f0f0f0f0f0f0ULL, 0xf0f0f0f0f0f0f0f0ULL); + + EXPECT_EQ(t & val, uint128_t(0)); + EXPECT_EQ(f & val, uint128_t(0)); + EXPECT_EQ(u8 & val, uint128_t(0xa0ULL)); + EXPECT_EQ(u16 & val, uint128_t(0xa0a0ULL)); + EXPECT_EQ(u32 & val, uint128_t(0xa0a0a0a0ULL)); + EXPECT_EQ(u64 & val, uint128_t(0xa0a0a0a0a0a0a0a0ULL)); + + EXPECT_EQ(t &= val, uint128_t(0x0ULL)); + EXPECT_EQ(f &= val, uint128_t(0x0ULL)); + EXPECT_EQ(u8 &= val, uint128_t(0xa0ULL)); + EXPECT_EQ(u16 &= val, uint128_t(0xa0a0ULL)); + EXPECT_EQ(u32 &= val, uint128_t(0xa0a0a0a0ULL)); + EXPECT_EQ(u64 &= val, uint128_t(0xa0a0a0a0a0a0a0a0ULL)); +} \ No newline at end of file diff --git a/test/utils.h b/test/utils.h new file mode 100644 index 000000000..734d88933 --- /dev/null +++ b/test/utils.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace test_utils +{ + +static std::string byteToHex(const unsigned char* byteArray, size_t sizeInByte) +{ + std::ostringstream oss; + for (size_t i = 0; i < sizeInByte; ++i) + { + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byteArray[i]); + } + return oss.str(); + +} +static m256i hexTo32Bytes(const std::string& hex, const int sizeInByte) +{ + if (hex.length() != sizeInByte * 2) { + throw std::invalid_argument("Hex string length does not match the expected size"); + } + + m256i byteArray; + for (size_t i = 0; i < sizeInByte; ++i) + { + byteArray.m256i_u8[i] = std::stoi(hex.substr(i * 2, 2), nullptr, 16); + } + + return byteArray; +} + +static void hexToByte(const std::string& hex, const int sizeInByte, unsigned char* out) +{ + if (hex.length() != sizeInByte * 2) + { + throw std::invalid_argument("Hex string length does not match the expected size"); + } + + for (size_t i = 0; i < sizeInByte; ++i) + { + out[i] = std::stoi(hex.substr(i * 2, 2), nullptr, 16); + } +} + +// Function to read and parse the CSV file +static std::vector> readCSV(const std::string& filename) +{ + std::vector> data; + std::ifstream file(filename); + std::string line; + + // Read each line from the file + while (std::getline(file, line)) + { + std::stringstream ss(line); + std::string item; + std::vector parsedLine; + + // Parse each item separated by commas + while (std::getline(ss, item, ',')) + { + // Remove any spaces in the string + item.erase(remove_if(item.begin(), item.end(), isspace), item.end()); + + parsedLine.push_back(item); + } + data.push_back(parsedLine); + } + return data; +} + +static m256i convertFromString(std::string& rStr) +{ + m256i value; + std::stringstream ss(rStr); + std::string item; + int i = 0; + while (std::getline(ss, item, '-')) + { + value.m256i_u64[i++] = std::stoull(item); + } + return value; +} + +static std::vector convertULLFromString(std::string& rStr) +{ + std::vector values; + std::stringstream ss(rStr); + std::string item; + int i = 0; + while (std::getline(ss, item, '-')) + { + values.push_back(std::stoull(item)); + } + return values; +} + +} // test_utils diff --git a/test/virtual_memory.cpp b/test/virtual_memory.cpp new file mode 100644 index 000000000..50794b981 --- /dev/null +++ b/test/virtual_memory.cpp @@ -0,0 +1,239 @@ +#define NO_UEFI + +#include "gtest/gtest.h" +#include "../src/network_messages/tick.h" +#include "../src/public_settings.h" +#include "../src/platform/virtual_memory.h" + +#include + +TEST(TestVirtualMemory, TestVirtualMemory_NativeChar) { + initFilesystem(); + registerAsynFileIO(NULL); + const unsigned long long name_u64 = 123456789; + const unsigned long long pageDir = 0; + VirtualMemory test_vm; + test_vm.init(); + std::vector arr; + // fill with random value + const int N = 1000000; + arr.resize(N); + srand(0); + for (int i = 0; i < N; i++) + { + arr[i] = int(rand() % 256) - 127; + } + // append to virtual memory via single append + for (int i = 0; i < 113; i++) + { + test_vm.append(arr[i]); + } + int pos = 113; + int stride = 1337; + while (pos < N) + { + int s = pos; + int e = std::min(pos + stride, N); + int n_item = e - s; + test_vm.appendMany(arr.data() + s, n_item); + pos += stride; + } + std::vector fetcher; + + for (int i = 0; i < 1024; i++) + { + int offset = rand() % (N/2); + int test_len = rand() % (N - offset); + fetcher.resize(test_len); + test_vm.getMany(fetcher.data(), offset, test_len); + EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len) == 0); + } + + for (int i = 0; i < 1024; i++) + { + int index = rand() % N; + EXPECT_TRUE(test_vm[index] == arr[index]); + } + test_vm.deinit(); +} + +#define IMAX_BITS(m) ((m)/((m)%255+1) / 255%255*8 + 7-86/((m)%255+12)) +#define RAND_MAX_WIDTH IMAX_BITS(RAND_MAX) +static_assert((RAND_MAX& (RAND_MAX + 1u)) == 0, "RAND_MAX not a Mersenne number"); +uint64_t rand64(void) { + uint64_t r = 0; + for (int i = 0; i < 64; i += RAND_MAX_WIDTH) { + r <<= RAND_MAX_WIDTH; + r ^= (unsigned)rand(); + } + return r; +} + +TEST(TestVirtualMemory, TestVirtualMemory_NativeU64) { + initFilesystem(); + registerAsynFileIO(NULL); + const unsigned long long name_u64 = 123456789; + const unsigned long long pageDir = 0; + VirtualMemory test_vm; + test_vm.init(); + std::vector arr; + // fill with random value + const int N = 1000000; + arr.resize(N); + srand(0); + for (int i = 0; i < N; i++) + { + arr[i] = rand64(); + } + // append to virtual memory via single append + for (int i = 0; i < 113; i++) + { + test_vm.append(arr[i]); + } + int pos = 113; + int stride = 1337; + while (pos < N) + { + int s = pos; + int e = std::min(pos + stride, N); + int n_item = e - s; + test_vm.appendMany(arr.data() + s, n_item); + pos += stride; + } + std::vector fetcher; + + for (int i = 0; i < 1024; i++) + { + int offset = rand() % (N/2); + int test_len = rand() % (N - offset); + fetcher.resize(test_len); + test_vm.getMany(fetcher.data(), offset, test_len); + EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len * sizeof(unsigned long long)) == 0); + } + for (int i = 0; i < 1024; i++) + { + int index = rand() % N; + EXPECT_TRUE(test_vm[index] == arr[index]); + } + test_vm.deinit(); +} + +TickData randTick() +{ + TickData res; + int* ptr = (int*)&res; + int sz = sizeof(res); + for (int i = 0; i < sz/4; i++) { + ptr[i] = rand(); + } + return res; +} + +bool tickEqual(const TickData a, const TickData b) +{ + return memcmp(&a, &b, sizeof(TickData)) == 0; +} + +TEST(TestVirtualMemory, TestVirtualMemory_TickStruct) { + initFilesystem(); + registerAsynFileIO(NULL); + const unsigned long long name_u64 = 123456789; + const unsigned long long pageDir = 0; + VirtualMemory test_vm; + test_vm.init(); + std::vector arr; + // fill with random value + const int N = 1000; + arr.resize(N); + srand(0); + for (int i = 0; i < N; i++) + { + arr[i] = randTick(); + } + // append to virtual memory via single append + for (int i = 0; i < 113; i++) + { + test_vm.append(arr[i]); + } + int pos = 113; + int stride = 1337; + while (pos < N) + { + int s = pos; + int e = std::min(pos + stride, N); + int n_item = e - s; + test_vm.appendMany(arr.data() + s, n_item); + pos += stride; + } + std::vector fetcher; + + for (int i = 0; i < 1024; i++) + { + int offset = rand() % (N/2); + int test_len = rand() % (N - offset); + fetcher.resize(test_len); + test_vm.getMany(fetcher.data(), offset, test_len); + EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len * sizeof(TickData)) == 0); + } + for (int i = 0; i < 1024; i++) + { + int index = rand() % N; + EXPECT_TRUE(tickEqual(test_vm[index], arr[index])); + } + test_vm.deinit(); +} + +TEST(TestVirtualMemory, TestVirtualMemory_SpecialCases) { + initFilesystem(); + registerAsynFileIO(NULL); + const unsigned long long name_u64 = 123456789; + const unsigned long long pageDir = 0; + const unsigned long long pageCap = 2001; + VirtualMemory test_vm; + test_vm.init(); + std::vector arr; + // fill with random value + const int N = 1000000; + arr.resize(N); + srand(0); + for (int i = 0; i < N; i++) + { + arr[i] = int(rand() % 256) - 127; + } + // append to virtual memory via single append + for (int i = 0; i < 113; i++) + { + test_vm.append(arr[i]); + } + int pos = 113; + int stride = 1337; + while (pos < N) + { + int s = pos; + int e = std::min(pos + stride, N); + int n_item = e - s; + test_vm.appendMany(arr.data() + s, n_item); + pos += stride; + } + + // case: head start aligns page + std::vector fetcher; + { + int offset = pageCap; + int test_len = pageCap * 5 + 1; + fetcher.resize(test_len); + test_vm.getMany(fetcher.data(), offset, test_len); + EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len) == 0); + } + + // case: tail end aligns page + { + int offset = 1337; + int test_len = pageCap * 5 + (pageCap - 1337); + fetcher.resize(test_len); + test_vm.getMany(fetcher.data(), offset, test_len); + EXPECT_TRUE(memcmp(fetcher.data(), arr.data() + offset, test_len) == 0); + } + + test_vm.deinit(); +} \ No newline at end of file diff --git a/test/vote_counter.cpp b/test/vote_counter.cpp new file mode 100644 index 000000000..1f4000e63 --- /dev/null +++ b/test/vote_counter.cpp @@ -0,0 +1,172 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#include "../src/public_settings.h" +#include "../src/vote_counter.h" + +#include + + +class TestVoteCounter : public VoteCounter +{ +public: + unsigned int testExtract10Bit(unsigned char* data, unsigned int idx) + { + return extract10Bit(data, idx); + } + void testUpdate10Bit(unsigned char* data, unsigned int idx, unsigned int value) + { + update10Bit(data, idx, value); + } +}; + +TestVoteCounter tvc; + + +TEST(TestCoreVoteCounter, TenBitsEncodeDecode) { + unsigned char data_u10[848]; + unsigned int data_u32[676]; + for (int i = 0; i < 32; i++) + { + srand(i); + for (int i = 0; i < 676; i++) + { + unsigned int rd = rand() % 676; + data_u32[i] = rd; + tvc.testUpdate10Bit(data_u10, i, rd); + } + bool isMatched = true; + for (int i = 0; i < 676; i++) + { + unsigned int extracted = tvc.testExtract10Bit(data_u10, i); + if (extracted != data_u32[i]) + { + isMatched = false; + break; + } + } + EXPECT_TRUE(isMatched); + } +} + +TEST(TestCoreVoteCounter, NewVotePacketValidation) { + unsigned char data_u10[848]; + srand(0); + setMem(data_u10, sizeof(data_u10), 0); + for (int i = 0; i < 451; i++) + { + unsigned int rd = rand() % 676; + tvc.testUpdate10Bit(data_u10, i, rd); + } + tvc.testUpdate10Bit(data_u10, 0, 0); + bool isValid = tvc.validateNewVotesPacket(data_u10, 0); // total votes is lower than 451 * 676 + EXPECT_TRUE(!isValid); + + setMem(data_u10, sizeof(data_u10), 0); + for (int i = 0; i < 676; i++) + { + unsigned int rd = 451 + rand() % (676-451); + tvc.testUpdate10Bit(data_u10, i, rd); + } + tvc.testUpdate10Bit(data_u10, 0, 300); + isValid = tvc.validateNewVotesPacket(data_u10, 0); // self-report is not zero + EXPECT_TRUE(!isValid); + + setMem(data_u10, sizeof(data_u10), 0); + for (int i = 0; i < 676; i++) + { + unsigned int rd = 451 + rand() % (676 - 451); + tvc.testUpdate10Bit(data_u10, i, rd); + } + tvc.testUpdate10Bit(data_u10, 0, 0); + isValid = tvc.validateNewVotesPacket(data_u10, 0); // valid + EXPECT_TRUE(isValid); +} + +TEST(TestCoreVoteCounter, AddGetVotes) { + unsigned char data_u10[848] = { 0 }; + unsigned int data_u32[676] = { 0 }; + srand(0); + tvc.init(); + for (int comp = 0; comp < 676; comp++) + { + for (int i = 0; i < 676; i++) + { + unsigned int rd = 451 + (rand() % (676-451)); + tvc.testUpdate10Bit(data_u10, i, rd); + } + tvc.testUpdate10Bit(data_u10, comp, 0); + + tvc.addVotes(data_u10, comp); + for (int i = 0; i < 676; i++) + { + data_u32[i] += tvc.testExtract10Bit(data_u10, i); + } + } + bool isMatched = true; + for (int i = 0; i < 676; i++) + { + if (tvc.getVoteCount(i) != data_u32[i]) + { + isMatched = false; + printf("[FAILED] comp %d: %llu vs %lu\n", i, tvc.getVoteCount(i), data_u32[i]); + break; + } + } + EXPECT_TRUE(isMatched); +} + +bool tick_data[676 * 4][676]; +TEST(TestCoreVoteCounter, RegisterNewCompressVotes) { + unsigned char data_u10[848] = { 0 }; + unsigned int data_u32[676] = { 0 }; + setMem(tick_data, sizeof(tick_data), 0); + srand(0); + tvc.init(); + bool isMatched = true; + unsigned int startTick = 676 + rand() % 676; + for (unsigned int tick = startTick; tick < 676 * 4; tick++) + { + bool flag[676]; + setMem(flag, sizeof(flag), true); + int n_non_vote = rand() % (676 - 451); + for (int i = 0; i < n_non_vote; i++) + { + flag[rand() % 676] = false; + } + for (int i = 0; i < 676; i++) + { + if (flag[i]) + { + tvc.registerNewVote(tick, i); + tick_data[tick][i] = true; + } + } + for (int k = 0; k < 2; k++) // randomly select 2 comp to test + { + int comp = rand() % 676; + tvc.compressNewVotesPacket(tick - 675, tick + 1, comp, data_u10); + setMem(data_u32, sizeof(data_u32), 0); + for (unsigned int t = tick - 675; t <= tick; t++) + { + for (int i = 0; i < 676; i++) + { + if (tick_data[t][i]) data_u32[i]++; + } + } + data_u32[comp] = 0; + for (int i = 0; i < 676; i++) + { + unsigned int vote = tvc.testExtract10Bit(data_u10, i); + if (vote != data_u32[i]) + { + isMatched = false; + break; + } + } + } + EXPECT_TRUE(isMatched); + //printf("[PASSED] tick %u\n", tick); + } +} \ No newline at end of file From 7bacfc0d65c33c2cad42f7478287754c15110b8c Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Tue, 24 Feb 2026 18:44:57 -0500 Subject: [PATCH 08/30] testnet-conflict-fix --- src/contract_core/contract_def.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h index 3b97da099..9350e99af 100644 --- a/src/contract_core/contract_def.h +++ b/src/contract_core/contract_def.h @@ -351,7 +351,7 @@ constexpr struct ContractDescription {"QBOND", 182, 10000, sizeof(QBOND)}, // proposal in epoch 180, IPO in 181, construction and first use in 182 {"QIP", 189, 10000, sizeof(QIP)}, // proposal in epoch 187, IPO in 188, construction and first use in 189 {"QRAFFLE", 192, 10000, sizeof(QRAFFLE)}, // proposal in epoch 190, IPO in 191, construction and first use in 192 - {"QRWA", 197, 10000, sizeof(QRWA)}, // proposal in epoch 195, IPO in 196, construction and first use in 197 + {"QRWA", 202, 10000, sizeof(QRWA)}, // proposal in epoch 195, IPO in 196, construction and first use in 197 {"QRP", 199, 10000, sizeof(IPO)}, // proposal in epoch 197, IPO in 198, construction and first use in 199 {"QTF", 199, 10000, sizeof(QTF)}, // proposal in epoch 197, IPO in 198, construction and first use in 199 {"QDUEL", 199, 10000, sizeof(QDUEL)}, // proposal in epoch 197, IPO in 198, construction and first use in 199 @@ -534,3 +534,4 @@ class UserProcedureRegistry // For registering and looking up user procedures independently of input type (for notifications), initialized by initContractExec() GLOBAL_VAR_DECL UserProcedureRegistry* userProcedureRegistry GLOBAL_VAR_INIT(nullptr); + From 9aee5606dbdc298d9f1bd3a846768e3712ed7c1b Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:54:56 -0300 Subject: [PATCH 09/30] TESTING: Payout every 100 ticks instead of weekly schedule --- src/contracts/qRWA.h | 48 +++++++++++--------------------------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index 5fe8a3597..8b50cd0ce 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -20,9 +20,10 @@ constexpr uint64 QRWA_QMINE_PER_QRWA_SHARE_MIN = 100000ULL; constexpr uint64 QRWA_CONTRACT_ASSET_NAME = 1096241745ULL; // assetNameFromString("QRWA") // Payout Timing Constants -constexpr uint64 QRWA_PAYOUT_DAY = FRIDAY; // Friday -constexpr uint64 QRWA_PAYOUT_HOUR = 12; // 12:00 PM UTC -constexpr uint64 QRWA_MIN_PAYOUT_INTERVAL_MS = 6 * 86400000LL; // 6 days in milliseconds +constexpr uint64 QRWA_PAYOUT_DAY = FRIDAY; // Friday (Production) +constexpr uint64 QRWA_PAYOUT_HOUR = 12; // 12:00 PM UTC (Production) +constexpr uint64 QRWA_MIN_PAYOUT_INTERVAL_MS = 6 * 86400000LL; // 6 days in milliseconds (Production) +constexpr uint64 QRWA_PAYOUT_TICK_INTERVAL = 100; // TESTING: Check every 100 ticks for payout // STATUS CODES for Procedures constexpr uint64 QRWA_STATUS_SUCCESS = 1; @@ -188,7 +189,8 @@ struct QRWA : public ContractBase HashMap mGeneralAssetBalances; // Balances for other assets (e.g., SC shares) // Payouts and Dividend Accounting - DateAndTime mLastPayoutTime; // Tracks the last payout time + DateAndTime mLastPayoutTime; // Tracks the last payout time (Production) + uint64 mLastPayoutTick; // TESTING: Tick-based payout tracking // Dividend Pools uint64 mRevenuePoolA; // Mined funds from Qubic farm (from SCs) @@ -1117,6 +1119,7 @@ struct QRWA : public ContractBase state.mTreasuryBalance = 0; state.mCurrentAssetProposalId = 0; setMemory(state.mLastPayoutTime, 0); + state.mLastPayoutTick = 0; // Initialize default governance parameters state.mCurrentGovParams.mAdminAddress = ID( @@ -1733,37 +1736,10 @@ struct QRWA : public ContractBase { locals.now = qpi.now(); - // Check payout conditions: Correct day, correct hour, and enough time passed - // TESTING: Freitag 12:00 Prüfung auskommentiert - nutze Tick 44602000 für Auszahlung - // if (qpi.dayOfWeek((uint8)mod(locals.now.getYear(), (uint16)100), locals.now.getMonth(), locals.now.getDay()) == QRWA_PAYOUT_DAY && - // locals.now.getHour() == QRWA_PAYOUT_HOUR) - if (qpi.tick() >= 44602200) // TESTING: Nur bei Tick 44602000+ - entfernen für Production + // TESTING: Check every 100 ticks for payout distribution + // Production: Use day/hour check instead (QRWA_PAYOUT_DAY + QRWA_PAYOUT_HOUR) + if (state.mLastPayoutTick == 0 || (qpi.tick() - state.mLastPayoutTick) >= QRWA_PAYOUT_TICK_INTERVAL) { - // check if mLastPayoutTime is 0 (never initialized) - if (state.mLastPayoutTime.getYear() == 0) - { - // If never paid, treat as if enough time has passed - locals.msSinceLastPayout = QRWA_MIN_PAYOUT_INTERVAL_MS; - } - else - { - locals.durationMicros = state.mLastPayoutTime.durationMicrosec(locals.now); - - if (locals.durationMicros != UINT64_MAX) - { - locals.msSinceLastPayout = div(locals.durationMicros, 1000); - } - else - { - // If it's invalid but NOT zero, something is wrong, so we prevent payout - locals.msSinceLastPayout = 0; - } - } - - // TESTING: 6-Tage Minimum-Interval auskommentiert für sofortiges Testing - // if (locals.msSinceLastPayout >= QRWA_MIN_PAYOUT_INTERVAL_MS) - if (true) // TESTING: Immer true - Auszahlung damit sofort bei Tick 44602000 - entfernen für Production - { locals.logger.contractId = CONTRACT_INDEX; locals.logger.logType = QRWA_LOG_TYPE_DISTRIBUTION; @@ -2090,14 +2066,14 @@ struct QRWA : public ContractBase } } - // Update last payout time + // Update last payout time/tick state.mLastPayoutTime = qpi.now(); + state.mLastPayoutTick = qpi.tick(); locals.logger.logType = QRWA_LOG_TYPE_DISTRIBUTION; locals.logger.primaryId = NULL_ID; locals.logger.valueA = 1; // Indicate success locals.logger.valueB = 0; LOG_INFO(locals.logger); - } } } From c6c085cf671ab60723b0c85149fe9c180abc153c Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:58:24 -0300 Subject: [PATCH 10/30] qRWA: reduce payout tick interval to 20 for testing --- src/contracts/qRWA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index 8b50cd0ce..f4b98a418 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -23,7 +23,7 @@ constexpr uint64 QRWA_CONTRACT_ASSET_NAME = 1096241745ULL; // assetNameFromStrin constexpr uint64 QRWA_PAYOUT_DAY = FRIDAY; // Friday (Production) constexpr uint64 QRWA_PAYOUT_HOUR = 12; // 12:00 PM UTC (Production) constexpr uint64 QRWA_MIN_PAYOUT_INTERVAL_MS = 6 * 86400000LL; // 6 days in milliseconds (Production) -constexpr uint64 QRWA_PAYOUT_TICK_INTERVAL = 100; // TESTING: Check every 100 ticks for payout +constexpr uint64 QRWA_PAYOUT_TICK_INTERVAL = 20; // TESTING: Check every 20 ticks for payout // STATUS CODES for Procedures constexpr uint64 QRWA_STATUS_SUCCESS = 1; From 330dd1910367cb344bdfd12032f5c8710f7a0f7f Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:11:41 -0300 Subject: [PATCH 11/30] qRWA: require 100K QMINE per share for regular qRWA dividend distribution Replace distributeDividends with manual iteration that checks each qRWA holder has >= QRWA_QMINE_PER_QRWA_SHARE_MIN (100K) QMINE per qRWA share, matching the dedicated pool logic. --- src/contracts/qRWA.h | 108 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index f4b98a418..5d04a70e7 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -1708,6 +1708,7 @@ struct QRWA : public ContractBase uint64 dedicatedQrwaPayout; uint64 amountPerQRWAShare; uint64 distributedAmount; + uint64 eligibleShares; uint64 dedicatedEligibleShares; uint64 dedicatedAmountPerShare; uint64 dedicatedDistributed; @@ -1944,16 +1945,111 @@ struct QRWA : public ContractBase state.mQmineDividendPool = locals.qmineDividendPool_128.low; } // End QMINE distribution - // Distribute qRWA shareholder rewards + // Distribute qRWA shareholder rewards (only to holders with >= 100K QMINE per qRWA share) if (state.mQRWADividendPool > 0) { - locals.amountPerQRWAShare = div(state.mQRWADividendPool, NUMBER_OF_COMPUTORS); - if (locals.amountPerQRWAShare > 0) + locals.qrwaAsset.issuer = id::zero(); + locals.qrwaAsset.assetName = QRWA_CONTRACT_ASSET_NAME; + locals.eligibleShares = 0; + + // First pass: count eligible shares + for (locals.qrwaIter.begin(locals.qrwaAsset); !locals.qrwaIter.reachedEnd(); locals.qrwaIter.next()) + { + locals.qrwaShares = static_cast(locals.qrwaIter.numberOfPossessedShares()); + if (locals.qrwaShares == 0) + { + continue; + } + + locals.holder = locals.qrwaIter.possessor(); + if (locals.holder == SELF) + { + continue; + } + + locals.qmineBalance = qpi.numberOfShares( + state.mQmineAsset, + AssetOwnershipSelect::byOwner(locals.holder), + AssetPossessionSelect::byPossessor(locals.holder) + ); + + if (locals.qmineBalance <= 0) + { + continue; + } + + locals.requiredQmine = smul(locals.qrwaShares, QRWA_QMINE_PER_QRWA_SHARE_MIN); + if (static_cast(locals.qmineBalance) >= locals.requiredQmine) + { + locals.eligibleShares = sadd(locals.eligibleShares, locals.qrwaShares); + } + } + + if (locals.eligibleShares > 0) { - if (qpi.distributeDividends(static_cast(locals.amountPerQRWAShare))) + locals.amountPerQRWAShare = div(state.mQRWADividendPool, locals.eligibleShares); + if (locals.amountPerQRWAShare > 0) { - locals.distributedAmount = smul(locals.amountPerQRWAShare, static_cast(NUMBER_OF_COMPUTORS)); - state.mQRWADividendPool -= locals.distributedAmount; + locals.distributedAmount = 0; + + // Second pass: distribute to eligible holders + for (locals.qrwaIter.begin(locals.qrwaAsset); !locals.qrwaIter.reachedEnd(); locals.qrwaIter.next()) + { + locals.qrwaShares = static_cast(locals.qrwaIter.numberOfPossessedShares()); + if (locals.qrwaShares == 0) + { + continue; + } + + locals.holder = locals.qrwaIter.possessor(); + if (locals.holder == SELF) + { + continue; + } + + locals.qmineBalance = qpi.numberOfShares( + state.mQmineAsset, + AssetOwnershipSelect::byOwner(locals.holder), + AssetPossessionSelect::byPossessor(locals.holder) + ); + + if (locals.qmineBalance <= 0) + { + continue; + } + + locals.requiredQmine = smul(locals.qrwaShares, QRWA_QMINE_PER_QRWA_SHARE_MIN); + if (static_cast(locals.qmineBalance) < locals.requiredQmine) + { + continue; + } + + locals.payout_u64 = smul(locals.amountPerQRWAShare, locals.qrwaShares); + if (locals.payout_u64 > 0) + { + if (qpi.transfer(locals.holder, static_cast(locals.payout_u64)) >= 0) + { + locals.distributedAmount = sadd(locals.distributedAmount, locals.payout_u64); + } + else + { + locals.logger.logType = QRWA_LOG_TYPE_ERROR; + locals.logger.primaryId = locals.holder; + locals.logger.valueA = locals.payout_u64; + locals.logger.valueB = QRWA_STATUS_FAILURE_TRANSFER_FAILED; + LOG_INFO(locals.logger); + } + } + } + + if (state.mQRWADividendPool > locals.distributedAmount) + { + state.mQRWADividendPool -= locals.distributedAmount; + } + else + { + state.mQRWADividendPool = 0; + } state.mTotalQRWADistributed = sadd(state.mTotalQRWADistributed, locals.distributedAmount); } } From c95226fd01e7b3853b558b7c24f34af297284a6b Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:09:55 -0300 Subject: [PATCH 12/30] feat(qRWA): add dedicated pool to GetDividendBalances + TEST 9c - Extend GetDividendBalances (Fn 5) with dedicatedRevenuePool and dedicatedQRWADividendPool fields (positions 5+6) - Add SEED3 for dedicated revenue address (PDQTKKIR...QEFC) - Add TEST 9c: send from dedicated address -> verify dedicated pool routing - Update all call_fn 5 output formats to 6 fields - TEST 13 payout detection now monitors dedicated pool changes - Update README Section 12 with dedicated pool documentation --- src/contracts/README_QRWA_TEST.md | 514 +++++++--- src/contracts/qRWA.h | 4 + test_qrwa.sh | 1447 +++++++++++++++++++++++++++++ 3 files changed, 1810 insertions(+), 155 deletions(-) create mode 100755 test_qrwa.sh diff --git a/src/contracts/README_QRWA_TEST.md b/src/contracts/README_QRWA_TEST.md index 999b4dc43..d3a2bcb4d 100644 --- a/src/contracts/README_QRWA_TEST.md +++ b/src/contracts/README_QRWA_TEST.md @@ -1,47 +1,47 @@ -# Qubic Smart Contract – Entwicklungs-Checkliste & Testreferenz +# Qubic Smart Contract – Development Checklist & Test Reference -## Schnellübersicht: Was den Node crashen kann +## Quick Reference: What Can Crash the Node -| Problem | Symptom | Lösung | -|---------|---------|--------| -| `struct XXXX2 {}` fehlt | Node startet nicht / Crash | Leere Struct vor der Haupt-Struct definieren | -| State > 1 GB | Node startet nicht | HashMap-Kapazitäten reduzieren | -| Verbotene C++ Features | Kompiliert evtl., crasht zur Laufzeit | Siehe Verbotsliste unten | -| Falsche Adress-Länge in `ID()` | Kompiliert nicht (Parameteranzahl) | Genau 56 Zeichen (ohne 4-Char Checksum) | +| Problem | Symptom | Solution | +|---------|---------|----------| +| `struct XXXX2 {}` missing | Node won't start / crash | Define empty struct before main struct | +| State > 1 GB | Node won't start | Reduce HashMap capacities | +| Forbidden C++ features | May compile, crashes at runtime | See forbidden list below | +| Wrong address length in `ID()` | Won't compile (parameter count) | Exactly 56 characters (without 4-char checksum) | --- -## 1. Dateistruktur +## 1. File Structure ``` -src/contracts/YourContract.h ← Der Contract (eine einzige .h Datei) -src/contract_core/contract_def.h ← Registrierung (3 Stellen) -test/contract_yourcontract.cpp ← GoogleTest Tests +src/contracts/YourContract.h ← The contract (a single .h file) +src/contract_core/contract_def.h ← Registration (3 locations) +test/contract_yourcontract.cpp ← GoogleTest tests ``` -### Contract-Datei Grundgerüst +### Contract File Skeleton ```cpp using namespace QPI; -// Globale Konstanten – MÜSSEN mit Contract-Name prefixed sein +// Global constants – MUST be prefixed with contract name constexpr uint64 MYCONTRACT_SOME_VALUE = 42; -// WICHTIG: Sekundäre State-Struct (für zukünftige EXPAND Events) +// IMPORTANT: Secondary state struct (for future EXPAND events) struct MYCONTRACT2 { }; -// Haupt-State-Struct +// Main state struct struct MYCONTRACT : public ContractBase { // ... State Members, Procedures, Functions ... }; ``` -### Registrierung in `contract_def.h` (3 Stellen!) +### Registration in `contract_def.h` (3 locations!) -**Stelle 1** – Contract Index & Include: +**Location 1** – Contract Index & Include: ```cpp #undef CONTRACT_INDEX #undef CONTRACT_STATE_TYPE @@ -50,65 +50,65 @@ struct MYCONTRACT : public ContractBase #define MYCONTRACT_CONTRACT_INDEX 20 #define CONTRACT_INDEX MYCONTRACT_CONTRACT_INDEX #define CONTRACT_STATE_TYPE MYCONTRACT -#define CONTRACT_STATE2_TYPE MYCONTRACT2 // ← MUSS existieren! +#define CONTRACT_STATE2_TYPE MYCONTRACT2 // ← MUST exist! #include "contracts/MyContract.h" ``` -**Stelle 2** – Contract Description: +**Location 2** – Contract Description: ```cpp {"MYCON", 197, 10000, sizeof(MYCONTRACT)}, // Format: {"ASSET_NAME", CONSTRUCTION_EPOCH, DESTRUCTION_EPOCH, sizeof(STATE)} -// Asset-Name: max 7 Zeichen, erster Buchstabe A-Z, danach A-Z oder 0-9 +// Asset name: max 7 chars, first char A-Z, then A-Z or 0-9 ``` -**Stelle 3** – Registrierung: +**Location 3** – Registration: ```cpp REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(MYCONTRACT); ``` --- -## 2. Verbotene C++ Features (Crashverursacher!) - -### Absolut verboten – kein einziges Vorkommen erlaubt - -| Verboten | Grund | Alternative | -|----------|-------|-------------| -| `*` (Pointer) | Sicherheitsrisiko | `*` nur für Multiplikation erlaubt | -| `[` und `]` | Low-Level Arrays ohne Bounds-Check | `Array` aus QPI verwenden | -| `#` (Preprocessor) | `#include`, `#define`, `#ifdef` etc. | Keine – nur `constexpr` für Konstanten | -| `"string"` | Sprung zu zufälligem Speicher | `STATIC_ASSERT` Macro statt `static_assert("msg")` | -| `'c'` (Char-Literal) | Wie Strings | Nicht verwenden | -| `float` / `double` | Nicht deterministische Arithmetik | `uint64`, `sint64`, `uint128` | -| `/` (Division) | Inkonsistentes Verhalten bei /0 | `div(a, b)` – gibt 0 bei /0 | -| `%` (Modulo) | Inkonsistentes Verhalten bei %0 | `mod(a, b)` – gibt 0 bei %0 | -| `...` (Variadic) | Verboten | Keine | -| `__` (Doppel-Underscore) | Compiler-Interna | Einfachen Underscore verwenden | -| `union` | Täuschung bei Code-Audit | `struct` verwenden | -| `typedef` (global) | Nur lokal erlaubt | `using` nur in Structs/Funktionen | -| `const_cast` | Sicherheitsrisiko | Nicht verwenden | -| `QpiContext` | Interne Klasse | Nicht referenzieren | -| `::` (Scope Resolution) | Nur für Structs/Enums aus Contract & qpi.h | Kein Zugriff auf fremde Namespaces | - -### Einzige erlaubte Ausnahme bei `using`: +## 2. Forbidden C++ Features (Crash Causes!) + +### Absolutely forbidden – not a single occurrence allowed + +| Forbidden | Reason | Alternative | +|-----------|--------|-------------| +| `*` (Pointer) | Security risk | `*` only allowed for multiplication | +| `[` and `]` | Low-level arrays without bounds check | Use `Array` from QPI | +| `#` (Preprocessor) | `#include`, `#define`, `#ifdef` etc. | None – use `constexpr` for constants | +| `"string"` | Jump to random memory | Use `STATIC_ASSERT` macro instead of `static_assert("msg")` | +| `'c'` (Char literal) | Same as strings | Do not use | +| `float` / `double` | Non-deterministic arithmetic | `uint64`, `sint64`, `uint128` | +| `/` (Division) | Inconsistent behavior on /0 | `div(a, b)` – returns 0 on /0 | +| `%` (Modulo) | Inconsistent behavior on %0 | `mod(a, b)` – returns 0 on %0 | +| `...` (Variadic) | Forbidden | None | +| `__` (Double underscore) | Compiler internals | Use single underscore | +| `union` | Deceptive for code audit | Use `struct` | +| `typedef` (global) | Only allowed locally | `using` only inside structs/functions | +| `const_cast` | Security risk | Do not use | +| `QpiContext` | Internal class | Do not reference | +| `::` (Scope resolution) | Only for structs/enums from contract & qpi.h | No access to foreign namespaces | + +### Only allowed exception for `using`: ```cpp -using namespace QPI; // ← OK am Dateianfang +using namespace QPI; // ← OK at top of file ``` --- -## 3. Variablen-Regeln +## 3. Variable Rules -### ❌ VERBOTEN: Lokale Variablen auf dem Stack +### ❌ FORBIDDEN: Local variables on the stack ```cpp PUBLIC_PROCEDURE(Bad) { - uint64 counter = 0; // ❌ VERBOTEN - Stack-Variable - for (uint64 i = 0; ...) // ❌ VERBOTEN - Loop-Variable auf Stack + uint64 counter = 0; // ❌ FORBIDDEN - stack variable + for (uint64 i = 0; ...) // ❌ FORBIDDEN - loop variable on stack } ``` -### ✅ RICHTIG: Alles in `_locals` Struct +### ✅ CORRECT: Everything in `_locals` struct ```cpp struct MyProc_locals { @@ -125,170 +125,170 @@ PUBLIC_PROCEDURE_WITH_LOCALS(MyProc) } ``` -### Verfügbare Variablen in Procedures/Functions +### Available Variables in Procedures/Functions -| Variable | Verfügbar in | Beschreibung | -|----------|-------------|--------------| -| `state` | Procedures: read/write, Functions: read-only | Contract State | -| `input` | Beide | Eingabedaten | -| `output` | Beide | Ausgabedaten (mit 0 initialisiert) | -| `locals` | `_WITH_LOCALS` Varianten | Lokale Variablen (mit 0 initialisiert) | -| `qpi` | Beide | QPI-Funktionen (`qpi.transfer()`, `qpi.tick()`, etc.) | +| Variable | Available in | Description | +|----------|-------------|-------------| +| `state` | Procedures: read/write, Functions: read-only | Contract state | +| `input` | Both | Input data | +| `output` | Both | Output data (zero-initialized) | +| `locals` | `_WITH_LOCALS` variants | Local variables (zero-initialized) | +| `qpi` | Both | QPI functions (`qpi.transfer()`, `qpi.tick()`, etc.) | --- -## 4. ID() Macro – Adressformat +## 4. ID() Macro – Address Format -### ❌ FALSCH: 60 Zeichen (mit Checksum) +### ❌ WRONG: 60 characters (with checksum) ``` JHUIZPGZNMTHPCZBAMSZQGBJCAAOGDAQVFHWYKLEGGRM JEJSXTREUVCPYHS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ -56 Zeichen Basis-Adresse 4 Checksum +56 characters base address 4 checksum ``` -### ✅ RICHTIG: Genau 56 Zeichen (ohne Checksum) +### ✅ CORRECT: Exactly 56 characters (without checksum) ```cpp state.mAdminAddress = ID( _J, _H, _U, _I, _Z, _P, _G, _Z, _N, _M, _T, _H, _P, _C, _Z, _I, _B, _A, _M, _S, _Z, _Q, _G, _B, _J, _C, _O, _A, _O, _G, _D, _A, _Q, _V, _F, _H, _W, _Y, _K, _L, _E, _G, _G, _R, _M, _J, _E, _J, - _S, _X, _T, _R, _E, _U, _V, _C // ← 56 Parameter, KEINE Checksum + _S, _X, _T, _R, _E, _U, _V, _C // ← 56 parameters, NO checksum ); ``` -**Tipp:** Qubic-Adressen sind immer 60 Zeichen lang (56 Basis + 4 Checksum). Im Code die **letzten 4 Zeichen abschneiden**. +**Tip:** Qubic addresses are always 60 characters long (56 base + 4 checksum). In code, **strip the last 4 characters**. --- -## 5. State-Größenlimit +## 5. State Size Limit -**Maximum: 1 GB (1.073.741.824 Bytes)** +**Maximum: 1 GB (1,073,741,824 bytes)** -### HashMap Speicherverbrauch berechnen +### Calculating HashMap Memory Usage ``` HashMap ≈ L × (sizeof(KeyT) + sizeof(ValueT)) + L/32 × 8 + 16 ``` -| Beispiel | Größe | -|----------|-------| +| Example | Size | +|---------|------| | `HashMap` | ~80.5 MB | | `HashMap` | ~40.3 MB | | `HashMap` | ~2.5 MB | -### qRWA State-Berechnung (aktuell) +### qRWA State Calculation (current) -| Komponente | Anzahl | Größe | -|-----------|--------|-------| +| Component | Count | Size | +|-----------|-------|------| | HashMap | 5× | ~403 MB | | HashMap | 2× | ~161 MB | -| Sonstige (Arrays, Skalare) | - | ~0.1 MB | -| **Gesamt** | | **~564 MB (55% vom Limit)** | +| Other (Arrays, scalars) | - | ~0.1 MB | +| **Total** | | **~564 MB (55% of limit)** | --- -## 6. Procedure & Function Typen +## 6. Procedure & Function Types -### System Procedures (automatisch vom Core aufgerufen) +### System Procedures (called automatically by the core) -| Macro | Wann | Zweck | -|-------|------|-------| -| `INITIALIZE()` | Einmal nach IPO | State initialisieren | -| `BEGIN_EPOCH()` | Jede Epoch | Snapshots, Resets | -| `END_EPOCH()` | Jede Epoch | Voting auswerten, Daten kopieren | -| `BEGIN_TICK()` | Jeder Tick | Vor Transaktionen | -| `END_TICK()` | Jeder Tick | **Vorsicht: läuft JEDEN Tick!** | -| `POST_INCOMING_TRANSFER()` | Bei QU-Eingang | Revenue-Tracking | -| `PRE_ACQUIRE_SHARES()` | Asset-Transfer | Erlaubnis prüfen | -| `POST_ACQUIRE_SHARES()` | Nach Asset-Transfer | Buchführung | +| Macro | When | Purpose | +|-------|------|--------| +| `INITIALIZE()` | Once after IPO | Initialize state | +| `BEGIN_EPOCH()` | Every epoch | Snapshots, resets | +| `END_EPOCH()` | Every epoch | Evaluate voting, copy data | +| `BEGIN_TICK()` | Every tick | Before transactions | +| `END_TICK()` | Every tick | **Caution: runs EVERY tick!** | +| `POST_INCOMING_TRANSFER()` | On QU receipt | Revenue tracking | +| `PRE_ACQUIRE_SHARES()` | Asset transfer | Permission check | +| `POST_ACQUIRE_SHARES()` | After asset transfer | Bookkeeping | -### User Procedures (durch Transaktionen aufgerufen) +### User Procedures (invoked by transactions) ```cpp struct MyProc_input { uint64 value; }; struct MyProc_output { uint64 status; }; -PUBLIC_PROCEDURE(MyProc) { /* kann state ändern */ } -PRIVATE_PROCEDURE(MyProc) { /* nicht von anderen Contracts aufrufbar */ } +PUBLIC_PROCEDURE(MyProc) { /* can modify state */ } +PRIVATE_PROCEDURE(MyProc) { /* not callable by other contracts */ } ``` -### User Functions (read-only Abfragen) +### User Functions (read-only queries) ```cpp struct MyFunc_input {}; struct MyFunc_output { uint64 result; }; -PUBLIC_FUNCTION(MyFunc) { /* state ist const, nur lesen */ } +PUBLIC_FUNCTION(MyFunc) { /* state is const, read-only */ } ``` -### Input/Output Struct Regeln +### Input/Output Struct Rules -Erlaubte Typen in `_input` und `_output`: +Allowed types in `_input` and `_output`: - `uint8`, `uint16`, `uint32`, `uint64`, `sint8`, `sint16`, `sint32`, `sint64` - `bit`, `id` - `Array`, `BitArray` -- Eigene Structs die nur erlaubte Typen enthalten +- Custom structs containing only allowed types -**Verboten** in Input/Output: `Collection`, `HashMap`, `HashSet` (inkonsistenter interner State) +**Forbidden** in Input/Output: `Collection`, `HashMap`, `HashSet` (inconsistent internal state) --- -## 7. Verfügbare Container-Typen +## 7. Available Container Types -| Typ | Beschreibung | Hinweis | -|-----|-------------|---------| -| `Array` | Feste Größe, L muss 2^N sein | Bounds-geprüft | -| `BitArray` | Bit-Array, L muss 2^N sein | Min. 8 Bytes | -| `HashMap` | Key-Value Store | `cleanup()` am Epochenende! | -| `HashSet` | Nur Keys | `cleanup()` am Epochenende! | -| `Collection` | Priority Queues pro ID | `cleanup()` am Epochenende! | +| Type | Description | Note | +|------|------------|------| +| `Array` | Fixed size, L must be 2^N | Bounds-checked | +| `BitArray` | Bit array, L must be 2^N | Min. 8 bytes | +| `HashMap` | Key-value store | `cleanup()` at epoch end! | +| `HashSet` | Keys only | `cleanup()` at epoch end! | +| `Collection` | Priority queues per ID | `cleanup()` at epoch end! | -**Wichtig:** Nach Entfernen von Elementen aus HashMap/HashSet/Collection → `cleanupIfNeeded()` oder `cleanup()` am `END_EPOCH` aufrufen! +**Important:** After removing elements from HashMap/HashSet/Collection → call `cleanupIfNeeded()` or `cleanup()` at `END_EPOCH`! --- -## 8. QPI Wichtige Funktionen +## 8. QPI Important Functions ```cpp -// Informationen -qpi.tick() // Aktueller Tick -qpi.epoch() // Aktuelle Epoch -qpi.now() // Aktuelle Zeit (DateAndTime) -qpi.invocator() // Wer hat aufgerufen (User-ID oder Contract-ID) -qpi.invocationReward() // Wie viel QU wurde gesendet -qpi.originator() // Ursprünglicher Transaktions-Ersteller +// Information +qpi.tick() // Current tick +qpi.epoch() // Current epoch +qpi.now() // Current time (DateAndTime) +qpi.invocator() // Who called (user ID or contract ID) +qpi.invocationReward() // How much QU was sent +qpi.originator() // Original transaction creator // Transfers -qpi.transfer(dest, amount) // QU senden -qpi.burn(amount) // QU verbrennen (füllt Fee-Reserve) +qpi.transfer(dest, amount) // Send QU +qpi.burn(amount) // Burn QU (fills fee reserve) // Assets qpi.issueAsset(name, issuer, numberOfShares, unitOfMeasurement, ...) qpi.numberOfShares(asset, ownershipSelect, possessionSelect) qpi.transferShareOwnershipAndPossession(name, issuer, owner, possessor, amount, newOwnerAndPossessor) qpi.releaseShares(asset, owner, possessor, amount, destOwnership, destPossession, fee) -qpi.distributeDividends(amountPerShare) // Dividende an Contract-Shareholder +qpi.distributeDividends(amountPerShare) // Dividend to contract shareholders -// Mathematik (statt / und %) -div(a, b) // Division (gibt 0 bei b==0) -mod(a, b) // Modulo (gibt 0 bei b==0) -sadd(a, b) // Saturating Add (kein Overflow) -smul(a, b) // Saturating Multiply +// Math (instead of / and %) +div(a, b) // Division (returns 0 on b==0) +mod(a, b) // Modulo (returns 0 on b==0) +sadd(a, b) // Saturating add (no overflow) +smul(a, b) // Saturating multiply ``` --- -## 9. Verifikations-Tool +## 9. Verification Tool -### Automatisch in GitHub Actions (für PRs zu develop/main): -Das [Qubic Contract Verification Tool](https://github.com/Franziska-Mueller/qubic-contract-verify) prüft automatisch alle Regeln. +### Automatic in GitHub Actions (for PRs to develop/main): +The [Qubic Contract Verification Tool](https://github.com/Franziska-Mueller/qubic-contract-verify) automatically checks all rules. -### Manuell ausführen: +### Run manually: ```bash -# Als GitHub Action in eigenem Repo: +# As a GitHub Action in your own repo: - uses: Franziska-Mueller/qubic-contract-verify@v1.0.4 with: filepaths: 'src/contracts/qRWA.h' -# Oder lokal bauen: +# Or build locally: git clone https://github.com/Franziska-Mueller/qubic-contract-verify cd qubic-contract-verify cd deps/CppParser && mkdir builds && cd builds && cmake .. && make && cd ../../.. @@ -298,44 +298,248 @@ mkdir build && cd build && cmake .. && make --- -## 10. Test-Checkliste vor Deployment - -- [ ] `struct XXXX2 {}` vorhanden (für `CONTRACT_STATE2_TYPE`) -- [ ] Keine verbotenen C++ Features (siehe Abschnitt 2) -- [ ] Alle Variablen in `_locals` Structs (keine Stack-Variablen) -- [ ] `ID()` Macro: genau 56 Zeichen pro Adresse -- [ ] State-Größe < 1 GB -- [ ] `REGISTER_USER_FUNCTIONS_AND_PROCEDURES()` implementiert -- [ ] Registrierung in `contract_def.h` an allen 3 Stellen -- [ ] `div<>()` und `mod()` statt `/` und `%` -- [ ] Keine `#include` im finalen Code -- [ ] Input/Output Structs nur erlaubte Typen -- [ ] HashMap/HashSet `cleanup()` am Epochenende -- [ ] Globale Konstanten mit Contract-Name prefixed (`QRWA_...`) -- [ ] Tests in GoogleTest Framework geschrieben -- [ ] Kompiliert ohne Warnings -- [ ] Contract Verification Tool bestanden -- [ ] Testnet mit mehreren Nodes stabil +## 10. Pre-Deployment Checklist + +- [ ] `struct XXXX2 {}` present (for `CONTRACT_STATE2_TYPE`) +- [ ] No forbidden C++ features (see section 2) +- [ ] All variables in `_locals` structs (no stack variables) +- [ ] `ID()` macro: exactly 56 characters per address +- [ ] State size < 1 GB +- [ ] `REGISTER_USER_FUNCTIONS_AND_PROCEDURES()` implemented +- [ ] Registration in `contract_def.h` at all 3 locations +- [ ] `div<>()` and `mod()` instead of `/` and `%` +- [ ] No `#include` in final code +- [ ] Input/Output structs use only allowed types +- [ ] HashMap/HashSet `cleanup()` at epoch end +- [ ] Global constants prefixed with contract name (`QRWA_...`) +- [ ] Tests written in GoogleTest framework +- [ ] Compiles without warnings +- [ ] Contract verification tool passed +- [ ] Testnet stable with multiple nodes --- -## 11. qRWA-spezifische Notizen +## 11. qRWA-Specific Notes -### Aktuelle Test-Konfiguration +### Current Test Configuration - Branch: `qrwa-dedicated-pool` - Contract Index: 20 - Construction Epoch: 197 -- Payout-Trigger: `qpi.tick() >= 44602200` (Test-Modus) -- 6 Governance-Adressen in `INITIALIZE()` (alle 56 Chars) +- Payout trigger: `qpi.tick() >= 44602200` (test mode) +- 6 governance addresses in `INITIALIZE()` (all 56 chars) -### Bekannte Fixes (bereits angewendet) -1. **`struct QRWA2 {}`** hinzugefügt – fehlte komplett, crashte den Node -2. **Adressen auf 56 Zeichen** getrimmt – `ID()` Macro erwartet exakt 56 Parameter +### Known Fixes (already applied) +1. **`struct QRWA2 {}`** added – was completely missing, crashed the node +2. **Addresses trimmed to 56 characters** – `ID()` macro expects exactly 56 parameters -### Für Production zurücksetzen +### Reset for Production ```cpp -// In END_TICK: Test-Bedingungen ersetzen durch: +// In END_TICK: replace test conditions with: if (qpi.dayOfWeek(...) == QRWA_PAYOUT_DAY && locals.now.getHour() == QRWA_PAYOUT_HOUR) -// und: +// and: if (locals.msSinceLastPayout >= QRWA_MIN_PAYOUT_INTERVAL_MS) ``` + +--- + +## 12. Pool System & Revenue Distribution + +### Overview: 3 Revenue Inputs → 6 Internal Pools → 3 Recipient Groups + +``` + ┌─────────────────────────┐ + QUTIL Transfer ──►│ Revenue Pool A │──► Gov Fees (50%) ──► Electricity (35%) + (SC source) │ mRevenuePoolA │ Maintenance (5%) + └────────────┬────────────┘ Reinvestment (10%) + │ Remainder (50%) + ▼ + ┌─────────────────────────┐ + User Transfer ───►│ Revenue Pool B │ + (wallet source) │ mRevenuePoolB │ + └────────────┬────────────┘ + │ + Pool A (after fees) + Pool B = totalDistribution + │ + ┌────────────┴────────────┐ + │ │ + ▼ 90% ▼ 10% + ┌───────────────┐ ┌───────────────┐ + │ QMINE Div Pool│ │ qRWA Div Pool │ + │ mQmineDividend│ │ mQRWADividend │ + │ Pool │ │ Pool │ + └───────┬───────┘ └───────┬───────┘ + │ │ + ▼ ▼ + QMINE Holders qRWA Holders + (Epoch Snapshot) (Live, ≥100K QMINE/Share) + + ┌─────────────────────────┐ + Dedicated Addr ──►│ Dedicated Revenue Pool │ + (configured) │ mDedicatedRevenuePool │ + └────────────┬────────────┘ + │ + ┌────────────┴────────────┐ + │ │ + ▼ 90% ▼ 10% + ┌───────────────┐ ┌───────────────┐ + │ → QMINE Div │ │ Dedicated │ + │ Pool (above)│ │ qRWA Div Pool │ + └───────────────┘ │ mDedicatedQRWA│ + │ DividendPool │ + └───────┬───────┘ + │ + ▼ + qRWA Holders + (Live, ≥100K QMINE/Share) +``` + +### Revenue Inputs (POST_INCOMING_TRANSFER) + +Every incoming QU transfer is routed to one of three revenue pools based on `sourceId`: + +| Source | Condition | Target Pool | Gov Fees? | +|--------|-----------|-------------|-----------| +| QUTIL Contract | `sourceId == id(QUTIL_CONTRACT_INDEX)` | **Pool A** (`mRevenuePoolA`) | ✅ Yes (50%) | +| Dedicated Address | `sourceId == mDedicatedRevenueAddress` | **Dedicated** (`mDedicatedRevenuePool`) | ❌ No | +| Everything else (User/SC) | `sourceId != NULL_ID` | **Pool B** (`mRevenuePoolB`) | ❌ No | + +**Important:** Direct `sendtoaddress` transfers from a wallet **always go to Pool B** — for Pool A the transfer must go through QUTIL (e.g., `SendToManyV1`). **Exception:** Transfers from the Dedicated Revenue Address (`PDQTKKIRSIGAGAOLJWZWTCBSFCYAIZIRYCHEBKHBJHHBJNJHWLYGXSVEQEFC`) go to the **Dedicated Pool** (no Gov Fees). + +### Governance Fees (Pool A only) + +Before the 90/10 split, governance fees are deducted from Pool A: + +``` +Gov Fee Total = electricityPercent + maintenancePercent + reinvestmentPercent +``` + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `electricityPercent` | 350‰ (35%) | Mining farm electricity costs | +| `maintenancePercent` | 50‰ (5%) | Maintenance & operations | +| `reinvestmentPercent` | 100‰ (10%) | Hardware reinvestment | +| **Total** | **500‰ (50%)** | **Maximum 1000‰ (100%)** | + +These percentages are changeable via governance voting by QMINE holders. Each percentage is applied individually to `mRevenuePoolA`: + +```cpp +electricityPayout = (mRevenuePoolA × electricityPercent) / 1000 +maintenancePayout = (mRevenuePoolA × maintenancePercent) / 1000 +reinvestmentPayout = (mRevenuePoolA × reinvestmentPercent) / 1000 +mRevenuePoolA -= (electricityPayout + maintenancePayout + reinvestmentPayout) +``` + +### 90/10 Split (QMINE vs. qRWA) + +After deducting gov fees, Pool A (remainder) + Pool B are combined: + +``` +totalDistribution = remainingPoolA + poolB + +QMINE share = totalDistribution × 900 / 1000 (90%) +qRWA share = totalDistribution - QMINE share (10%, rounding remainder goes here) +``` + +| Constant | Value | Meaning | +|----------|-------|---------| +| `QRWA_QMINE_HOLDER_PERCENT` | 900 | 90% for QMINE holders | +| `QRWA_QRWA_HOLDER_PERCENT` | 100 | 10% for qRWA holders | +| `QRWA_PERCENT_DENOMINATOR` | 1000 | Base (= 100%) | + +The Dedicated Pool is split identically: 90% → QMINE Div Pool, 10% → **separate** Dedicated qRWA Div Pool. + +### QMINE Holder Distribution + +**Data source:** Epoch snapshots (NOT live) + +``` +BEGIN_EPOCH: Snapshot of all QMINE holders → mBeginEpochBalances +END_EPOCH: Snapshot of all QMINE holders → mEndEpochBalances + Copy to mPayoutBeginBalances / mPayoutEndBalances +END_TICK: Distribution from mPayoutBeginBalances +``` + +**Eligibility:** +- `eligibleBalance = min(beginBalance, endBalance)` — holders who sold QMINE during the epoch only receive based on the lower balance +- Prevents "epoch hopping" (buy → snapshot → sell) + +**Per-holder calculation:** +``` +payout = (eligibleBalance × qmineDividendPool) / payoutTotalQmineBegin +``` + +**Important — First Epoch:** `mPayoutTotalQmineBegin` is 0 until the first `END_EPOCH` has run. QMINE holders **only receive payouts starting from the 2nd epoch**. The QMINE share accumulates in `mQmineDividendPool` and is distributed in one lump sum after the epoch transition. + +**QMINE Dev (Remainder):** After distributing to all eligible holders, the `qmineDevAddress` receives the entire remaining balance of the pool. This captures rounding differences and the shares of holders who lost QMINE between begin/end epoch. + +### qRWA Holder Distribution + +**Data source:** Live `AssetPossessionIterator` (works immediately from epoch 1) + +**Eligibility:** Each qRWA holder must hold ≥100,000 QMINE per qRWA share: +``` +requiredQmine = qrwaShares × 100,000 +eligible = (actualQmine ≥ requiredQmine) +``` + +| qRWA Shares | Required QMINE | +|-------------|----------------| +| 1 | 100,000 | +| 3 | 300,000 | +| 10 | 1,000,000 | +| 50 | 5,000,000 | + +**Calculation (2-pass method):** + +1. **Pass 1:** Count all eligible shares → `eligibleShares` +2. **Pass 2:** Distribute proportionally: +``` +amountPerShare = qRWADividendPool / eligibleShares +payout = amountPerShare × holderShares +``` + +**Ineligible holders** are completely skipped — their share is automatically distributed proportionally to the qualified holders. + +**Rounding remainder:** At most `eligibleShares - 1` QU remain in the pool and are distributed with the next payout. + +**Dedicated qRWA Pool:** Identical logic, but from the separate `mDedicatedQRWADividendPool` — distributed independently from the regular qRWA pool. + +### Payout Timing + +| Mode | Trigger | Configuration | +|------|---------|---------------| +| **Testing** | Every 20 ticks | `QRWA_PAYOUT_TICK_INTERVAL = 20` | +| **Production** | Friday 12:00 UTC, min. 6 days apart | `QRWA_PAYOUT_DAY`, `QRWA_PAYOUT_HOUR`, `QRWA_MIN_PAYOUT_INTERVAL_MS` | + +### Query Functions + +| Function | Index | Output | Description | +|----------|-------|--------|-------------| +| `GetDividendBalances` | 5 | `{ revenuePoolA, revenuePoolB, qmineDividendPool, qrwaDividendPool, dedicatedRevenuePool, dedicatedQRWADividendPool }` | Current pool balances (incl. dedicated) | +| `GetTotalDistributed` | 6 | `{ totalQmineDistributed, totalQRWADistributed }` | Cumulative distributions | +| `GetGovParams` | 1 | `{ admin, electricity, maintenance, reinvestment, qmineDev, elecPct, maintPct, reinvPct }` | Gov addresses & percentages | + +### Worked Example + +Incoming: 10,000,000 QU via QUTIL → Pool A + +``` +1) Gov Fees (from Pool A): + Electricity: 10M × 350/1000 = 3,500,000 QU + Maintenance: 10M × 50/1000 = 500,000 QU + Reinvestment: 10M × 100/1000 = 1,000,000 QU + → Gov Total: 5,000,000 QU + +2) Remaining Pool A: 10M - 5M = 5,000,000 QU + Pool B: 0 (no user transfer) + → totalDistribution = 5,000,000 QU + +3) 90/10 Split: + QMINE Div Pool: 5M × 900/1000 = 4,500,000 QU + qRWA Div Pool: 5M - 4.5M = 500,000 QU + +4) qRWA Distribution (example: 50 eligible shares out of 676 total): + amountPerShare = 500,000 / 50 = 10,000 QU/share + Holder with 3 shares: 3 × 10,000 = 30,000 QU + Holder with 1 share: 1 × 10,000 = 10,000 QU +``` diff --git a/src/contracts/qRWA.h b/src/contracts/qRWA.h index 5d04a70e7..90ff81701 100644 --- a/src/contracts/qRWA.h +++ b/src/contracts/qRWA.h @@ -959,6 +959,8 @@ struct QRWA : public ContractBase uint64 revenuePoolB; uint64 qmineDividendPool; uint64 qrwaDividendPool; + uint64 dedicatedRevenuePool; + uint64 dedicatedQRWADividendPool; }; PUBLIC_FUNCTION(GetDividendBalances) { @@ -966,6 +968,8 @@ struct QRWA : public ContractBase output.revenuePoolB = state.mRevenuePoolB; output.qmineDividendPool = state.mQmineDividendPool; output.qrwaDividendPool = state.mQRWADividendPool; + output.dedicatedRevenuePool = state.mDedicatedRevenuePool; + output.dedicatedQRWADividendPool = state.mDedicatedQRWADividendPool; } struct GetTotalDistributed_input {}; diff --git a/test_qrwa.sh b/test_qrwa.sh new file mode 100755 index 000000000..5ef6262a7 --- /dev/null +++ b/test_qrwa.sh @@ -0,0 +1,1447 @@ +#!/bin/bash +# +# qRWA Smart Contract - Automated Test Suite +# ============================================ +# Testet alle Funktionen und Procedures des qRWA Contracts (Index 20). +# +# Contract Identity: UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQEE +# (publicKey = { 20, 0, 0, 0 } → base26 + K12 checksum) +# +# Nutzung: +# chmod +x test_qrwa.sh +# ./test_qrwa.sh # Alle Tests +# ./test_qrwa.sh --readonly # Nur Read-Only Functions +# ./test_qrwa.sh --send # Nur sende Tests (QU → Contract) +# ./test_qrwa.sh --vote # Nur Governance Vote +# ./test_qrwa.sh --payout # Payout-Zyklus, 90/10 Split, Gov Fees +# + +set -uo pipefail + +############################################### +# KONFIGURATION +############################################### + +NODE_IP="135.181.160.185" +NODE_PORT="31841" + +# Seeds (55 Zeichen) +SEED1="gtfgjhtoxcddbxrydatevcmildkmqeiezwgztpwseihqhqxmoamxfak" +SEED2="ytcltfdvfjvskmarrjxloxkjrwtbjbepzjphowjfszldyjscrmztmor" +SEED3="uvvpotkojamyifpzgklgwnrxudclcjoqcmsywcbqarujokczzqxykml" # Dedicated Revenue Address + +CLI="../qubic-cli/build/qubic-cli" + +CONTRACT_INDEX=20 +QRWA_IDENTITY="UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQEE" +DEDICATED_IDENTITY="PDQTKKIRSIGAGAOLJWZWTCBSFCYAIZIRYCHEBKHBJHHBJNJHWLYGXSVEQEFC" + +# QMINE Asset +QMINE_ISSUER="QMINEQQXYBEGBHNSUPOUYDIQKZPCBPQIIHUUZMCPLBPCCAIARVZBTYKGFCWM" +QMINE_NAME="QMINE" + +SCHEDULE_TICK=20 +SEND_AMOUNT=5000000 # 5M QU für Pool B Test +DEDICATED_AMOUNT=3000000 # 3M QU für Dedicated Pool Test +TX_WAIT_SEC=45 # Wartezeit für TX-Bestätigung + +############################################### +# FARBEN & ZAEHLER +############################################### +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +PASS=0 +FAIL=0 +SKIP=0 +TOTAL=0 +declare -a RESULTS=() + +############################################### +# HILFSFUNKTIONEN +############################################### + +header() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BOLD}${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +step() { + echo -e " ${CYAN}▶ $1${NC}" +} + +record_pass() { + PASS=$((PASS+1)) + TOTAL=$((TOTAL+1)) + RESULTS+=("${GREEN}PASS${NC} | $1") + echo -e " ${GREEN}✓ PASS${NC} $1" +} + +record_fail() { + FAIL=$((FAIL+1)) + TOTAL=$((TOTAL+1)) + RESULTS+=("${RED}FAIL${NC} | $1 — $2") + echo -e " ${RED}✗ FAIL${NC} $1 — $2" +} + +record_skip() { + SKIP=$((SKIP+1)) + TOTAL=$((TOTAL+1)) + RESULTS+=("${YELLOW}SKIP${NC} | $1 — $2") + echo -e " ${YELLOW}⊘ SKIP${NC} $1 — $2" +} + +# Rufe CLI auf und gib Output zurück, ohne automatisches Exit bei Fehler +cli_call() { + "$CLI" -nodeip "$NODE_IP" -nodeport "$NODE_PORT" "$@" 2>&1 +} + +cli_call_seed() { + local seed="$1"; shift + "$CLI" -nodeip "$NODE_IP" -nodeport "$NODE_PORT" -seed "$seed" -scheduletick "$SCHEDULE_TICK" "$@" 2>&1 +} + +# Contract Function aufrufen (read-only, mit Retry) +call_fn() { + local fn_id="$1" input="$2" output="$3" + local result + for _retry in 1 2 3; do + result=$(cli_call -enabletestcontracts -callcontractfunction "$CONTRACT_INDEX" "$fn_id" "$input" "$output") + if echo "$result" | grep -q "Contract Function Output"; then + echo "$result" + return 0 + fi + sleep 2 + done + echo "$result" +} + +# Extrahiere Zahlen aus Contract-Function-Output (Zeilen mit nur Zahlen) +extract_numbers() { + echo "$1" | grep -oE '[0-9]+' | tail -n +1 +} + +# Warte auf TX-Bestätigung mit Retry (wartet bis Tick erreicht) +wait_and_check_tx() { + local tick="$1" tx_hash="$2" + local max_attempts=8 + local result + for attempt in $(seq 1 $max_attempts); do + sleep "$TX_WAIT_SEC" + result=$(cli_call -checktxontick "$tick" "$tx_hash") + # Wenn "Please wait" → Tick noch nicht erreicht, retry + if echo "$result" | grep -q "Please wait"; then + local cur_tick=$(echo "$result" | grep -oE 'current tick [0-9]+' | grep -oE '[0-9]+') + echo -e " [${attempt}/${max_attempts}] Tick noch nicht erreicht (aktuell: ${cur_tick:-?})" >&2 + continue + fi + echo "$result" + return 0 + done + echo "$result" + return 1 +} + +############################################### +# MODUS AUSWAHL +############################################### +MODE="${1:-all}" + +############################################### +# VORAUSSETZUNGEN +############################################### + +header "VORAUSSETZUNGEN" + +# CLI prüfen +if [[ ! -x "$CLI" ]]; then + echo -e " ${RED}✗ qubic-cli nicht gefunden: $CLI${NC}" + echo " → cd ../qubic-cli && mkdir -p build && cd build && cmake .. && make" + exit 1 +fi +echo -e " ${GREEN}✓${NC} qubic-cli: $CLI" + +# Identities ermitteln +IDENTITY1=$(cli_call -seed "$SEED1" -showkeys | grep "Identity:" | awk '{print $2}') +IDENTITY2=$(cli_call -seed "$SEED2" -showkeys | grep "Identity:" | awk '{print $2}') + +if [[ -z "$IDENTITY1" || -z "$IDENTITY2" ]]; then + echo -e " ${RED}✗ Konnte Identities nicht ermitteln!${NC}" + exit 1 +fi +echo -e " ${GREEN}✓${NC} Seed 1 Identity: $IDENTITY1" +echo -e " ${GREEN}✓${NC} Seed 2 Identity: $IDENTITY2" + +# Node prüfen +TICK_INFO=$(cli_call -getcurrenttick) +CURRENT_TICK=$(echo "$TICK_INFO" | grep "Tick:" | awk '{print $2}') +CURRENT_EPOCH=$(echo "$TICK_INFO" | grep "Epoch:" | awk '{print $2}') + +if [[ -z "$CURRENT_TICK" ]]; then + echo -e " ${RED}✗ Node nicht erreichbar: $NODE_IP:$NODE_PORT${NC}" + exit 1 +fi +echo -e " ${GREEN}✓${NC} Node: $NODE_IP:$NODE_PORT | Tick: $CURRENT_TICK | Epoch: $CURRENT_EPOCH" +echo -e " ${GREEN}✓${NC} Contract: Index $CONTRACT_INDEX = $QRWA_IDENTITY" +echo "" + +# Balances prüfen +BAL1=$(cli_call -getbalance "$IDENTITY1" | grep "Balance:" | head -1) +BAL2=$(cli_call -getbalance "$IDENTITY2" | grep "Balance:" | head -1) +echo -e " Seed 1 $BAL1" +echo -e " Seed 2 $BAL2" + +############################################### +# SNAPSHOT: qRWA + QMINE Holder Balances (VOR Tests) +############################################### + +header "SNAPSHOT: qRWA + QMINE Holder Balances (Anfang)" +step "Lese qRWA-Owner von der Blockchain..." + +SNAP_QRWA_RAW=$(cli_call -queryassets ownerships "name=QRWA") + +declare -a SNAP_QRWA_IDS=() +declare -a SNAP_QRWA_SHARES=() +SNAP_QRWA_OWNER="" + +while IFS= read -r line; do + if echo "$line" | grep -q 'owner = '; then + SNAP_QRWA_OWNER=$(echo "$line" | sed 's/.*owner = //' | grep -oE '[A-Z]{50,60}') + fi + if echo "$line" | grep -q 'number of shares = '; then + SNAP_SHARES=$(echo "$line" | sed 's/.*number of shares = //' | grep -oE '[0-9]+') + if [[ -n "$SNAP_QRWA_OWNER" && -n "$SNAP_SHARES" && "$SNAP_SHARES" -gt 0 ]]; then + SNAP_QRWA_IDS+=("$SNAP_QRWA_OWNER") + SNAP_QRWA_SHARES+=("$SNAP_SHARES") + fi + fi +done <<< "$SNAP_QRWA_RAW" + +echo -e " ${GREEN}✓${NC} qRWA Owner: ${#SNAP_QRWA_IDS[@]}" + +step "Lese QMINE-Owner von der Blockchain..." +SNAP_QMINE_RAW=$(cli_call -queryassets ownerships "name=QMINE,issuer=${QMINE_ISSUER}") + +declare -a SNAP_QMINE_IDS=() +declare -a SNAP_QMINE_SHARES=() +SNAP_QMINE_OWNER="" + +while IFS= read -r line; do + if echo "$line" | grep -q 'owner = '; then + SNAP_QMINE_OWNER=$(echo "$line" | sed 's/.*owner = //' | grep -oE '[A-Z]{50,60}') + fi + if echo "$line" | grep -q 'number of shares = '; then + SNAP_SHARES=$(echo "$line" | sed 's/.*number of shares = //' | grep -oE '[0-9]+') + if [[ -n "$SNAP_QMINE_OWNER" && -n "$SNAP_SHARES" && "$SNAP_SHARES" -gt 0 ]]; then + SNAP_QMINE_IDS+=("$SNAP_QMINE_OWNER") + SNAP_QMINE_SHARES+=("$SNAP_SHARES") + fi + fi +done <<< "$SNAP_QMINE_RAW" + +echo -e " ${GREEN}✓${NC} QMINE Owner: ${#SNAP_QMINE_IDS[@]}" + +# Wähle max 20 Holder für Payout-Verifikation (Kombination: qRWA-Holder + QMINE-Holder) +# Priorisiere qRWA-Holder da diese den 10%-Anteil bekommen sollen +SNAP_MAX_CHECK=20 +declare -a SNAP_CHECK_IDS=() +declare -a SNAP_CHECK_BAL_BEFORE=() +declare -a SNAP_CHECK_TYPE=() # "qRWA", "QMINE", "BOTH" + +# Erst qRWA-Holder (max 10) +SNAP_QRWA_LIMIT=$((SNAP_MAX_CHECK / 2)) +if [[ ${#SNAP_QRWA_IDS[@]} -lt $SNAP_QRWA_LIMIT ]]; then + SNAP_QRWA_LIMIT=${#SNAP_QRWA_IDS[@]} +fi +for i in $(seq 0 $((SNAP_QRWA_LIMIT - 1))); do + SNAP_CHECK_IDS+=("${SNAP_QRWA_IDS[$i]}") + SNAP_CHECK_TYPE+=("qRWA:${SNAP_QRWA_SHARES[$i]}") +done + +# Dann QMINE-Holder (auffüllen bis max 20, nur wenn nicht schon in qRWA-Liste) +QMINE_IDX=0 +while [[ ${#SNAP_CHECK_IDS[@]} -lt $SNAP_MAX_CHECK && $QMINE_IDX -lt ${#SNAP_QMINE_IDS[@]} ]]; do + CANDIDATE="${SNAP_QMINE_IDS[$QMINE_IDX]}" + # Prüfe ob schon in der Liste + ALREADY=0 + for existing in "${SNAP_CHECK_IDS[@]}"; do + if [[ "$existing" == "$CANDIDATE" ]]; then + # Bereits als qRWA drin → upgraden zu BOTH + for upd_idx in $(seq 0 $((${#SNAP_CHECK_IDS[@]} - 1))); do + if [[ "${SNAP_CHECK_IDS[$upd_idx]}" == "$CANDIDATE" ]]; then + SNAP_CHECK_TYPE[$upd_idx]="BOTH:${SNAP_QRWA_SHARES[$upd_idx]}qRWA+${SNAP_QMINE_SHARES[$QMINE_IDX]}QMINE" + fi + done + ALREADY=1 + break + fi + done + if [[ "$ALREADY" -eq 0 ]]; then + SNAP_CHECK_IDS+=("$CANDIDATE") + SNAP_CHECK_TYPE+=("QMINE:${SNAP_QMINE_SHARES[$QMINE_IDX]}") + fi + QMINE_IDX=$((QMINE_IDX + 1)) +done + +step "Snapshot QU-Balances für ${#SNAP_CHECK_IDS[@]} Holder..." +for i in $(seq 0 $((${#SNAP_CHECK_IDS[@]} - 1))); do + BAL_OUT=$(cli_call -getbalance "${SNAP_CHECK_IDS[$i]}") + BAL_VAL=$(echo "$BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + BAL_VAL=${BAL_VAL:-0} + SNAP_CHECK_BAL_BEFORE+=("$BAL_VAL") + SHORT_ID="${SNAP_CHECK_IDS[$i]:0:12}…${SNAP_CHECK_IDS[$i]: -6}" + echo -e " ${SHORT_ID} [${SNAP_CHECK_TYPE[$i]}] Balance: ${BAL_VAL} QU" +done + +echo "" +echo -e " ${CYAN}Snapshot gespeichert. Vergleich folgt am Ende des Scripts.${NC}" + +############################################### +# TEST 1: READ-ONLY CONTRACT FUNCTIONS +############################################### + +if [[ "$MODE" == "all" || "$MODE" == "--readonly" ]]; then + +header "TEST 1: GetGovParams (Function 1)" +step "QRWAGovParams = { id admin, id elec, id maint, id reinv, id dev, u64 elec%, u64 maint%, u64 reinv% }" +OUTPUT=$(call_fn 1 "" "{ { id, id, id, id, id, uint64, uint64, uint64 } }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + # Prüfe ob Governance-Adressen vorhanden sind (nicht alles Nullen) + if echo "$OUTPUT" | grep -qE '[A-Z]{56}'; then + record_pass "GetGovParams — Governance-Adressen vorhanden" + else + record_fail "GetGovParams" "Keine Adressen im Output" + fi +else + record_fail "GetGovParams" "Kein Output empfangen" +fi + +header "TEST 2: GetGovPoll (Function 2) — Poll ID 0" +step "Input: 0uint64 | Output: { QRWAGovProposal, uint64 status }" +OUTPUT=$(call_fn 2 "0uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + record_pass "GetGovPoll(0)" +else + record_fail "GetGovPoll(0)" "Kein Output" +fi + +header "TEST 3: GetAssetReleasePoll (Function 3) — Poll ID 0" +step "Input: 0uint64 | Output: { AssetReleaseProposal, uint64 status }" +OUTPUT=$(call_fn 3 "0uint64" "{ { uint64, id, { id, uint64 }, uint64, id, uint64, uint64, uint64 }, uint64 }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + record_pass "GetAssetReleasePoll(0)" +else + record_fail "GetAssetReleasePoll(0)" "Kein Output" +fi + +header "TEST 4: GetTreasuryBalance (Function 4)" +OUTPUT=$(call_fn 4 "" "{ uint64 }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +TREASURY=$(echo "$OUTPUT" | grep -oE '[0-9]+' | tail -1) +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + record_pass "GetTreasuryBalance — Balance: ${TREASURY:-0}" +else + record_fail "GetTreasuryBalance" "Kein Output" +fi + +header "TEST 5: GetDividendBalances (Function 5)" +step "Output: { revenuePoolA, revenuePoolB, qmineDividendPool, qrwaDividendPool, dedicatedRevenuePool, dedicatedQRWADividendPool }" +OUTPUT=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + # Extrahiere die 6 Werte + POOL_VALUES=$(echo "$OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') + POOL_A=$(echo "$POOL_VALUES" | sed -n '1p') + POOL_B=$(echo "$POOL_VALUES" | sed -n '2p') + QMINE_DIV=$(echo "$POOL_VALUES" | sed -n '3p') + QRWA_DIV=$(echo "$POOL_VALUES" | sed -n '4p') + DEDICATED_REV=$(echo "$POOL_VALUES" | sed -n '5p') + DEDICATED_QRWA=$(echo "$POOL_VALUES" | sed -n '6p') + echo "" + echo -e " ${CYAN}Pool A (QUTIL): ${POOL_A:-0} QU${NC}" + echo -e " ${CYAN}Pool B (User): ${POOL_B:-0} QU${NC}" + echo -e " ${CYAN}QMINE Div Pool: ${QMINE_DIV:-0} QU${NC}" + echo -e " ${CYAN}QRWA Div Pool: ${QRWA_DIV:-0} QU${NC}" + echo -e " ${CYAN}Dedicated Revenue Pool: ${DEDICATED_REV:-0} QU${NC}" + echo -e " ${CYAN}Dedicated QRWA Div Pool: ${DEDICATED_QRWA:-0} QU${NC}" + record_pass "GetDividendBalances" +else + record_fail "GetDividendBalances" "Kein Output" +fi + +header "TEST 6: GetTotalDistributed (Function 6)" +step "Output: { totalQmineDistributed, totalQRWADistributed }" +OUTPUT=$(call_fn 6 "" "{ uint64, uint64 }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + DIST_VALUES=$(echo "$OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') + TOTAL_QMINE_DIST=$(echo "$DIST_VALUES" | sed -n '1p') + TOTAL_QRWA_DIST=$(echo "$DIST_VALUES" | sed -n '2p') + echo "" + echo -e " ${CYAN}Total QMINE distributed: ${TOTAL_QMINE_DIST:-0} QU${NC}" + echo -e " ${CYAN}Total QRWA distributed: ${TOTAL_QRWA_DIST:-0} QU${NC}" + record_pass "GetTotalDistributed" +else + record_fail "GetTotalDistributed" "Kein Output" +fi + +header "TEST 7: GetActiveGovPollIds (Function 8)" +OUTPUT=$(call_fn 8 "" "{ uint64, [64; uint64] }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + GOV_COUNT=$(echo "$OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) + echo -e " ${CYAN}Aktive Gov Polls: ${GOV_COUNT:-0}${NC}" + record_pass "GetActiveGovPollIds — Count: ${GOV_COUNT:-0}" +else + record_fail "GetActiveGovPollIds" "Kein Output" +fi + +header "TEST 8: GetActiveAssetReleasePollIds (Function 7)" +OUTPUT=$(call_fn 7 "" "{ uint64, [64; uint64] }") +echo "$OUTPUT" | grep -v "WARNING" | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + ASSET_COUNT=$(echo "$OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) + echo -e " ${CYAN}Aktive Asset Release Polls: ${ASSET_COUNT:-0}${NC}" + record_pass "GetActiveAssetReleasePollIds — Count: ${ASSET_COUNT:-0}" +else + record_fail "GetActiveAssetReleasePollIds" "Kein Output" +fi + +fi # end --readonly + +############################################### +# TEST 9: QU AN CONTRACT SENDEN (Pool B) +############################################### + +if [[ "$MODE" == "all" || "$MODE" == "--send" ]]; then + +header "TEST 9: QU an QRWA senden (Revenue Pool B)" + +# Snapshot Dividend Balances VOR Transfer +BEFORE=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +POOL_B_BEFORE=$(echo "$BEFORE" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') +POOL_B_BEFORE=${POOL_B_BEFORE:-0} +echo -e " Pool B VOR Transfer: ${POOL_B_BEFORE} QU" + +step "Sende ${SEND_AMOUNT} QU von Seed 1 → $QRWA_IDENTITY" +TX_OUTPUT=$(cli_call_seed "$SEED1" -sendtoaddress "$QRWA_IDENTITY" "$SEND_AMOUNT") +echo "$TX_OUTPUT" | sed 's/^/ /' + +TX_HASH=$(echo "$TX_OUTPUT" | grep "TxHash:" | awk '{print $2}') +TX_TICK=$(echo "$TX_OUTPUT" | grep "Tick:" | awk '{print $2}') + +if [[ -z "$TX_HASH" || -z "$TX_TICK" ]]; then + record_fail "QU senden" "TX konnte nicht gesendet werden" +else + echo "" + step "Warte ${TX_WAIT_SEC}s auf Bestätigung (Tick $TX_TICK)..." + CHECK=$(wait_and_check_tx "$TX_TICK" "$TX_HASH") + echo "$CHECK" | sed 's/^/ /' + + if echo "$CHECK" | grep -q "MoneyFlew: Yes"; then + record_pass "QU senden — ${SEND_AMOUNT} QU, MoneyFlew: Yes" + elif echo "$CHECK" | grep -q "MoneyFlew: N/A"; then + record_skip "QU senden" "TX noch nicht bestätigt (N/A)" + else + record_fail "QU senden" "MoneyFlew != Yes" + fi +fi + +############################################### +# TEST 9b: QUTIL SENDTOMANYV1 → Pool A (Gov Fees!) +############################################### + +header "TEST 9b: QUTIL SendToManyV1 → QRWA (Revenue Pool A)" +step "QUTIL-Transfer wird als sourceId=QUTIL erkannt → Pool A → Gov Fees 50%" + +# Snapshot Dividend Balances VOR QUTIL Transfer +BEFORE_A=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +POOL_A_BEFORE=$(echo "$BEFORE_A" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') +POOL_A_BEFORE=${POOL_A_BEFORE:-0} +echo -e " Pool A VOR Transfer: ${POOL_A_BEFORE} QU" + +QUTIL_AMOUNT=${SEND_AMOUNT} +step "Erstelle QUTIL Payout-File: ${QRWA_IDENTITY} ${QUTIL_AMOUNT}" + +QUTIL_FILE="/tmp/qrwa_qutil_payout.txt" +echo "${QRWA_IDENTITY} ${QUTIL_AMOUNT}" > "$QUTIL_FILE" + +step "Sende via qutilsendtomanyv1 (Seed 1) → Pool A..." +TX_OUTPUT_A=$(cli_call_seed "$SEED1" -qutilsendtomanyv1 "$QUTIL_FILE") +echo "$TX_OUTPUT_A" | sed 's/^/ /' + +TX_HASH_A=$(echo "$TX_OUTPUT_A" | grep "TxHash:" | awk '{print $2}') +TX_TICK_A=$(echo "$TX_OUTPUT_A" | grep "Tick:" | awk '{print $2}') + +if [[ -z "$TX_HASH_A" || -z "$TX_TICK_A" ]]; then + record_fail "QUTIL → Pool A" "TX konnte nicht gesendet werden" +else + echo "" + step "Warte ${TX_WAIT_SEC}s auf Bestätigung (Tick $TX_TICK_A)..." + CHECK_A=$(wait_and_check_tx "$TX_TICK_A" "$TX_HASH_A") + echo "$CHECK_A" | sed 's/^/ /' + + if echo "$CHECK_A" | grep -q "MoneyFlew: Yes"; then + record_pass "QUTIL → Pool A — ${QUTIL_AMOUNT} QU, MoneyFlew: Yes" + + # Verifiziere dass Pool A gewachsen ist + sleep 3 + AFTER_A=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") + POOL_A_AFTER=$(echo "$AFTER_A" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') + POOL_A_AFTER=${POOL_A_AFTER:-0} + echo "" + echo -e " Pool A vorher: ${POOL_A_BEFORE} QU → nachher: ${POOL_A_AFTER} QU" + if [[ "$POOL_A_AFTER" -gt "$POOL_A_BEFORE" ]]; then + record_pass "Pool A befüllt — +$((POOL_A_AFTER - POOL_A_BEFORE)) QU" + else + echo -e " ${YELLOW}Pool A nicht gestiegen (evtl. sofort Payout verteilt)${NC}" + record_skip "Pool A Check" "Pool A: ${POOL_A_AFTER} (evtl. sofort verteilt)" + fi + elif echo "$CHECK_A" | grep -q "MoneyFlew: N/A"; then + record_skip "QUTIL → Pool A" "TX noch nicht bestätigt (N/A)" + else + record_fail "QUTIL → Pool A" "MoneyFlew != Yes" + fi +fi + +rm -f "$QUTIL_FILE" + +############################################### +# TEST 9c: DEDICATED ADDRESS → Dedicated Pool (No Gov Fees!) +############################################### + +header "TEST 9c: Dedicated Address → QRWA (Dedicated Revenue Pool)" +step "Transfer von mDedicatedRevenueAddress → Dedicated Pool (kein Gov Fee)" +step "Dedicated Address: ${DEDICATED_IDENTITY}" + +# Snapshot Dividend Balances VOR Dedicated Transfer +BEFORE_D=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +DEDICATED_BEFORE=$(echo "$BEFORE_D" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '5p') +DEDICATED_BEFORE=${DEDICATED_BEFORE:-0} +echo -e " Dedicated Revenue Pool VOR Transfer: ${DEDICATED_BEFORE} QU" + +step "Sende ${DEDICATED_AMOUNT} QU von Dedicated Address (Seed 3) → $QRWA_IDENTITY" +TX_OUTPUT_D=$(cli_call_seed "$SEED3" -sendtoaddress "$QRWA_IDENTITY" "$DEDICATED_AMOUNT") +echo "$TX_OUTPUT_D" | sed 's/^/ /' + +TX_HASH_D=$(echo "$TX_OUTPUT_D" | grep "TxHash:" | awk '{print $2}') +TX_TICK_D=$(echo "$TX_OUTPUT_D" | grep "Tick:" | awk '{print $2}') + +if [[ -z "$TX_HASH_D" || -z "$TX_TICK_D" ]]; then + record_fail "Dedicated → Pool" "TX konnte nicht gesendet werden" +else + echo "" + step "Warte ${TX_WAIT_SEC}s auf Bestätigung (Tick $TX_TICK_D)..." + CHECK_D=$(wait_and_check_tx "$TX_TICK_D" "$TX_HASH_D") + echo "$CHECK_D" | sed 's/^/ /' + + if echo "$CHECK_D" | grep -q "MoneyFlew: Yes"; then + record_pass "Dedicated → Pool — ${DEDICATED_AMOUNT} QU, MoneyFlew: Yes" + + # Verifiziere dass Dedicated Revenue Pool gewachsen ist + sleep 3 + AFTER_D=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") + DEDICATED_AFTER=$(echo "$AFTER_D" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '5p') + DEDICATED_AFTER=${DEDICATED_AFTER:-0} + echo "" + echo -e " Dedicated Revenue Pool vorher: ${DEDICATED_BEFORE} QU → nachher: ${DEDICATED_AFTER} QU" + if [[ "$DEDICATED_AFTER" -gt "$DEDICATED_BEFORE" ]]; then + DEDICATED_DIFF=$((DEDICATED_AFTER - DEDICATED_BEFORE)) + record_pass "Dedicated Pool befüllt — +${DEDICATED_DIFF} QU (erwartet: ${DEDICATED_AMOUNT})" + # Verify full amount arrived (no gov fee deduction) + if [[ "$DEDICATED_DIFF" -eq "$DEDICATED_AMOUNT" ]]; then + record_pass "Keine Gov Fees abgezogen — voller Betrag angekommen" + else + echo -e " ${YELLOW}Differenz: ${DEDICATED_DIFF} vs. erwartet ${DEDICATED_AMOUNT} (evtl. bereits Payout verteilt)${NC}" + record_skip "Gov Fee Check" "Dedicated Pool Diff: ${DEDICATED_DIFF} (evtl. schon verteilt)" + fi + else + echo -e " ${YELLOW}Dedicated Pool nicht gestiegen (evtl. sofort Payout verteilt)${NC}" + record_skip "Dedicated Pool Check" "Dedicated Pool: ${DEDICATED_AFTER} (evtl. sofort verteilt)" + fi + + # Prüfe dass Pool A und Pool B sich NICHT verändert haben + POOL_A_CHECK=$(echo "$AFTER_D" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') + POOL_B_CHECK=$(echo "$AFTER_D" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') + POOL_A_ORIG=$(echo "$BEFORE_D" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') + POOL_B_ORIG=$(echo "$BEFORE_D" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') + echo "" + echo -e " Pool A: ${POOL_A_ORIG:-0} → ${POOL_A_CHECK:-0}" + echo -e " Pool B: ${POOL_B_ORIG:-0} → ${POOL_B_CHECK:-0}" + if [[ "${POOL_A_CHECK:-0}" -eq "${POOL_A_ORIG:-0}" && "${POOL_B_CHECK:-0}" -eq "${POOL_B_ORIG:-0}" ]]; then + record_pass "Pool A/B unverändert — korrekte Routing zu Dedicated Pool" + else + echo -e " ${YELLOW}Pool A oder B hat sich geändert (anderer Transfer dazwischen?)${NC}" + record_skip "Pool A/B Check" "Möglicherweise parallele Transfers" + fi + elif echo "$CHECK_D" | grep -q "MoneyFlew: N/A"; then + record_skip "Dedicated → Pool" "TX noch nicht bestätigt (N/A)" + else + record_fail "Dedicated → Pool" "MoneyFlew != Yes" + fi +fi + +fi # end --send + +############################################### +# TEST 10: GOVERNANCE VOTE (Procedure 4) +############################################### + +if [[ "$MODE" == "all" || "$MODE" == "--vote" ]]; then + +header "TEST 10: Governance Vote (Procedure 4)" +step "Seed 1 hat 2000 QMINE → kann voten" +step "Input: QRWAGovParams = { admin, elec, maint, reinv, dev, elec%, maint%, reinv% }" + +# Lese aktuelle Governance-Parameter dynamisch aus dem Contract +step "Lese aktuelle GovParams vom Contract..." +VOTE_GOV_OUTPUT=$(call_fn 1 "" "{ { id, id, id, id, id, uint64, uint64, uint64 } }") +VOTE_GOV_ADDRS=$(echo "$VOTE_GOV_OUTPUT" | grep -oE '[A-Z]{55,60}' | head -5) +ADMIN_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '1p') +ELEC_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '2p') +MAINT_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '3p') +REINV_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '4p') +DEV_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '5p') +echo -e " Admin: ${ADMIN_ID:-?}" +echo -e " Elec: ${ELEC_ID:-?}" +echo -e " Maint: ${MAINT_ID:-?}" +echo -e " Reinv: ${REINV_ID:-?}" +echo -e " Dev: ${DEV_ID:-?}" + +# invokecontractprocedure +# Proc 4 = VoteGovParams, Amount = 0 (wird refunded) +TX_OUTPUT=$(cli_call_seed "$SEED1" \ + -enabletestcontracts \ + -invokecontractprocedure "$CONTRACT_INDEX" 4 0 \ + "{${ADMIN_ID}id,${ELEC_ID}id,${MAINT_ID}id,${REINV_ID}id,${DEV_ID}id,350uint64,50uint64,100uint64}") +echo "$TX_OUTPUT" | sed 's/^/ /' + +TX_HASH=$(echo "$TX_OUTPUT" | grep "TxHash:" | awk '{print $2}') +TX_TICK=$(echo "$TX_OUTPUT" | grep "Tick:" | awk '{print $2}') + +if [[ -z "$TX_HASH" || -z "$TX_TICK" ]]; then + record_fail "VoteGovParams" "TX konnte nicht gesendet werden" +else + echo "" + step "Warte ${TX_WAIT_SEC}s auf Bestätigung..." + CHECK=$(wait_and_check_tx "$TX_TICK" "$TX_HASH") + echo "$CHECK" | sed 's/^/ /' + + if echo "$CHECK" | grep -q "MoneyFlew: Yes"; then + record_pass "VoteGovParams — TX bestätigt, MoneyFlew: Yes" + + # Prüfe ob Gov Poll angelegt wurde + step "Prüfe aktive Gov Polls..." + OUTPUT=$(call_fn 8 "" "{ uint64, [64; uint64] }") + GOV_COUNT=$(echo "$OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) + if [[ "${GOV_COUNT:-0}" -gt 0 ]]; then + record_pass "Gov Poll erstellt — Count: $GOV_COUNT" + else + record_skip "Gov Poll Count" "Count ist 0 (ggf. kein QMINE-Balance)" + fi + elif echo "$CHECK" | grep -q "MoneyFlew: N/A"; then + record_skip "VoteGovParams" "TX noch nicht bestätigt" + else + # MoneyFlew: No kann OK sein wenn kein QMINE → Einsatz wird refunded + record_pass "VoteGovParams — TX verarbeitet (MoneyFlew kann No sein bei Refund)" + fi +fi + +fi # end --vote + +############################################### +# TEST 12: GETGENERALASSETS + GETGENERALASSETBALANCE +############################################### + +if [[ "$MODE" == "all" || "$MODE" == "--readonly" ]]; then + +header "TEST 12: GetGeneralAssets (Function 10)" +OUTPUT=$(call_fn 10 "" "{ uint64, [1024; { id, uint64 }], [1024; uint64] }") +echo "$OUTPUT" | grep -v "WARNING" | head -20 | sed 's/^/ /' + +if echo "$OUTPUT" | grep -q "Contract Function Output"; then + record_pass "GetGeneralAssets" +else + record_fail "GetGeneralAssets" "Kein Output" +fi + +fi + +############################################### +# TEST 13: PAYOUT-ZYKLUS — 90/10 VERTEILUNG +############################################### + +if [[ "$MODE" == "all" || "$MODE" == "--payout" ]]; then + +header "TEST 13: Payout-Zyklus — 90/10 Pool-Verteilung" + +# Schritt 1: Sicherstellen dass Revenue in Pool B ist +step "Prüfe aktuelle Pools..." +PRE_CHECK=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +PRE_POOL_B=$(echo "$PRE_CHECK" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') +PRE_POOL_B=${PRE_POOL_B:-0} + +if [[ "$PRE_POOL_B" -eq 0 ]]; then + step "Pool B ist leer — sende ${SEND_AMOUNT} QU für Payout-Test..." + TX_OUT=$(cli_call_seed "$SEED1" -sendtoaddress "$QRWA_IDENTITY" "$SEND_AMOUNT") + TX_H=$(echo "$TX_OUT" | grep "TxHash:" | awk '{print $2}') + TX_T=$(echo "$TX_OUT" | grep "Tick:" | awk '{print $2}') + if [[ -n "$TX_H" && -n "$TX_T" ]]; then + echo -e " TX gesendet: ${TX_H} (Tick ${TX_T})" + step "Warte auf Bestätigung..." + CHK=$(wait_and_check_tx "$TX_T" "$TX_H") + if echo "$CHK" | grep -q "MoneyFlew: Yes"; then + echo -e " ${GREEN}MoneyFlew: Yes${NC}" + else + echo -e " ${YELLOW}MoneyFlew Status: $(echo "$CHK" | grep MoneyFlew)${NC}" + fi + fi + sleep 5 +fi + +# Schritt 2: Snapshot VOR Payout +step "Snapshot VOR Payout..." +BEFORE_DIV=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +BEFORE_POOL_A=$(echo "$BEFORE_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') +BEFORE_POOL_B=$(echo "$BEFORE_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') +BEFORE_QMINE_POOL=$(echo "$BEFORE_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '3p') +BEFORE_QRWA_POOL=$(echo "$BEFORE_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '4p') +BEFORE_POOL_A=${BEFORE_POOL_A:-0} +BEFORE_POOL_B=${BEFORE_POOL_B:-0} +BEFORE_QMINE_POOL=${BEFORE_QMINE_POOL:-0} +BEFORE_QRWA_POOL=${BEFORE_QRWA_POOL:-0} +BEFORE_DEDICATED_REV=$(echo "$BEFORE_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '5p') +BEFORE_DEDICATED_QRWA=$(echo "$BEFORE_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '6p') +BEFORE_DEDICATED_REV=${BEFORE_DEDICATED_REV:-0} +BEFORE_DEDICATED_QRWA=${BEFORE_DEDICATED_QRWA:-0} + +echo -e " Pool A: ${BEFORE_POOL_A} QU" +echo -e " Pool B: ${BEFORE_POOL_B} QU" +echo -e " QMINE Div Pool: ${BEFORE_QMINE_POOL} QU" +echo -e " QRWA Div Pool: ${BEFORE_QRWA_POOL} QU" +echo -e " Dedicated Rev Pool: ${BEFORE_DEDICATED_REV} QU" +echo -e " Dedicated QRWA Pool: ${BEFORE_DEDICATED_QRWA} QU" + +# TotalDistributed VOR Payout +BEFORE_TOTAL=$(call_fn 6 "" "{ uint64, uint64 }") +BEFORE_TOTAL_QMINE=$(echo "$BEFORE_TOTAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') +BEFORE_TOTAL_QRWA=$(echo "$BEFORE_TOTAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') +BEFORE_TOTAL_QMINE=${BEFORE_TOTAL_QMINE:-0} +BEFORE_TOTAL_QRWA=${BEFORE_TOTAL_QRWA:-0} +echo -e " Total QMINE Dist: ${BEFORE_TOTAL_QMINE} QU" +echo -e " Total QRWA Dist: ${BEFORE_TOTAL_QRWA} QU" + +TOTAL_REVENUE=$((BEFORE_POOL_A + BEFORE_POOL_B + BEFORE_DEDICATED_REV)) + +# Berechne erwartete Verteilung (basierend auf aktuellem Snapshot) +if [[ "$TOTAL_REVENUE" -gt 0 ]]; then + GOV_FEE_TOTAL=$((BEFORE_POOL_A * 500 / 1000)) + REMAINING_A=$((BEFORE_POOL_A - GOV_FEE_TOTAL)) + TOTAL_FOR_SPLIT=$((REMAINING_A + BEFORE_POOL_B)) + EXPECTED_QMINE=$((TOTAL_FOR_SPLIT * 900 / 1000)) + EXPECTED_QRWA=$((TOTAL_FOR_SPLIT - EXPECTED_QMINE)) + + # Dedicated Pool: kein Gov Fee, eigener 90/10 Split + EXPECTED_DEDICATED_QMINE=$((BEFORE_DEDICATED_REV * 900 / 1000)) + EXPECTED_DEDICATED_QRWA=$((BEFORE_DEDICATED_REV - EXPECTED_DEDICATED_QMINE)) + + echo "" + echo -e " ${CYAN}Erwartete Verteilung bei nächstem Payout:${NC}" + echo -e " Gov Fees (aus Pool A): ${GOV_FEE_TOTAL} QU (elec 35% + maint 5% + reinv 10%)" + echo -e " Pool A+B nach Fees: ${TOTAL_FOR_SPLIT} QU (rest A + B)" + echo -e " → QMINE (90%): ${EXPECTED_QMINE} QU" + echo -e " → qRWA (10%): ${EXPECTED_QRWA} QU" + if [[ "$BEFORE_DEDICATED_REV" -gt 0 ]]; then + echo -e " Dedicated Pool (kein Gov Fee): ${BEFORE_DEDICATED_REV} QU" + echo -e " → Dedicated QMINE (90%): ${EXPECTED_DEDICATED_QMINE} QU" + echo -e " → Dedicated qRWA (10%): ${EXPECTED_DEDICATED_QRWA} QU" + fi +else + echo "" + echo -e " ${YELLOW}Pool A + B + Dedicated = 0 — alle Revenue wurde bereits verteilt${NC}" + echo -e " QMINE Div Pool: ${BEFORE_QMINE_POOL} QU (wartet auf Epoch-Snapshot)" + echo -e " QRWA Dist bisher: ${BEFORE_TOTAL_QRWA} QU" +fi + +if [[ "$TOTAL_REVENUE" -eq 0 ]]; then + record_skip "Payout 90/10" "Pool A+B+Dedicated=0 — Revenue bereits verteilt, QMINE Pool: ${BEFORE_QMINE_POOL}" +else + echo "" + step "Warte auf nächsten Payout-Zyklus (alle 20 Ticks ≈ 40s)..." + echo -e " ${YELLOW}Prüfe alle 10s ob Payout stattfand...${NC}" + + PAYOUT_DETECTED=0 + for i in $(seq 1 12); do + sleep 10 + AFTER_DIV=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") + AFTER_POOL_B=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') + AFTER_POOL_B=${AFTER_POOL_B:-0} + + AFTER_QMINE_POOL=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '3p') + AFTER_QMINE_POOL=${AFTER_QMINE_POOL:-0} + + AFTER_DEDICATED_CHK=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '5p') + AFTER_DEDICATED_CHK=${AFTER_DEDICATED_CHK:-0} + + AFTER_TOTAL_CHK=$(call_fn 6 "" "{ uint64, uint64 }") + AFTER_TOTAL_QRWA_CHK=$(echo "$AFTER_TOTAL_CHK" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') + AFTER_TOTAL_QRWA_CHK=${AFTER_TOTAL_QRWA_CHK:-0} + + echo -e " [${i}/12] Pool B: ${AFTER_POOL_B} | QMINE Pool: ${AFTER_QMINE_POOL} | Dedicated: ${AFTER_DEDICATED_CHK} | QRWA Dist: ${AFTER_TOTAL_QRWA_CHK}" + + # Payout erkannt: Pool B gesunken ODER QMINE Pool gestiegen ODER Total QRWA Dist gestiegen ODER Dedicated Pool verändert + if [[ "$AFTER_POOL_B" -lt "$BEFORE_POOL_B" ]] || \ + [[ "$AFTER_QMINE_POOL" -gt "$BEFORE_QMINE_POOL" ]] || \ + [[ "$AFTER_TOTAL_QRWA_CHK" -gt "$BEFORE_TOTAL_QRWA" ]] || \ + [[ "$AFTER_DEDICATED_CHK" -ne "$BEFORE_DEDICATED_REV" ]]; then + PAYOUT_DETECTED=1 + echo -e " ${GREEN}Payout erkannt!${NC}" + break + fi + done + + if [[ "$PAYOUT_DETECTED" -eq 1 ]]; then + # Finaler Zustand + AFTER_DIV=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") + AFTER_POOL_A=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') + AFTER_POOL_B=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') + AFTER_QMINE_POOL=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '3p') + AFTER_QRWA_POOL=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '4p') + AFTER_POOL_A=${AFTER_POOL_A:-0} + AFTER_POOL_B=${AFTER_POOL_B:-0} + AFTER_QMINE_POOL=${AFTER_QMINE_POOL:-0} + AFTER_QRWA_POOL=${AFTER_QRWA_POOL:-0} + AFTER_DEDICATED_REV=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '5p') + AFTER_DEDICATED_QRWA=$(echo "$AFTER_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '6p') + AFTER_DEDICATED_REV=${AFTER_DEDICATED_REV:-0} + AFTER_DEDICATED_QRWA=${AFTER_DEDICATED_QRWA:-0} + + AFTER_TOTAL=$(call_fn 6 "" "{ uint64, uint64 }") + AFTER_TOTAL_QMINE=$(echo "$AFTER_TOTAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') + AFTER_TOTAL_QRWA=$(echo "$AFTER_TOTAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') + AFTER_TOTAL_QMINE=${AFTER_TOTAL_QMINE:-0} + AFTER_TOTAL_QRWA=${AFTER_TOTAL_QRWA:-0} + + echo "" + echo -e " ${BOLD}NACH Payout:${NC}" + echo -e " Pool A: ${AFTER_POOL_A} QU" + echo -e " Pool B: ${AFTER_POOL_B} QU" + echo -e " QMINE Div Pool: ${AFTER_QMINE_POOL} QU" + echo -e " QRWA Div Pool: ${AFTER_QRWA_POOL} QU" + echo -e " Dedicated Rev Pool: ${AFTER_DEDICATED_REV} QU" + echo -e " Dedicated QRWA Pool: ${AFTER_DEDICATED_QRWA} QU" + echo -e " Total QMINE Dist: ${AFTER_TOTAL_QMINE} QU" + echo -e " Total QRWA Dist: ${AFTER_TOTAL_QRWA} QU" + + # 13a: Pool B wurde aufgebraucht + if [[ "$AFTER_POOL_B" -eq 0 ]] && [[ "$BEFORE_POOL_B" -gt 0 ]]; then + record_pass "Pool B aufgebraucht — war: ${BEFORE_POOL_B}, jetzt: 0" + elif [[ "$BEFORE_POOL_B" -eq 0 ]]; then + record_pass "Pool B war bereits 0" + else + record_fail "Pool B Consumption" "Pool B: ${AFTER_POOL_B} (erwartet: 0)" + fi + + # 13b: 90/10 Split verifizieren + DELTA_TOTAL_QMINE=$((AFTER_TOTAL_QMINE - BEFORE_TOTAL_QMINE)) + DELTA_TOTAL_QRWA=$((AFTER_TOTAL_QRWA - BEFORE_TOTAL_QRWA)) + QMINE_POOL_DELTA=$((AFTER_QMINE_POOL - BEFORE_QMINE_POOL)) + + echo "" + echo -e " ${BOLD}Verteilungsanalyse:${NC}" + echo -e " Δ QMINE Distributed: +${DELTA_TOTAL_QMINE} QU" + echo -e " Δ QRWA Distributed: +${DELTA_TOTAL_QRWA} QU" + echo -e " Δ QMINE Pool (akkum): +${QMINE_POOL_DELTA} QU" + + # Fall A: Beide verteilt (Epoch-Snapshot vorhanden) + if [[ "$DELTA_TOTAL_QMINE" -gt 0 && "$DELTA_TOTAL_QRWA" -gt 0 ]]; then + TOTAL_DELTA=$((DELTA_TOTAL_QMINE + DELTA_TOTAL_QRWA)) + ACTUAL_QMINE_PCT=$((DELTA_TOTAL_QMINE * 1000 / TOTAL_DELTA)) + ACTUAL_QRWA_PCT=$((DELTA_TOTAL_QRWA * 1000 / TOTAL_DELTA)) + echo -e " QMINE: ${ACTUAL_QMINE_PCT}‰ | QRWA: ${ACTUAL_QRWA_PCT}‰ (erwartet: 900/100)" + if [[ "$ACTUAL_QMINE_PCT" -ge 880 && "$ACTUAL_QMINE_PCT" -le 920 ]]; then + record_pass "90/10 Split — QMINE ${ACTUAL_QMINE_PCT}‰ ≈ 90%" + else + record_fail "90/10 Split" "QMINE ${ACTUAL_QMINE_PCT}‰ (erwartet: ~900‰)" + fi + + # Fall B: QMINE akkumuliert in Pool (kein Epoch-Snapshot), QRWA verteilt + elif [[ "$QMINE_POOL_DELTA" -gt 0 && "$DELTA_TOTAL_QRWA" -gt 0 ]]; then + TOTAL_MOVED=$((QMINE_POOL_DELTA + DELTA_TOTAL_QRWA)) + ACTUAL_QMINE_PCT=$((QMINE_POOL_DELTA * 1000 / TOTAL_MOVED)) + echo -e " QMINE(pool)/QRWA(dist) = ${ACTUAL_QMINE_PCT}‰ (erwartet: ~900‰)" + if [[ "$ACTUAL_QMINE_PCT" -ge 880 && "$ACTUAL_QMINE_PCT" -le 920 ]]; then + record_pass "90/10 Split (via Pool) — QMINE ${ACTUAL_QMINE_PCT}‰ ≈ 90%" + else + record_fail "90/10 Split" "QMINE Pool ${ACTUAL_QMINE_PCT}‰ (erwartet: ~900‰)" + fi + + # Fall C: Nur QMINE Pool wuchs (kein Epoch-Snapshot UND qRWA nicht verteilt) + elif [[ "$QMINE_POOL_DELTA" -gt 0 ]]; then + echo -e " ${YELLOW}QMINE Pool akkumuliert, QRWA nicht verteilt (kein Epoch?)${NC}" + record_skip "90/10 Split" "Nur QMINE Pool +${QMINE_POOL_DELTA}, kein QRWA Delta" + + else + record_skip "90/10 Split" "Keine messbare Verteilung" + fi + else + record_skip "Payout-Zyklus" "Kein Payout in 120s erkannt" + fi +fi + +############################################### +# TEST 14: GOVERNANCE FEE ADRESSEN PRÜFEN +############################################### + +header "TEST 14: Governance Fee Adressen — Balance Check" +step "Lese GovParams für Adressen..." + +# Parse Adressen aus GetGovParams Output (5 Identities + 3 uint64) +GOV_OUTPUT=$(call_fn 1 "" "{ { id, id, id, id, id, uint64, uint64, uint64 } }") +GOV_ADDRS=$(echo "$GOV_OUTPUT" | grep -oE '[A-Z]{55,60}' | head -5) +GOV_ADMIN=$(echo "$GOV_ADDRS" | sed -n '1p') +GOV_ELEC=$(echo "$GOV_ADDRS" | sed -n '2p') +GOV_MAINT=$(echo "$GOV_ADDRS" | sed -n '3p') +GOV_REINV=$(echo "$GOV_ADDRS" | sed -n '4p') +GOV_DEV=$(echo "$GOV_ADDRS" | sed -n '5p') + +GOV_PCTS=$(echo "$GOV_OUTPUT" | sed -n '/Contract Function Output/,$ p' | sed 's/^[[:space:]]*//' | sed 's/,$//' | grep -oE '^[0-9]+$') +GOV_ELEC_PCT=$(echo "$GOV_PCTS" | sed -n '1p') +GOV_MAINT_PCT=$(echo "$GOV_PCTS" | sed -n '2p') +GOV_REINV_PCT=$(echo "$GOV_PCTS" | sed -n '3p') + +echo -e " Admin: ${GOV_ADMIN:-?}" +echo -e " Electricity: ${GOV_ELEC:-?} (${GOV_ELEC_PCT:-?}‰)" +echo -e " Maintenance: ${GOV_MAINT:-?} (${GOV_MAINT_PCT:-?}‰)" +echo -e " Reinvestment:${GOV_REINV:-?} (${GOV_REINV_PCT:-?}‰)" +echo -e " QMINE Dev: ${GOV_DEV:-?}" +echo "" + +# Balance-Check für jede Governance-Adresse +for LABEL_ADDR in "Electricity:${GOV_ELEC}" "Maintenance:${GOV_MAINT}" "Reinvestment:${GOV_REINV}" "QMINE Dev:${GOV_DEV}"; do + LABEL=$(echo "$LABEL_ADDR" | cut -d: -f1) + ADDR=$(echo "$LABEL_ADDR" | cut -d: -f2) + if [[ -z "$ADDR" || "$ADDR" == "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFXIB" ]]; then + record_skip "${LABEL} Balance" "Adresse ist NULL_ID" + continue + fi + BAL_OUT=$(cli_call -getbalance "$ADDR") + GOV_BAL=$(echo "$BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + GOV_BAL=${GOV_BAL:-0} + echo -e " ${LABEL} Balance: ${GOV_BAL} QU" + if [[ "$GOV_BAL" -gt 0 ]]; then + record_pass "${LABEL} hat Funds — ${GOV_BAL} QU" + else + record_skip "${LABEL} Balance" "0 QU (Pool A war evtl. leer → keine Gov Fees)" + fi +done + +# Pool A Payout-Verifikation: Summe der Gov Fee Balances prüfen +step "Pool A Payout-Verifikation..." +GOV_FEE_SUM=0 +for CHK_ADDR in "$GOV_ELEC" "$GOV_MAINT" "$GOV_REINV"; do + if [[ -n "$CHK_ADDR" && "$CHK_ADDR" != "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFXIB" ]]; then + CHK_BAL_OUT=$(cli_call -getbalance "$CHK_ADDR") + CHK_BAL=$(echo "$CHK_BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + GOV_FEE_SUM=$((GOV_FEE_SUM + ${CHK_BAL:-0})) + fi +done + +echo -e " ${CYAN}Summe Gov Fees (Elec+Maint+Reinv): ${GOV_FEE_SUM} QU${NC}" + +# Pool A Status prüfen +POOL_A_DIV=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +POOL_A_NOW=$(echo "$POOL_A_DIV" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') +POOL_A_NOW=${POOL_A_NOW:-0} +echo -e " ${CYAN}Pool A aktuell: ${POOL_A_NOW} QU${NC}" + +if [[ "$GOV_FEE_SUM" -gt 0 ]]; then + record_pass "Pool A Payout — Gov Fees verteilt: ${GOV_FEE_SUM} QU (Pool A jetzt: ${POOL_A_NOW})" +elif [[ "$POOL_A_NOW" -gt 0 ]]; then + record_skip "Pool A Payout" "Pool A hat ${POOL_A_NOW} QU aber Gov Fees noch 0 — Payout steht aus" +else + record_skip "Pool A Payout" "Pool A = 0 und Gov Fees = 0 — kein QUTIL-Revenue eingegangen" +fi + +echo "" +echo -e " ${CYAN}Hinweis: Gov Fees werden nur aus Pool A (QUTIL) abgezogen.${NC}" +echo -e " ${CYAN}Wenn Pool A = 0, erhalten die Adressen keine Fees.${NC}" + +############################################### +# TEST 15: BLOCKCHAIN qRWA + QMINE OWNER ANALYSE +############################################### + +header "TEST 15: Blockchain qRWA Owner + QMINE Balance Analyse" +step "Lese alle qRWA-Owner von der Blockchain (queryassets ownerships)..." + +# --- 15a: Alle qRWA-Shareholder abfragen --- +QRWA_OWNERS_RAW=$(cli_call -queryassets ownerships "name=QRWA") + +# Parse: owner → shares (nur Einträge mit shares > 0) +declare -a QRWA_OWNER_IDS=() +declare -a QRWA_OWNER_SHARES=() +QRWA_TOTAL_SHARES=0 + +while IFS= read -r line; do + if echo "$line" | grep -q 'owner = '; then + OWNER=$(echo "$line" | sed 's/.*owner = //' | grep -oE '[A-Z]{50,60}') + fi + if echo "$line" | grep -q 'number of shares = '; then + SHARES=$(echo "$line" | sed 's/.*number of shares = //' | grep -oE '[0-9]+') + if [[ -n "$OWNER" && -n "$SHARES" && "$SHARES" -gt 0 ]]; then + QRWA_OWNER_IDS+=("$OWNER") + QRWA_OWNER_SHARES+=("$SHARES") + QRWA_TOTAL_SHARES=$((QRWA_TOTAL_SHARES + SHARES)) + fi + fi +done <<< "$QRWA_OWNERS_RAW" + +QRWA_OWNER_COUNT=${#QRWA_OWNER_IDS[@]} +echo -e " ${CYAN}qRWA Owner gefunden: ${QRWA_OWNER_COUNT}${NC}" +echo -e " ${CYAN}qRWA Total Shares: ${QRWA_TOTAL_SHARES}${NC}" + +if [[ "$QRWA_OWNER_COUNT" -gt 0 ]]; then + record_pass "qRWA Owner Query — ${QRWA_OWNER_COUNT} Owner, ${QRWA_TOTAL_SHARES} Shares" +else + record_fail "qRWA Owner Query" "Keine qRWA Owner gefunden" +fi + +# --- 15b: Für jeden qRWA-Owner die QMINE-Balance prüfen --- +QRWA_CHECK_LIMIT=15 +if [[ "$QRWA_OWNER_COUNT" -gt "$QRWA_CHECK_LIMIT" ]]; then + QRWA_LOOP_MAX=$QRWA_CHECK_LIMIT +else + QRWA_LOOP_MAX=$QRWA_OWNER_COUNT +fi +step "Prüfe QMINE-Balance der ersten ${QRWA_LOOP_MAX} qRWA-Owners (von ${QRWA_OWNER_COUNT})..." +echo "" + +QMINE_PER_QRWA_SHARE_MIN=100000 # aus qRWA.h: QRWA_QMINE_PER_QRWA_SHARE_MIN +ELIGIBLE_COUNT=0 +ELIGIBLE_SHARES=0 +declare -a ELIGIBLE_IDS=() +declare -a ELIGIBLE_QRWA_SHARES=() +declare -a ELIGIBLE_QMINE=() + +for idx in $(seq 0 $((QRWA_LOOP_MAX - 1))); do + OWN_ID="${QRWA_OWNER_IDS[$idx]}" + OWN_QRWA="${QRWA_OWNER_SHARES[$idx]}" + + # Hole alle Assets dieses Owners + ASSETS_OUT=$(cli_call -getasset "$OWN_ID") + + # QMINE Shares extrahieren (Suche nach QMINE + danach "Number Of Shares") + OWN_QMINE=0 + if echo "$ASSETS_OUT" | grep -qi "QMINE"; then + # Finde den QMINE-Block und extrahiere Shares + OWN_QMINE=$(echo "$ASSETS_OUT" | awk '/QMINE/{found=1} found && /Number Of Shares/{print $NF; exit}') + OWN_QMINE=${OWN_QMINE:-0} + fi + + # Berechne benötigte QMINE + REQUIRED_QMINE=$((OWN_QRWA * QMINE_PER_QRWA_SHARE_MIN)) + + # Eligibility + if [[ "$OWN_QMINE" -ge "$REQUIRED_QMINE" && "$REQUIRED_QMINE" -gt 0 ]]; then + ELIGIBLE="✓" + ELIGIBLE_COUNT=$((ELIGIBLE_COUNT + 1)) + ELIGIBLE_SHARES=$((ELIGIBLE_SHARES + OWN_QRWA)) + ELIGIBLE_IDS+=("$OWN_ID") + ELIGIBLE_QRWA_SHARES+=("$OWN_QRWA") + ELIGIBLE_QMINE+=("$OWN_QMINE") + COLOR="$GREEN" + else + ELIGIBLE="✗" + COLOR="$RED" + fi + + # Kürze ID für Übersicht + SHORT_ID="${OWN_ID:0:12}…${OWN_ID: -6}" + echo -e " ${COLOR}${ELIGIBLE}${NC} ${SHORT_ID} qRWA: ${OWN_QRWA} QMINE: ${OWN_QMINE} benötigt: ${REQUIRED_QMINE}" +done + +echo "" +echo -e " ${BOLD}═══ Eligibility Zusammenfassung ═══${NC}" +echo -e " ${CYAN}Eligible Owner (≥100K QMINE/Share): ${ELIGIBLE_COUNT} / ${QRWA_OWNER_COUNT}${NC}" +echo -e " ${CYAN}Eligible Shares: ${ELIGIBLE_SHARES} / ${QRWA_TOTAL_SHARES}${NC}" + +if [[ "$ELIGIBLE_COUNT" -gt 0 ]]; then + record_pass "qRWA Eligible Holders — ${ELIGIBLE_COUNT} Owner mit ${ELIGIBLE_SHARES} Shares" +else + record_fail "qRWA Eligible Holders" "Kein Owner hat ≥100K QMINE pro qRWA Share" +fi + +# --- 15c: QMINE Owner Gesamtübersicht --- +step "Lese QMINE-Owner von der Blockchain..." +QMINE_OWNERS_RAW=$(cli_call -queryassets ownerships "name=QMINE,issuer=${QMINE_ISSUER}") + +declare -a QMINE_OWNER_IDS=() +declare -a QMINE_OWNER_SHARES=() +QMINE_TOTAL_SHARES=0 + +while IFS= read -r line; do + if echo "$line" | grep -q 'owner = '; then + OWNER=$(echo "$line" | sed 's/.*owner = //' | grep -oE '[A-Z]{50,60}') + fi + if echo "$line" | grep -q 'number of shares = '; then + SHARES=$(echo "$line" | sed 's/.*number of shares = //' | grep -oE '[0-9]+') + if [[ -n "$OWNER" && -n "$SHARES" && "$SHARES" -gt 0 ]]; then + QMINE_OWNER_IDS+=("$OWNER") + QMINE_OWNER_SHARES+=("$SHARES") + QMINE_TOTAL_SHARES=$((QMINE_TOTAL_SHARES + SHARES)) + fi + fi +done <<< "$QMINE_OWNERS_RAW" + +QMINE_OWNER_COUNT=${#QMINE_OWNER_IDS[@]} +echo -e " ${CYAN}QMINE Owner gefunden: ${QMINE_OWNER_COUNT}${NC}" +echo -e " ${CYAN}QMINE Total Shares: ${QMINE_TOTAL_SHARES}${NC}" +echo "" + +# Top 10 QMINE Holder ausgeben +step "Top 10 QMINE Holder:" +# Sortiere nach Shares (absteigende Reihenfolge) +declare -a SORTED_INDICES=() +for i in $(seq 0 $((QMINE_OWNER_COUNT - 1))); do + echo "${QMINE_OWNER_SHARES[$i]} $i" +done | sort -rn | head -10 | while read -r shares idx; do + QM_ID="${QMINE_OWNER_IDS[$idx]}" + SHORT_QM="${QM_ID:0:12}…${QM_ID: -6}" + echo -e " ${SHORT_QM} QMINE: ${shares}" +done + +if [[ "$QMINE_OWNER_COUNT" -gt 0 ]]; then + record_pass "QMINE Owner Query — ${QMINE_OWNER_COUNT} Owner, ${QMINE_TOTAL_SHARES} Total Shares" +else + record_fail "QMINE Owner Query" "Keine QMINE Owner gefunden" +fi + +############################################### +# TEST 16-DIST: PAYOUT DISTRIBUTION VERIFIKATION +############################################### + +header "TEST 16: Payout Distribution Verifikation" +step "Prüfe Contract-State: TotalDistributed + Pools..." + +QRWA_DIV_OUT=$(call_fn 5 "" "{ uint64, uint64, uint64, uint64, uint64, uint64 }") +QRWA_DIV_POOL=$(echo "$QRWA_DIV_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '4p') +QRWA_DIV_POOL=${QRWA_DIV_POOL:-0} + +QMINE_DIV_POOL=$(echo "$QRWA_DIV_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '3p') +QMINE_DIV_POOL=${QMINE_DIV_POOL:-0} + +DEDICATED_REV_POOL=$(echo "$QRWA_DIV_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '5p') +DEDICATED_REV_POOL=${DEDICATED_REV_POOL:-0} +DEDICATED_QRWA_POOL=$(echo "$QRWA_DIV_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '6p') +DEDICATED_QRWA_POOL=${DEDICATED_QRWA_POOL:-0} + +TOTAL_DIST_OUT=$(call_fn 6 "" "{ uint64, uint64 }") +TOTAL_QMINE_FINAL=$(echo "$TOTAL_DIST_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '1p') +TOTAL_QRWA_FINAL=$(echo "$TOTAL_DIST_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | sed -n '2p') +TOTAL_QMINE_FINAL=${TOTAL_QMINE_FINAL:-0} +TOTAL_QRWA_FINAL=${TOTAL_QRWA_FINAL:-0} + +echo -e " QMINE Total Distributed: ${TOTAL_QMINE_FINAL} QU" +echo -e " qRWA Total Distributed: ${TOTAL_QRWA_FINAL} QU" +echo -e " QMINE Div Pool (rest): ${QMINE_DIV_POOL} QU" +echo -e " qRWA Div Pool (rest): ${QRWA_DIV_POOL} QU" +echo -e " Dedicated Rev Pool: ${DEDICATED_REV_POOL} QU" +echo -e " Dedicated QRWA Div Pool: ${DEDICATED_QRWA_POOL} QU" + +# 16a: qRWA Distribution passiert +if [[ "$TOTAL_QRWA_FINAL" -gt 0 ]]; then + if [[ "$ELIGIBLE_SHARES" -gt 0 ]]; then + PER_SHARE=$((TOTAL_QRWA_FINAL / ELIGIBLE_SHARES)) + echo -e " Durchschnitt pro eligible qRWA Share: ~${PER_SHARE} QU" + fi + record_pass "qRWA Distribution — Total: ${TOTAL_QRWA_FINAL} QU verteilt" +else + if [[ "$QRWA_DIV_POOL" -gt 0 ]]; then + if [[ "$ELIGIBLE_COUNT" -eq 0 ]]; then + echo -e " ${YELLOW}qRWA Div Pool hat ${QRWA_DIV_POOL} QU — ABER kein eligible Holder!${NC}" + echo -e " ${YELLOW}(Kein Owner hat ≥100K QMINE pro qRWA Share)${NC}" + record_skip "qRWA Distribution" "Pool: ${QRWA_DIV_POOL} QU, 0 eligible Holder" + else + echo -e " ${YELLOW}qRWA Div Pool hat ${QRWA_DIV_POOL} QU — wartet auf nächsten Payout${NC}" + record_skip "qRWA Distribution" "Pool: ${QRWA_DIV_POOL} QU, ${ELIGIBLE_COUNT} eligible Holder" + fi + else + record_skip "qRWA Distribution" "Noch kein Revenue verteilt" + fi +fi + +# 16b: Erwarteter Payout pro eligible Holder +if [[ "$TOTAL_QRWA_FINAL" -gt 0 && "$ELIGIBLE_COUNT" -gt 0 ]]; then + echo "" + echo -e " ${BOLD}Erwartete qRWA-Verteilung an eligible Holder:${NC}" + for eidx in $(seq 0 $((ELIGIBLE_COUNT - 1))); do + E_ID="${ELIGIBLE_IDS[$eidx]}" + E_QRWA="${ELIGIBLE_QRWA_SHARES[$eidx]}" + E_QMINE="${ELIGIBLE_QMINE[$eidx]}" + EXPECTED_PAYOUT=$((TOTAL_QRWA_FINAL * E_QRWA / ELIGIBLE_SHARES)) + SHORT_E="${E_ID:0:12}…${E_ID: -6}" + echo -e " ${GREEN}✓${NC} ${SHORT_E} qRWA: ${E_QRWA} QMINE: ${E_QMINE} → ~${EXPECTED_PAYOUT} QU" + done +fi + +# 16c: QMINE Distribution (benötigt Epoch-Snapshot) +if [[ "$TOTAL_QMINE_FINAL" -gt 0 ]]; then + record_pass "QMINE Distribution — Total: ${TOTAL_QMINE_FINAL} QU an Holder verteilt" +else + if [[ "$QMINE_DIV_POOL" -gt 0 ]]; then + echo -e " ${YELLOW}QMINE Pool hat ${QMINE_DIV_POOL} QU — wartet auf Epoch-Snapshot${NC}" + echo -e " ${YELLOW}(BEGIN_EPOCH muss einmal gelaufen sein um Snapshots anzulegen)${NC}" + record_skip "QMINE Distribution" "Pool: ${QMINE_DIV_POOL} QU, kein Epoch-Snapshot" + else + record_skip "QMINE Distribution" "Noch kein Revenue verteilt" + fi +fi + +# 16d: Gesamtverteilung 90/10 Plausibilität +if [[ "$TOTAL_QMINE_FINAL" -gt 0 && "$TOTAL_QRWA_FINAL" -gt 0 ]]; then + GRAND_TOTAL=$((TOTAL_QMINE_FINAL + TOTAL_QRWA_FINAL)) + FINAL_QMINE_PCT=$((TOTAL_QMINE_FINAL * 1000 / GRAND_TOTAL)) + FINAL_QRWA_PCT=$((TOTAL_QRWA_FINAL * 1000 / GRAND_TOTAL)) + echo "" + echo -e " ${BOLD}Gesamtverteilung Lifetime:${NC}" + echo -e " QMINE: ${TOTAL_QMINE_FINAL} QU (${FINAL_QMINE_PCT}‰)" + echo -e " qRWA: ${TOTAL_QRWA_FINAL} QU (${FINAL_QRWA_PCT}‰)" + + if [[ "$FINAL_QMINE_PCT" -ge 870 && "$FINAL_QMINE_PCT" -le 930 ]]; then + record_pass "Lifetime 90/10 — QMINE ${FINAL_QMINE_PCT}‰ ≈ 90%" + else + record_fail "Lifetime 90/10" "QMINE ${FINAL_QMINE_PCT}‰ (erwartet: ~900‰)" + fi +fi + +# 16e: Balance-Check eligible Holder VOR/NACH (Snapshot) +if [[ "$ELIGIBLE_COUNT" -gt 0 ]]; then + echo "" + step "Snapshot QU-Balances der eligible qRWA-Holder..." + declare -a ELIGIBLE_BALANCES=() + for eidx in $(seq 0 $((ELIGIBLE_COUNT - 1))); do + E_ID="${ELIGIBLE_IDS[$eidx]}" + BAL_OUT=$(cli_call -getbalance "$E_ID") + E_BAL=$(echo "$BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + E_BAL=${E_BAL:-0} + ELIGIBLE_BALANCES+=("$E_BAL") + SHORT_E="${E_ID:0:12}…${E_ID: -6}" + echo -e " ${SHORT_E} QU Balance: ${E_BAL}" + done +fi + +############################################### +# TEST 17: QMINE ASSET CHECK (Test Seeds) +############################################### + +header "TEST 17: QMINE Assets prüfen (Test Seeds)" + +step "Prüfe QMINE Balance für Seed 1..." +ASSETS1=$(cli_call -getasset "$IDENTITY1") +echo "$ASSETS1" | grep -iE 'QMINE|Number Of Shares' | sed 's/^/ /' || echo " (kein QMINE gefunden)" + +if echo "$ASSETS1" | grep -qi "QMINE"; then + QMINE_SHARES=$(echo "$ASSETS1" | grep -i 'Number Of Shares' | head -1 | awk '{print $NF}') + record_pass "Seed 1 QMINE — ${QMINE_SHARES:-?} Shares" +else + record_skip "Seed 1 QMINE" "Keine QMINE Shares gefunden" +fi + +step "Prüfe QMINE Balance für Seed 2..." +ASSETS2=$(cli_call -getasset "$IDENTITY2") +echo "$ASSETS2" | grep -iE 'QMINE|Number Of Shares' | sed 's/^/ /' || echo " (kein QMINE gefunden)" + +if echo "$ASSETS2" | grep -qi "QMINE"; then + QMINE_SHARES2=$(echo "$ASSETS2" | grep -i 'Number Of Shares' | head -1 | awk '{print $NF}') + record_pass "Seed 2 QMINE — ${QMINE_SHARES2:-?} Shares" +else + record_skip "Seed 2 QMINE" "Keine QMINE Shares" +fi + +fi # end --payout + +############################################### +# TEST 18: PAYOUT VERIFIKATION (Snapshot Vergleich) +############################################### + +header "TEST 18: Payout Verifikation — Balance Vorher/Nachher" +step "Vergleiche QU-Balances der ${#SNAP_CHECK_IDS[@]} Holder mit Snapshot..." + +PAYOUT_RECEIVED=0 +PAYOUT_UNCHANGED=0 +PAYOUT_DECREASED=0 + +echo "" +printf " ${BOLD}%-15s %-28s %15s %15s %15s${NC}\n" "Typ" "Identity" "Vorher" "Nachher" "Δ" +echo -e " ─────────────────────────────────────────────────────────────────────────────────────" + +for i in $(seq 0 $((${#SNAP_CHECK_IDS[@]} - 1))); do + CHECK_ID="${SNAP_CHECK_IDS[$i]}" + CHECK_TYPE="${SNAP_CHECK_TYPE[$i]}" + BAL_BEFORE="${SNAP_CHECK_BAL_BEFORE[$i]}" + + BAL_OUT=$(cli_call -getbalance "$CHECK_ID") + BAL_AFTER=$(echo "$BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + BAL_AFTER=${BAL_AFTER:-0} + + DELTA=$((BAL_AFTER - BAL_BEFORE)) + SHORT_ID="${CHECK_ID:0:12}…${CHECK_ID: -6}" + TYPE_SHORT=$(echo "$CHECK_TYPE" | cut -d: -f1) + + if [[ "$DELTA" -gt 0 ]]; then + printf " ${GREEN}✓${NC} %-13s ${SHORT_ID} %15s %15s ${GREEN}+%s${NC}\n" "$TYPE_SHORT" "$BAL_BEFORE" "$BAL_AFTER" "$DELTA" + PAYOUT_RECEIVED=$((PAYOUT_RECEIVED + 1)) + elif [[ "$DELTA" -eq 0 ]]; then + printf " ${YELLOW}─${NC} %-13s ${SHORT_ID} %15s %15s ${YELLOW}±0${NC}\n" "$TYPE_SHORT" "$BAL_BEFORE" "$BAL_AFTER" + PAYOUT_UNCHANGED=$((PAYOUT_UNCHANGED + 1)) + else + printf " ${RED}↓${NC} %-13s ${SHORT_ID} %15s %15s ${RED}%s${NC}\n" "$TYPE_SHORT" "$BAL_BEFORE" "$BAL_AFTER" "$DELTA" + PAYOUT_DECREASED=$((PAYOUT_DECREASED + 1)) + fi +done + +echo "" +echo -e " ${BOLD}Ergebnis:${NC}" +echo -e " ${GREEN}Payout erhalten: ${PAYOUT_RECEIVED}${NC}" +echo -e " ${YELLOW}Unverändert: ${PAYOUT_UNCHANGED}${NC}" +echo -e " ${RED}Balance sank: ${PAYOUT_DECREASED}${NC}" +echo "" + +# Bewertung +CHECKED_TOTAL=${#SNAP_CHECK_IDS[@]} +if [[ "$PAYOUT_RECEIVED" -gt 0 ]]; then + record_pass "Payout Verifikation — ${PAYOUT_RECEIVED}/${CHECKED_TOTAL} Holder erhielten Payout" +else + if [[ "$CHECKED_TOTAL" -eq 0 ]]; then + record_skip "Payout Verifikation" "Keine Holder im Snapshot" + else + record_fail "Payout Verifikation" "0/${CHECKED_TOTAL} Holder erhielten Payout" + fi +fi + +# Detailanalyse: qRWA-Holder speziell prüfen (die sollten 10% bekommen) +QRWA_RECEIVED=0 +QRWA_CHECKED=0 +for i in $(seq 0 $((${#SNAP_CHECK_IDS[@]} - 1))); do + TYPE_TAG=$(echo "${SNAP_CHECK_TYPE[$i]}" | cut -d: -f1) + if [[ "$TYPE_TAG" == "qRWA" || "$TYPE_TAG" == "BOTH" ]]; then + QRWA_CHECKED=$((QRWA_CHECKED + 1)) + CHECK_ID="${SNAP_CHECK_IDS[$i]}" + BAL_BEFORE="${SNAP_CHECK_BAL_BEFORE[$i]}" + BAL_OUT=$(cli_call -getbalance "$CHECK_ID") + BAL_AFTER=$(echo "$BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + BAL_AFTER=${BAL_AFTER:-0} + if [[ "$BAL_AFTER" -gt "$BAL_BEFORE" ]]; then + QRWA_RECEIVED=$((QRWA_RECEIVED + 1)) + fi + fi +done + +if [[ "$QRWA_CHECKED" -gt 0 ]]; then + if [[ "$QRWA_RECEIVED" -gt 0 ]]; then + record_pass "qRWA Holder Payout — ${QRWA_RECEIVED}/${QRWA_CHECKED} qRWA-Holder erhielten 10%-Anteil" + else + echo -e " ${YELLOW}Hinweis: qRWA-Holder brauchen ≥100K QMINE pro Share für Eligibility${NC}" + record_skip "qRWA Holder Payout" "${QRWA_RECEIVED}/${QRWA_CHECKED} — evtl. nicht eligible (QMINE < 100K/Share)" + fi +fi + +# QMINE-Holder prüfen +QMINE_RECEIVED=0 +QMINE_CHECKED=0 +for i in $(seq 0 $((${#SNAP_CHECK_IDS[@]} - 1))); do + TYPE_TAG=$(echo "${SNAP_CHECK_TYPE[$i]}" | cut -d: -f1) + if [[ "$TYPE_TAG" == "QMINE" || "$TYPE_TAG" == "BOTH" ]]; then + QMINE_CHECKED=$((QMINE_CHECKED + 1)) + CHECK_ID="${SNAP_CHECK_IDS[$i]}" + BAL_BEFORE="${SNAP_CHECK_BAL_BEFORE[$i]}" + BAL_OUT=$(cli_call -getbalance "$CHECK_ID") + BAL_AFTER=$(echo "$BAL_OUT" | grep -oE 'Balance: [0-9]+' | head -1 | awk '{print $2}') + BAL_AFTER=${BAL_AFTER:-0} + if [[ "$BAL_AFTER" -gt "$BAL_BEFORE" ]]; then + QMINE_RECEIVED=$((QMINE_RECEIVED + 1)) + fi + fi +done + +if [[ "$QMINE_CHECKED" -gt 0 ]]; then + if [[ "$QMINE_RECEIVED" -gt 0 ]]; then + record_pass "QMINE Holder Payout — ${QMINE_RECEIVED}/${QMINE_CHECKED} QMINE-Holder erhielten 90%-Anteil" + else + echo -e " ${YELLOW}Hinweis: QMINE Payouts benötigen BEGIN_EPOCH Snapshot${NC}" + record_skip "QMINE Holder Payout" "${QMINE_RECEIVED}/${QMINE_CHECKED} — evtl. kein Epoch-Snapshot" + fi +fi + +############################################### +# ZUSAMMENFASSUNG +############################################### + +header "ZUSAMMENFASSUNG" + +echo "" +echo -e " ${BOLD}Testumgebung${NC}" +echo -e " ──────────────────────────────────────────" +echo -e " Node: $NODE_IP:$NODE_PORT" +echo -e " Epoch: $CURRENT_EPOCH" +echo -e " Tick bei Start: $CURRENT_TICK" +echo -e " Contract Index: $CONTRACT_INDEX" +echo -e " Contract Identity: $QRWA_IDENTITY" +echo -e " Seed 1: $IDENTITY1" +echo -e " Seed 2: $IDENTITY2" +echo "" +echo -e " ${BOLD}Ergebnisse${NC}" +echo -e " ──────────────────────────────────────────" + +for r in "${RESULTS[@]}"; do + echo -e " $r" +done + +echo "" +echo -e " ──────────────────────────────────────────" +echo -ne " Total: ${TOTAL} | " +echo -ne "${GREEN}Pass: ${PASS}${NC} | " +echo -ne "${RED}Fail: ${FAIL}${NC} | " +echo -e "${YELLOW}Skip: ${SKIP}${NC}" +echo "" + +if [[ $FAIL -eq 0 ]]; then + echo -e " ${GREEN}${BOLD}╔═══════════════════════════════════╗${NC}" + echo -e " ${GREEN}${BOLD}║ ALLE TESTS BESTANDEN ✓ ║${NC}" + echo -e " ${GREEN}${BOLD}╚═══════════════════════════════════╝${NC}" +else + echo -e " ${RED}${BOLD}╔═══════════════════════════════════╗${NC}" + echo -e " ${RED}${BOLD}║ $FAIL TEST(S) FEHLGESCHLAGEN ✗ ║${NC}" + echo -e " ${RED}${BOLD}╚═══════════════════════════════════╝${NC}" +fi + +echo "" +echo -e " ${CYAN}Hinweise:${NC}" +echo -e " • Payout-Cycle: Alle 20 Ticks (QRWA_PAYOUT_TICK_INTERVAL)" +echo -e " • QMINE-Holder werden erst nach Epoch-Wechsel in Payout-Buffers aufgenommen" +echo -e " • Pool A = QUTIL Revenue, Pool B = User/andere SCs, Dedicated = spez. Adresse" +echo -e " • Verteilung: 90% QMINE Holder, 10% qRWA Shareholder (nur eligible!)" +echo -e " • qRWA Eligibility: ≥100K QMINE pro qRWA Share (QRWA_QMINE_PER_QRWA_SHARE_MIN)" +echo -e " • TEST 15 zeigt alle qRWA-Owner + deren QMINE und Eligibility-Status" +echo -e " • -enabletestcontracts ist Pflicht für Contract Index >= 10" +echo "" + +exit $FAIL From 017f8398b3fd11f24efafae61c9fd306cba2c753 Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:24:43 -0300 Subject: [PATCH 13/30] test(qRWA): add comprehensive Gov Proposal lifecycle test (TEST 10a-g) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 10a: Read current GovParams baseline - 10b: Check active Gov Polls count before proposal - 10c: Create new Gov Proposal (Seed1) with modified percentages (Elec 300‰ + Maint 100‰ + Reinv 100‰ = 500‰) - 10d: Second vote on same proposal (Seed2) — tests matching logic - 10e: Verify active Gov Polls increased + GetGovPoll details - 10f: Create competing proposal with different params (Seed3) - 10g: Final overview of all active polls with details Tests cover: proposal creation, duplicate matching, competing proposals, GetActiveGovPollIds, GetGovPoll detail query, and status codes. --- test_qrwa.sh | 279 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 250 insertions(+), 29 deletions(-) diff --git a/test_qrwa.sh b/test_qrwa.sh index 5ef6262a7..759b3d9ce 100755 --- a/test_qrwa.sh +++ b/test_qrwa.sh @@ -40,10 +40,10 @@ DEDICATED_IDENTITY="PDQTKKIRSIGAGAOLJWZWTCBSFCYAIZIRYCHEBKHBJHHBJNJHWLYGXSVEQEFC QMINE_ISSUER="QMINEQQXYBEGBHNSUPOUYDIQKZPCBPQIIHUUZMCPLBPCCAIARVZBTYKGFCWM" QMINE_NAME="QMINE" -SCHEDULE_TICK=20 +SCHEDULE_TICK=5 SEND_AMOUNT=5000000 # 5M QU für Pool B Test DEDICATED_AMOUNT=3000000 # 3M QU für Dedicated Pool Test -TX_WAIT_SEC=45 # Wartezeit für TX-Bestätigung +TX_WAIT_SEC=10 # Wartezeit für TX-Bestätigung ############################################### # FARBEN & ZAEHLER @@ -602,17 +602,16 @@ fi fi # end --send ############################################### -# TEST 10: GOVERNANCE VOTE (Procedure 4) +# TEST 10: GOVERNANCE PROPOSAL FULL LIFECYCLE ############################################### if [[ "$MODE" == "all" || "$MODE" == "--vote" ]]; then -header "TEST 10: Governance Vote (Procedure 4)" -step "Seed 1 hat 2000 QMINE → kann voten" -step "Input: QRWAGovParams = { admin, elec, maint, reinv, dev, elec%, maint%, reinv% }" - -# Lese aktuelle Governance-Parameter dynamisch aus dem Contract -step "Lese aktuelle GovParams vom Contract..." +# ═══════════════════════════════════════════ +# 10a: Lese aktuelle GovParams +# ═══════════════════════════════════════════ +header "TEST 10a: Aktuelle Gov Params lesen (Function 1)" +step "Lese aktuelle GovParams als Baseline..." VOTE_GOV_OUTPUT=$(call_fn 1 "" "{ { id, id, id, id, id, uint64, uint64, uint64 } }") VOTE_GOV_ADDRS=$(echo "$VOTE_GOV_OUTPUT" | grep -oE '[A-Z]{55,60}' | head -5) ADMIN_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '1p') @@ -620,25 +619,72 @@ ELEC_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '2p') MAINT_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '3p') REINV_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '4p') DEV_ID=$(echo "$VOTE_GOV_ADDRS" | sed -n '5p') + +# Extrahiere aktuelle Prozentsätze +CURRENT_PCTS=$(echo "$VOTE_GOV_OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') +CURRENT_ELEC_PCT=$(echo "$CURRENT_PCTS" | tail -3 | head -1) +CURRENT_MAINT_PCT=$(echo "$CURRENT_PCTS" | tail -2 | head -1) +CURRENT_REINV_PCT=$(echo "$CURRENT_PCTS" | tail -1) + +echo -e " ${CYAN}Aktuelle Gov Params:${NC}" echo -e " Admin: ${ADMIN_ID:-?}" -echo -e " Elec: ${ELEC_ID:-?}" -echo -e " Maint: ${MAINT_ID:-?}" -echo -e " Reinv: ${REINV_ID:-?}" +echo -e " Elec: ${ELEC_ID:-?} (${CURRENT_ELEC_PCT:-?}‰)" +echo -e " Maint: ${MAINT_ID:-?} (${CURRENT_MAINT_PCT:-?}‰)" +echo -e " Reinv: ${REINV_ID:-?} (${CURRENT_REINV_PCT:-?}‰)" echo -e " Dev: ${DEV_ID:-?}" -# invokecontractprocedure -# Proc 4 = VoteGovParams, Amount = 0 (wird refunded) +if [[ -n "$ADMIN_ID" ]]; then + record_pass "Gov Params gelesen — Admin: ${ADMIN_ID:0:12}…" +else + record_fail "Gov Params" "Keine Adressen gefunden" +fi + +# ═══════════════════════════════════════════ +# 10b: Prüfe Active Gov Polls VOR Proposal +# ═══════════════════════════════════════════ +header "TEST 10b: Active Gov Polls VOR Proposal (Function 8)" +step "GetActiveGovPollIds — wie viele aktive Polls gibt es aktuell?" +GOV_POLLS_BEFORE=$(call_fn 8 "" "{ uint64, [64; uint64] }") +GOV_COUNT_BEFORE=$(echo "$GOV_POLLS_BEFORE" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) +GOV_COUNT_BEFORE=${GOV_COUNT_BEFORE:-0} +echo -e " Aktive Gov Polls: ${GOV_COUNT_BEFORE}" + +if [[ "$GOV_COUNT_BEFORE" -gt 0 ]]; then + # Extrahiere Poll IDs + GOV_IDS_RAW=$(echo "$GOV_POLLS_BEFORE" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -n +2 | head -"$GOV_COUNT_BEFORE") + echo -e " Poll IDs: $(echo $GOV_IDS_RAW | tr '\n' ' ')" +fi +record_pass "GetActiveGovPollIds — Count: ${GOV_COUNT_BEFORE}" + +# ═══════════════════════════════════════════ +# 10c: Neues Gov Proposal erstellen (VoteGovParams mit geänderten Prozentsätzen) +# ═══════════════════════════════════════════ +header "TEST 10c: Neues Gov Proposal erstellen (Procedure 4 — VoteGovParams)" + +# Erstelle Proposal mit geänderten Werten: +# Electricity: 300‰ (statt 350), Maintenance: 100‰ (statt 50), Reinvestment: 100‰ (gleich) +# → Gesamt bleibt 500‰ = 50% +NEW_ELEC_PCT=300 +NEW_MAINT_PCT=100 +NEW_REINV_PCT=100 +NEW_TOTAL=$((NEW_ELEC_PCT + NEW_MAINT_PCT + NEW_REINV_PCT)) + +step "Neues Proposal: Elec ${NEW_ELEC_PCT}‰ + Maint ${NEW_MAINT_PCT}‰ + Reinv ${NEW_REINV_PCT}‰ = ${NEW_TOTAL}‰" +step "Seed 1 erstellt+votet auf dieses Proposal..." + +# Proc 4 = VoteGovParams +# Input: QRWAGovParams = { admin, electricity, maintenance, reinvestment, qmineDev, electricityPercent, maintenancePercent, reinvestmentPercent } TX_OUTPUT=$(cli_call_seed "$SEED1" \ -enabletestcontracts \ -invokecontractprocedure "$CONTRACT_INDEX" 4 0 \ - "{${ADMIN_ID}id,${ELEC_ID}id,${MAINT_ID}id,${REINV_ID}id,${DEV_ID}id,350uint64,50uint64,100uint64}") + "{${ADMIN_ID}id,${ELEC_ID}id,${MAINT_ID}id,${REINV_ID}id,${DEV_ID}id,${NEW_ELEC_PCT}uint64,${NEW_MAINT_PCT}uint64,${NEW_REINV_PCT}uint64}") echo "$TX_OUTPUT" | sed 's/^/ /' TX_HASH=$(echo "$TX_OUTPUT" | grep "TxHash:" | awk '{print $2}') TX_TICK=$(echo "$TX_OUTPUT" | grep "Tick:" | awk '{print $2}') if [[ -z "$TX_HASH" || -z "$TX_TICK" ]]; then - record_fail "VoteGovParams" "TX konnte nicht gesendet werden" + record_fail "VoteGovParams (Seed1)" "TX konnte nicht gesendet werden" else echo "" step "Warte ${TX_WAIT_SEC}s auf Bestätigung..." @@ -646,25 +692,200 @@ else echo "$CHECK" | sed 's/^/ /' if echo "$CHECK" | grep -q "MoneyFlew: Yes"; then - record_pass "VoteGovParams — TX bestätigt, MoneyFlew: Yes" - - # Prüfe ob Gov Poll angelegt wurde - step "Prüfe aktive Gov Polls..." - OUTPUT=$(call_fn 8 "" "{ uint64, [64; uint64] }") - GOV_COUNT=$(echo "$OUTPUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) - if [[ "${GOV_COUNT:-0}" -gt 0 ]]; then - record_pass "Gov Poll erstellt — Count: $GOV_COUNT" + record_pass "VoteGovParams (Seed1) — Proposal erstellt, MoneyFlew: Yes" + elif echo "$CHECK" | grep -q "MoneyFlew: N/A"; then + record_skip "VoteGovParams (Seed1)" "TX noch nicht bestätigt" + else + record_pass "VoteGovParams (Seed1) — TX verarbeitet (Refund = MoneyFlew: No ist OK)" + fi +fi + +# ═══════════════════════════════════════════ +# 10d: Seed 2 votet auf das GLEICHE Proposal +# ═══════════════════════════════════════════ +header "TEST 10d: Zweiter Vote auf gleiches Proposal (Seed 2)" +step "Seed 2 votet mit identischen Params → findet bestehendes Proposal" +step "Gleiche Params: Elec ${NEW_ELEC_PCT}‰ + Maint ${NEW_MAINT_PCT}‰ + Reinv ${NEW_REINV_PCT}‰" + +TX_OUTPUT2=$(cli_call_seed "$SEED2" \ + -enabletestcontracts \ + -invokecontractprocedure "$CONTRACT_INDEX" 4 0 \ + "{${ADMIN_ID}id,${ELEC_ID}id,${MAINT_ID}id,${REINV_ID}id,${DEV_ID}id,${NEW_ELEC_PCT}uint64,${NEW_MAINT_PCT}uint64,${NEW_REINV_PCT}uint64}") +echo "$TX_OUTPUT2" | sed 's/^/ /' + +TX_HASH2=$(echo "$TX_OUTPUT2" | grep "TxHash:" | awk '{print $2}') +TX_TICK2=$(echo "$TX_OUTPUT2" | grep "Tick:" | awk '{print $2}') + +if [[ -z "$TX_HASH2" || -z "$TX_TICK2" ]]; then + record_fail "VoteGovParams (Seed2)" "TX konnte nicht gesendet werden" +else + echo "" + step "Warte ${TX_WAIT_SEC}s auf Bestätigung..." + CHECK2=$(wait_and_check_tx "$TX_TICK2" "$TX_HASH2") + echo "$CHECK2" | sed 's/^/ /' + + if echo "$CHECK2" | grep -q "MoneyFlew: Yes"; then + record_pass "VoteGovParams (Seed2) — Zweiter Vote, MoneyFlew: Yes" + elif echo "$CHECK2" | grep -q "MoneyFlew: N/A"; then + record_skip "VoteGovParams (Seed2)" "TX noch nicht bestätigt" + else + record_pass "VoteGovParams (Seed2) — TX verarbeitet (Refund = MoneyFlew: No ist OK)" + fi +fi + +# ═══════════════════════════════════════════ +# 10e: Prüfe Active Gov Polls NACH Proposal +# ═══════════════════════════════════════════ +header "TEST 10e: Active Gov Polls NACH Proposal (Function 8)" +sleep 5 # Kurz warten bis State aktualisiert +GOV_POLLS_AFTER=$(call_fn 8 "" "{ uint64, [64; uint64] }") +GOV_COUNT_AFTER=$(echo "$GOV_POLLS_AFTER" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) +GOV_COUNT_AFTER=${GOV_COUNT_AFTER:-0} +echo -e " Aktive Gov Polls vorher: ${GOV_COUNT_BEFORE} → nachher: ${GOV_COUNT_AFTER}" + +if [[ "$GOV_COUNT_AFTER" -gt 0 ]]; then + GOV_IDS_AFTER=$(echo "$GOV_POLLS_AFTER" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -n +2 | head -"$GOV_COUNT_AFTER") + echo -e " Poll IDs: $(echo $GOV_IDS_AFTER | tr '\n' ' ')" + + # Hol Details des ersten aktiven Polls via GetGovPoll (Function 2) + FIRST_POLL_ID=$(echo "$GOV_IDS_AFTER" | head -1) + if [[ -n "$FIRST_POLL_ID" ]]; then + step "Lese Gov Poll Details für ID ${FIRST_POLL_ID} (Function 2)..." + # GetGovPoll Input: { uint64 proposalId } + # Output: { { uint64 proposalId, uint64 status, uint64 score, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 status } + POLL_DETAIL=$(call_fn 2 "${FIRST_POLL_ID}uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") + echo "$POLL_DETAIL" | grep -v "WARNING" | sed 's/^/ /' + + POLL_STATUS_VAL=$(echo "$POLL_DETAIL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -1) + if [[ "${POLL_STATUS_VAL}" == "1" ]]; then + # Extrahiere Proposal-Details + POLL_NUMS=$(echo "$POLL_DETAIL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') + POLL_ID_RET=$(echo "$POLL_NUMS" | sed -n '1p') + POLL_STAT=$(echo "$POLL_NUMS" | sed -n '2p') + POLL_SCORE=$(echo "$POLL_NUMS" | sed -n '3p') + echo "" + echo -e " ${CYAN}Proposal ID: ${POLL_ID_RET}${NC}" + echo -e " ${CYAN}Status: ${POLL_STAT} (1=Active, 2=Passed, 3=Failed)${NC}" + echo -e " ${CYAN}Score (Votes): ${POLL_SCORE}${NC}" + + # Prozentsätze aus dem Proposal + POLL_ADDRS=$(echo "$POLL_DETAIL" | grep -oE '[A-Z]{55,60}') + POLL_LAST_NUMS=$(echo "$POLL_NUMS" | tail -4) + PROP_ELEC=$(echo "$POLL_LAST_NUMS" | sed -n '1p') + PROP_MAINT=$(echo "$POLL_LAST_NUMS" | sed -n '2p') + PROP_REINV=$(echo "$POLL_LAST_NUMS" | sed -n '3p') + echo -e " ${CYAN}Elec%: ${PROP_ELEC:-?}‰ Maint%: ${PROP_MAINT:-?}‰ Reinv%: ${PROP_REINV:-?}‰${NC}" + + record_pass "GetGovPoll — ID: ${POLL_ID_RET}, Status: ${POLL_STAT}, Score: ${POLL_SCORE}" else - record_skip "Gov Poll Count" "Count ist 0 (ggf. kein QMINE-Balance)" + record_fail "GetGovPoll" "Poll nicht gefunden (Status: ${POLL_STATUS_VAL})" fi - elif echo "$CHECK" | grep -q "MoneyFlew: N/A"; then - record_skip "VoteGovParams" "TX noch nicht bestätigt" + fi + + if [[ "$GOV_COUNT_AFTER" -ge "$GOV_COUNT_BEFORE" ]]; then + record_pass "Active Gov Polls gestiegen — ${GOV_COUNT_BEFORE} → ${GOV_COUNT_AFTER}" + else + record_skip "Active Gov Polls" "Count: ${GOV_COUNT_AFTER} (evtl. END_EPOCH dazwischen)" + fi +else + record_fail "Active Gov Polls" "Keine aktiven Polls nach VoteGovParams" +fi + +# ═══════════════════════════════════════════ +# 10f: Erstelle ein KONKURRIERENDES Proposal mit anderen Werten +# ═══════════════════════════════════════════ +header "TEST 10f: Konkurrierendes Gov Proposal (andere Prozentsätze)" + +# Proposal 2: Electricity 400‰, Maintenance 50‰, Reinvestment 50‰ = 500‰ +ALT_ELEC_PCT=400 +ALT_MAINT_PCT=50 +ALT_REINV_PCT=50 +ALT_TOTAL=$((ALT_ELEC_PCT + ALT_MAINT_PCT + ALT_REINV_PCT)) + +step "Alternatives Proposal: Elec ${ALT_ELEC_PCT}‰ + Maint ${ALT_MAINT_PCT}‰ + Reinv ${ALT_REINV_PCT}‰ = ${ALT_TOTAL}‰" +step "Seed 3 (Dedicated Address) votet auf dieses alternative Proposal..." + +TX_OUTPUT3=$(cli_call_seed "$SEED3" \ + -enabletestcontracts \ + -invokecontractprocedure "$CONTRACT_INDEX" 4 0 \ + "{${ADMIN_ID}id,${ELEC_ID}id,${MAINT_ID}id,${REINV_ID}id,${DEV_ID}id,${ALT_ELEC_PCT}uint64,${ALT_MAINT_PCT}uint64,${ALT_REINV_PCT}uint64}") +echo "$TX_OUTPUT3" | sed 's/^/ /' + +TX_HASH3=$(echo "$TX_OUTPUT3" | grep "TxHash:" | awk '{print $2}') +TX_TICK3=$(echo "$TX_OUTPUT3" | grep "Tick:" | awk '{print $2}') + +if [[ -z "$TX_HASH3" || -z "$TX_TICK3" ]]; then + record_fail "VoteGovParams (Seed3)" "TX konnte nicht gesendet werden" +else + echo "" + step "Warte ${TX_WAIT_SEC}s auf Bestätigung..." + CHECK3=$(wait_and_check_tx "$TX_TICK3" "$TX_HASH3") + echo "$CHECK3" | sed 's/^/ /' + + if echo "$CHECK3" | grep -q "MoneyFlew: Yes"; then + record_pass "VoteGovParams (Seed3) — Konkurrierendes Proposal, MoneyFlew: Yes" + elif echo "$CHECK3" | grep -q "MoneyFlew: N/A"; then + record_skip "VoteGovParams (Seed3)" "TX noch nicht bestätigt" else - # MoneyFlew: No kann OK sein wenn kein QMINE → Einsatz wird refunded - record_pass "VoteGovParams — TX verarbeitet (MoneyFlew kann No sein bei Refund)" + # Seed3 hat evtl. kein QMINE → wird abgelehnt (NOT_AUTHORIZED) + record_skip "VoteGovParams (Seed3)" "TX verarbeitet (braucht QMINE, Seed3 hat evtl. keins)" fi fi +# ═══════════════════════════════════════════ +# 10g: Final — Alle aktiven Gov Polls auflisten + Details +# ═══════════════════════════════════════════ +header "TEST 10g: Finale Gov Poll Übersicht" +sleep 3 +GOV_POLLS_FINAL=$(call_fn 8 "" "{ uint64, [64; uint64] }") +GOV_COUNT_FINAL=$(echo "$GOV_POLLS_FINAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) +GOV_COUNT_FINAL=${GOV_COUNT_FINAL:-0} +echo -e " ${BOLD}Aktive Gov Polls: ${GOV_COUNT_FINAL}${NC}" + +if [[ "$GOV_COUNT_FINAL" -gt 0 ]]; then + GOV_IDS_FINAL=$(echo "$GOV_POLLS_FINAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -n +2 | head -"$GOV_COUNT_FINAL") + + # Iteriere über alle aktiven Polls und zeige Details + POLL_NUM=0 + while IFS= read -r PID; do + POLL_NUM=$((POLL_NUM + 1)) + POLL_OUT=$(call_fn 2 "${PID}uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") + P_NUMS=$(echo "$POLL_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') + P_ID=$(echo "$P_NUMS" | sed -n '1p') + P_STAT=$(echo "$P_NUMS" | sed -n '2p') + P_SCORE=$(echo "$P_NUMS" | sed -n '3p') + # Letzten 4 Zahlen (vor dem status): elec%, maint%, reinv%, query_status + P_LAST_NUMS=$(echo "$P_NUMS" | tail -4) + P_ELEC=$(echo "$P_LAST_NUMS" | sed -n '1p') + P_MAINT=$(echo "$P_LAST_NUMS" | sed -n '2p') + P_REINV=$(echo "$P_LAST_NUMS" | sed -n '3p') + + # Status-Label + case "$P_STAT" in + 0) STAT_LABEL="Empty" ;; + 1) STAT_LABEL="Active" ;; + 2) STAT_LABEL="Passed" ;; + 3) STAT_LABEL="Failed" ;; + *) STAT_LABEL="Unknown($P_STAT)" ;; + esac + + echo -e " ${CYAN}━━━ Poll ${POLL_NUM} ━━━${NC}" + echo -e " ID: ${P_ID} Status: ${STAT_LABEL} Score: ${P_SCORE} QMINE" + echo -e " Elec: ${P_ELEC:-?}‰ Maint: ${P_MAINT:-?}‰ Reinv: ${P_REINV:-?}‰" + done <<< "$GOV_IDS_FINAL" + + record_pass "Gov Poll Übersicht — ${GOV_COUNT_FINAL} aktive Polls" +else + record_skip "Gov Poll Übersicht" "Keine aktiven Polls (evtl. alle nach END_EPOCH abgeschlossen)" +fi + +echo "" +echo -e " ${YELLOW}══════════════════════════════════════════════════${NC}" +echo -e " ${YELLOW}Hinweis: Gov Proposals werden erst bei END_EPOCH ausgewertet.${NC}" +echo -e " ${YELLOW}Das Proposal mit dem höchsten Score (min 2/3 Quorum)${NC}" +echo -e " ${YELLOW}wird dann als neue GovParams übernommen.${NC}" +echo -e " ${YELLOW}══════════════════════════════════════════════════${NC}" + fi # end --vote ############################################### From 4200cc1299067ccfd6e62bf96b2a0841c1d25675 Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:31:20 -0300 Subject: [PATCH 14/30] fix(test): scan Gov Polls by ID (0..15) instead of active-only GetActiveGovPollIds (Fn 8) only returns Status=Active polls. After END_EPOCH, all polls are resolved (Passed/Failed) and disappear from the active list. Now TEST 10e+10g use GetGovPoll (Fn 2) to scan IDs 0..15, finding polls in ALL states (Active/Passed/Failed/Empty). Shows color-coded summary with counts per status. --- test_qrwa.sh | 190 +++++++++++++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 81 deletions(-) diff --git a/test_qrwa.sh b/test_qrwa.sh index 759b3d9ce..e37b007e4 100755 --- a/test_qrwa.sh +++ b/test_qrwa.sh @@ -734,61 +734,80 @@ else fi # ═══════════════════════════════════════════ -# 10e: Prüfe Active Gov Polls NACH Proposal +# 10e: Prüfe Gov Polls NACH Proposals — scanne per ID (auch resolved/historische) # ═══════════════════════════════════════════ -header "TEST 10e: Active Gov Polls NACH Proposal (Function 8)" +header "TEST 10e: Gov Polls NACH Proposal — Scan per ID (Function 2 + 8)" sleep 5 # Kurz warten bis State aktualisiert + +# Zuerst: aktive Polls abfragen GOV_POLLS_AFTER=$(call_fn 8 "" "{ uint64, [64; uint64] }") GOV_COUNT_AFTER=$(echo "$GOV_POLLS_AFTER" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) GOV_COUNT_AFTER=${GOV_COUNT_AFTER:-0} -echo -e " Aktive Gov Polls vorher: ${GOV_COUNT_BEFORE} → nachher: ${GOV_COUNT_AFTER}" +echo -e " Aktive Gov Polls (Status=Active): ${GOV_COUNT_AFTER}" if [[ "$GOV_COUNT_AFTER" -gt 0 ]]; then GOV_IDS_AFTER=$(echo "$GOV_POLLS_AFTER" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -n +2 | head -"$GOV_COUNT_AFTER") - echo -e " Poll IDs: $(echo $GOV_IDS_AFTER | tr '\n' ' ')" - - # Hol Details des ersten aktiven Polls via GetGovPoll (Function 2) - FIRST_POLL_ID=$(echo "$GOV_IDS_AFTER" | head -1) - if [[ -n "$FIRST_POLL_ID" ]]; then - step "Lese Gov Poll Details für ID ${FIRST_POLL_ID} (Function 2)..." - # GetGovPoll Input: { uint64 proposalId } - # Output: { { uint64 proposalId, uint64 status, uint64 score, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 status } - POLL_DETAIL=$(call_fn 2 "${FIRST_POLL_ID}uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") - echo "$POLL_DETAIL" | grep -v "WARNING" | sed 's/^/ /' - - POLL_STATUS_VAL=$(echo "$POLL_DETAIL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -1) - if [[ "${POLL_STATUS_VAL}" == "1" ]]; then - # Extrahiere Proposal-Details - POLL_NUMS=$(echo "$POLL_DETAIL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') - POLL_ID_RET=$(echo "$POLL_NUMS" | sed -n '1p') - POLL_STAT=$(echo "$POLL_NUMS" | sed -n '2p') - POLL_SCORE=$(echo "$POLL_NUMS" | sed -n '3p') - echo "" - echo -e " ${CYAN}Proposal ID: ${POLL_ID_RET}${NC}" - echo -e " ${CYAN}Status: ${POLL_STAT} (1=Active, 2=Passed, 3=Failed)${NC}" - echo -e " ${CYAN}Score (Votes): ${POLL_SCORE}${NC}" - - # Prozentsätze aus dem Proposal - POLL_ADDRS=$(echo "$POLL_DETAIL" | grep -oE '[A-Z]{55,60}') - POLL_LAST_NUMS=$(echo "$POLL_NUMS" | tail -4) - PROP_ELEC=$(echo "$POLL_LAST_NUMS" | sed -n '1p') - PROP_MAINT=$(echo "$POLL_LAST_NUMS" | sed -n '2p') - PROP_REINV=$(echo "$POLL_LAST_NUMS" | sed -n '3p') - echo -e " ${CYAN}Elec%: ${PROP_ELEC:-?}‰ Maint%: ${PROP_MAINT:-?}‰ Reinv%: ${PROP_REINV:-?}‰${NC}" - - record_pass "GetGovPoll — ID: ${POLL_ID_RET}, Status: ${POLL_STAT}, Score: ${POLL_SCORE}" - else - record_fail "GetGovPoll" "Poll nicht gefunden (Status: ${POLL_STATUS_VAL})" + echo -e " Aktive Poll IDs: $(echo $GOV_IDS_AFTER | tr '\n' ' ')" +fi + +# Dann: Scanne Proposal IDs 0..15 per GetGovPoll (Fn 2) — findet auch resolved Polls +step "Scanne Gov Poll IDs 0..15 per GetGovPoll (auch Passed/Failed)..." +GOV_FOUND_COUNT=0 +GOV_FOUND_IDS=() +GOV_FOUND_DETAILS=() + +for SCAN_ID in $(seq 0 15); do + SCAN_OUT=$(call_fn 2 "${SCAN_ID}uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") + SCAN_STATUS=$(echo "$SCAN_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -1) + + if [[ "${SCAN_STATUS}" == "1" ]]; then + # Found — extrahiere Details + SCAN_NUMS=$(echo "$SCAN_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') + S_ID=$(echo "$SCAN_NUMS" | sed -n '1p') + S_STAT=$(echo "$SCAN_NUMS" | sed -n '2p') + S_SCORE=$(echo "$SCAN_NUMS" | sed -n '3p') + S_LAST=$(echo "$SCAN_NUMS" | tail -4) + S_ELEC=$(echo "$S_LAST" | sed -n '1p') + S_MAINT=$(echo "$S_LAST" | sed -n '2p') + S_REINV=$(echo "$S_LAST" | sed -n '3p') + + # Status-Label + case "$S_STAT" in + 0) S_LABEL="Empty" ;; + 1) S_LABEL="Active" ;; + 2) S_LABEL="Passed" ;; + 3) S_LABEL="Failed" ;; + 4) S_LABEL="PassedFailedExec" ;; + *) S_LABEL="Unknown($S_STAT)" ;; + esac + + GOV_FOUND_COUNT=$((GOV_FOUND_COUNT + 1)) + GOV_FOUND_IDS+=("$S_ID") + echo -e " ${CYAN}━━━ Poll ID: ${S_ID} ━━━${NC}" + echo -e " Status: ${S_LABEL} Score: ${S_SCORE} QMINE" + echo -e " Elec: ${S_ELEC:-?}‰ Maint: ${S_MAINT:-?}‰ Reinv: ${S_REINV:-?}‰" + + # Zeige Adressen + SCAN_ADDRS=$(echo "$SCAN_OUT" | grep -oE '[A-Z]{55,60}') + S_ADMIN=$(echo "$SCAN_ADDRS" | head -1) + if [[ -n "$S_ADMIN" ]]; then + echo -e " Admin: ${S_ADMIN:0:12}…${S_ADMIN: -6}" fi fi +done - if [[ "$GOV_COUNT_AFTER" -ge "$GOV_COUNT_BEFORE" ]]; then - record_pass "Active Gov Polls gestiegen — ${GOV_COUNT_BEFORE} → ${GOV_COUNT_AFTER}" - else - record_skip "Active Gov Polls" "Count: ${GOV_COUNT_AFTER} (evtl. END_EPOCH dazwischen)" - fi +echo "" +echo -e " ${BOLD}Gefundene Gov Polls (alle Status): ${GOV_FOUND_COUNT}${NC}" +echo -e " Davon aktiv: ${GOV_COUNT_AFTER}" + +if [[ "$GOV_FOUND_COUNT" -gt 0 ]]; then + record_pass "Gov Polls gefunden — ${GOV_FOUND_COUNT} total (${GOV_COUNT_AFTER} aktiv)" +elif [[ "$GOV_COUNT_AFTER" -gt 0 ]]; then + record_pass "Aktive Gov Polls: ${GOV_COUNT_AFTER}" else - record_fail "Active Gov Polls" "Keine aktiven Polls nach VoteGovParams" + echo -e " ${YELLOW}Keine Polls gefunden — möglicherweise hat VoteGovParams fehlgeschlagen${NC}" + echo -e " ${YELLOW}(Seed muss QMINE halten, Prozentsätze ≤ 1000‰, Admin ≠ NULL)${NC}" + record_skip "Gov Polls" "Keine Polls (IDs 0..15 leer, evtl. kein QMINE oder Epoch-Reset)" fi # ═══════════════════════════════════════════ @@ -833,57 +852,66 @@ else fi # ═══════════════════════════════════════════ -# 10g: Final — Alle aktiven Gov Polls auflisten + Details +# 10g: Final — Alle Gov Polls auflisten (aktiv + historisch) # ═══════════════════════════════════════════ -header "TEST 10g: Finale Gov Poll Übersicht" +header "TEST 10g: Finale Gov Poll Übersicht (alle Status)" sleep 3 -GOV_POLLS_FINAL=$(call_fn 8 "" "{ uint64, [64; uint64] }") -GOV_COUNT_FINAL=$(echo "$GOV_POLLS_FINAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | head -1) -GOV_COUNT_FINAL=${GOV_COUNT_FINAL:-0} -echo -e " ${BOLD}Aktive Gov Polls: ${GOV_COUNT_FINAL}${NC}" - -if [[ "$GOV_COUNT_FINAL" -gt 0 ]]; then - GOV_IDS_FINAL=$(echo "$GOV_POLLS_FINAL" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -n +2 | head -"$GOV_COUNT_FINAL") - - # Iteriere über alle aktiven Polls und zeige Details - POLL_NUM=0 - while IFS= read -r PID; do - POLL_NUM=$((POLL_NUM + 1)) - POLL_OUT=$(call_fn 2 "${PID}uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") - P_NUMS=$(echo "$POLL_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') - P_ID=$(echo "$P_NUMS" | sed -n '1p') - P_STAT=$(echo "$P_NUMS" | sed -n '2p') - P_SCORE=$(echo "$P_NUMS" | sed -n '3p') - # Letzten 4 Zahlen (vor dem status): elec%, maint%, reinv%, query_status - P_LAST_NUMS=$(echo "$P_NUMS" | tail -4) - P_ELEC=$(echo "$P_LAST_NUMS" | sed -n '1p') - P_MAINT=$(echo "$P_LAST_NUMS" | sed -n '2p') - P_REINV=$(echo "$P_LAST_NUMS" | sed -n '3p') - # Status-Label - case "$P_STAT" in - 0) STAT_LABEL="Empty" ;; - 1) STAT_LABEL="Active" ;; - 2) STAT_LABEL="Passed" ;; - 3) STAT_LABEL="Failed" ;; - *) STAT_LABEL="Unknown($P_STAT)" ;; +# Scanne durch IDs 0..15 und zeige alle die existieren +step "Scanne Gov Poll IDs 0..15 (aktiv + resolved)..." +FINAL_FOUND=0 +FINAL_ACTIVE=0 +FINAL_PASSED=0 +FINAL_FAILED=0 + +for FID in $(seq 0 15); do + F_OUT=$(call_fn 2 "${FID}uint64" "{ { uint64, uint64, uint64, { id, id, id, id, id, uint64, uint64, uint64 } }, uint64 }") + F_QUERY_STATUS=$(echo "$F_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+' | tail -1) + + if [[ "${F_QUERY_STATUS}" == "1" ]]; then + F_NUMS=$(echo "$F_OUT" | sed -n '/Contract Function Output/,$ p' | grep -oE '[0-9]+') + F_ID=$(echo "$F_NUMS" | sed -n '1p') + F_STAT=$(echo "$F_NUMS" | sed -n '2p') + F_SCORE=$(echo "$F_NUMS" | sed -n '3p') + F_LAST=$(echo "$F_NUMS" | tail -4) + F_ELEC=$(echo "$F_LAST" | sed -n '1p') + F_MAINT=$(echo "$F_LAST" | sed -n '2p') + F_REINV=$(echo "$F_LAST" | sed -n '3p') + + case "$F_STAT" in + 0) F_LABEL="Empty"; F_COLOR="$NC" ;; + 1) F_LABEL="Active"; F_COLOR="$GREEN"; FINAL_ACTIVE=$((FINAL_ACTIVE + 1)) ;; + 2) F_LABEL="Passed"; F_COLOR="$GREEN"; FINAL_PASSED=$((FINAL_PASSED + 1)) ;; + 3) F_LABEL="Failed"; F_COLOR="$RED"; FINAL_FAILED=$((FINAL_FAILED + 1)) ;; + 4) F_LABEL="PassedFailedExec"; F_COLOR="$YELLOW" ;; + *) F_LABEL="Unknown($F_STAT)"; F_COLOR="$YELLOW" ;; esac - echo -e " ${CYAN}━━━ Poll ${POLL_NUM} ━━━${NC}" - echo -e " ID: ${P_ID} Status: ${STAT_LABEL} Score: ${P_SCORE} QMINE" - echo -e " Elec: ${P_ELEC:-?}‰ Maint: ${P_MAINT:-?}‰ Reinv: ${P_REINV:-?}‰" - done <<< "$GOV_IDS_FINAL" + FINAL_FOUND=$((FINAL_FOUND + 1)) + echo -e " ${F_COLOR}━━━ Poll ID: ${F_ID} [${F_LABEL}] Score: ${F_SCORE} QMINE${NC}" + echo -e " Elec: ${F_ELEC:-?}‰ Maint: ${F_MAINT:-?}‰ Reinv: ${F_REINV:-?}‰" + fi +done - record_pass "Gov Poll Übersicht — ${GOV_COUNT_FINAL} aktive Polls" +echo "" +echo -e " ${BOLD}════════ Gov Poll Zusammenfassung ════════${NC}" +echo -e " ${BOLD}Gefunden: ${FINAL_FOUND}${NC}" +echo -e " ${GREEN}Active: ${FINAL_ACTIVE}${NC}" +echo -e " ${GREEN}Passed: ${FINAL_PASSED}${NC}" +echo -e " ${RED}Failed: ${FINAL_FAILED}${NC}" + +if [[ "$FINAL_FOUND" -gt 0 ]]; then + record_pass "Gov Poll Übersicht — ${FINAL_FOUND} Polls (${FINAL_ACTIVE} aktiv, ${FINAL_PASSED} passed, ${FINAL_FAILED} failed)" else - record_skip "Gov Poll Übersicht" "Keine aktiven Polls (evtl. alle nach END_EPOCH abgeschlossen)" + record_skip "Gov Poll Übersicht" "Keine Polls gefunden (IDs 0..15)" fi echo "" echo -e " ${YELLOW}══════════════════════════════════════════════════${NC}" -echo -e " ${YELLOW}Hinweis: Gov Proposals werden erst bei END_EPOCH ausgewertet.${NC}" +echo -e " ${YELLOW}Hinweis: Gov Proposals werden bei END_EPOCH ausgewertet.${NC}" echo -e " ${YELLOW}Das Proposal mit dem höchsten Score (min 2/3 Quorum)${NC}" echo -e " ${YELLOW}wird dann als neue GovParams übernommen.${NC}" +echo -e " ${YELLOW}Status: 1=Active, 2=Passed, 3=Failed${NC}" echo -e " ${YELLOW}══════════════════════════════════════════════════${NC}" fi # end --vote From c7bd86a025ead4228733b3528a3263ea2bd1466c Mon Sep 17 00:00:00 2001 From: MZoxx <148331637+MZoxx@users.noreply.github.com> Date: Wed, 25 Feb 2026 01:52:09 -0300 Subject: [PATCH 15/30] feat(test): QMINE Pre-Check + QX-Kauf vor Gov Votes, fix Poll Parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TEST 10-PRE: Prüft QMINE Balance für alle 3 Seeds vor VoteGovParams - Kauft automatisch 100 QMINE via QX Bid Order (5000 QU/Share) - Re-Check nach Kauf mit Warnung bei Fehlschlag - Fix Gov Poll Parsing (TEST 10e + 10g): - Handhabt (empty) ContractObject korrekt (leere QRWAGovParams) - Zählt extrahierte Nummern, parst Prozentsätze nur bei >= 7 - Filtert Geister-Slots (proposalId=0, status=0, score=0) --- build/CMakeCache.txt | 686 ++++ .../4.1.1/CMakeASM_MASMCompiler.cmake | 30 + build/CMakeFiles/4.1.1/CMakeCCompiler.cmake | 84 + build/CMakeFiles/4.1.1/CMakeCXXCompiler.cmake | 104 + .../4.1.1/CMakeDetermineCompilerABI_C.bin | Bin 0 -> 33560 bytes .../4.1.1/CMakeDetermineCompilerABI_CXX.bin | Bin 0 -> 33560 bytes build/CMakeFiles/4.1.1/CMakeSystem.cmake | 15 + .../4.1.1/CompilerIdC/CMakeCCompilerId.c | 934 ++++++ build/CMakeFiles/4.1.1/CompilerIdC/a.out | Bin 0 -> 33736 bytes .../CMakeFiles/4.1.1/CompilerIdC/apple-sdk.c | 1 + .../CompilerIdCXX/CMakeCXXCompilerId.cpp | 949 ++++++ build/CMakeFiles/4.1.1/CompilerIdCXX/a.out | Bin 0 -> 33736 bytes .../4.1.1/CompilerIdCXX/apple-sdk.cpp | 1 + build/CMakeFiles/CMakeConfigureLog.yaml | 2787 +++++++++++++++++ .../CMakeDirectoryInformation.cmake | 16 + build/CMakeFiles/InstallScripts.json | 15 + build/CMakeFiles/Makefile.cmake | 217 ++ build/CMakeFiles/Makefile2 | 563 ++++ build/CMakeFiles/Progress/12 | 1 + build/CMakeFiles/Progress/13 | 1 + build/CMakeFiles/Progress/15 | 1 + build/CMakeFiles/Progress/16 | 1 + build/CMakeFiles/Progress/17 | 1 + build/CMakeFiles/Progress/20 | 1 + build/CMakeFiles/Progress/21 | 1 + build/CMakeFiles/Progress/8 | 1 + build/CMakeFiles/Progress/9 | 1 + build/CMakeFiles/Progress/count.txt | 1 + build/CMakeFiles/TargetDirectories.txt | 72 + build/CMakeFiles/cmake.check_cache | 1 + build/CMakeFiles/progress.marks | 1 + build/CTestTestfile.cmake | 11 + build/Makefile | 326 ++ .../CMakeDirectoryInformation.cmake | 16 + .../CMakeFiles/progress.marks | 1 + .../googletest-build/CTestTestfile.cmake | 7 + build/_deps/googletest-build/Makefile | 200 ++ .../googletest-build/cmake_install.cmake | 51 + .../CMakeDirectoryInformation.cmake | 16 + .../CMakeFiles/gmock.dir/DependInfo.cmake | 23 + .../CMakeFiles/gmock.dir/build.make | 114 + .../CMakeFiles/gmock.dir/cmake_clean.cmake | 11 + .../gmock.dir/cmake_clean_target.cmake | 3 + .../CMakeFiles/gmock.dir/compiler_depend.make | 2 + .../CMakeFiles/gmock.dir/compiler_depend.ts | 2 + .../CMakeFiles/gmock.dir/depend.make | 2 + .../CMakeFiles/gmock.dir/flags.make | 12 + .../googlemock/CMakeFiles/gmock.dir/link.txt | 2 + .../CMakeFiles/gmock.dir/progress.make | 3 + .../gmock_main.dir/DependInfo.cmake | 23 + .../CMakeFiles/gmock_main.dir/build.make | 114 + .../gmock_main.dir/cmake_clean.cmake | 11 + .../gmock_main.dir/cmake_clean_target.cmake | 3 + .../gmock_main.dir/compiler_depend.make | 2 + .../gmock_main.dir/compiler_depend.ts | 2 + .../CMakeFiles/gmock_main.dir/depend.make | 2 + .../CMakeFiles/gmock_main.dir/flags.make | 12 + .../CMakeFiles/gmock_main.dir/link.txt | 2 + .../CMakeFiles/gmock_main.dir/progress.make | 3 + .../googlemock/CMakeFiles/progress.marks | 1 + .../googlemock/CTestTestfile.cmake | 7 + .../googletest-build/googlemock/Makefile | 284 ++ .../googlemock/cmake_install.cmake | 79 + .../CMakeDirectoryInformation.cmake | 16 + .../GTestTargets-release.cmake | 49 + .../GTestTargets.cmake | 139 + .../CMakeFiles/gtest.dir/DependInfo.cmake | 23 + .../CMakeFiles/gtest.dir/build.make | 114 + .../CMakeFiles/gtest.dir/cmake_clean.cmake | 11 + .../gtest.dir/cmake_clean_target.cmake | 3 + .../CMakeFiles/gtest.dir/compiler_depend.make | 2 + .../CMakeFiles/gtest.dir/compiler_depend.ts | 2 + .../CMakeFiles/gtest.dir/depend.make | 2 + .../CMakeFiles/gtest.dir/flags.make | 12 + .../googletest/CMakeFiles/gtest.dir/link.txt | 2 + .../CMakeFiles/gtest.dir/progress.make | 3 + .../CMakeFiles/gtest.dir/src/gtest-all.cc.o | Bin 0 -> 1490560 bytes .../CMakeFiles/gtest.dir/src/gtest-all.cc.o.d | 1152 +++++++ .../gtest_main.dir/DependInfo.cmake | 23 + .../CMakeFiles/gtest_main.dir/build.make | 114 + .../gtest_main.dir/cmake_clean.cmake | 11 + .../gtest_main.dir/cmake_clean_target.cmake | 3 + .../gtest_main.dir/compiler_depend.make | 2 + .../gtest_main.dir/compiler_depend.ts | 2 + .../CMakeFiles/gtest_main.dir/depend.make | 2 + .../CMakeFiles/gtest_main.dir/flags.make | 12 + .../CMakeFiles/gtest_main.dir/link.txt | 2 + .../CMakeFiles/gtest_main.dir/progress.make | 3 + .../googletest/CMakeFiles/progress.marks | 1 + .../googletest/CTestTestfile.cmake | 6 + .../googletest-build/googletest/Makefile | 284 ++ .../googletest/cmake_install.cmake | 103 + .../googletest/generated/GTestConfig.cmake | 37 + .../generated/GTestConfigVersion.cmake | 43 + .../googletest/generated/gmock.pc | 10 + .../googletest/generated/gmock_main.pc | 10 + .../googletest/generated/gtest.pc | 9 + .../googletest/generated/gtest_main.pc | 10 + build/_deps/googletest-src | 1 + .../_deps/googletest-subbuild/CMakeCache.txt | 151 + .../CMakeFiles/4.1.1/CMakeSystem.cmake | 15 + .../CMakeFiles/CMakeConfigureLog.yaml | 209 ++ .../CMakeDirectoryInformation.cmake | 16 + .../CMakeFiles/CMakeRuleHashes.txt | 11 + .../CMakeFiles/InstallScripts.json | 7 + .../CMakeFiles/Makefile.cmake | 55 + .../googletest-subbuild/CMakeFiles/Makefile2 | 122 + .../CMakeFiles/TargetDirectories.txt | 3 + .../CMakeFiles/cmake.check_cache | 1 + .../CMakeFiles/googletest-populate-complete | 0 .../googletest-populate.dir/DependInfo.cmake | 22 + .../googletest-populate.dir/Labels.json | 46 + .../googletest-populate.dir/Labels.txt | 14 + .../googletest-populate.dir/build.make | 162 + .../googletest-populate.dir/cmake_clean.cmake | 17 + .../compiler_depend.make | 2 + .../compiler_depend.ts | 2 + .../googletest-populate.dir/progress.make | 10 + .../CMakeFiles/progress.marks | 1 + .../_deps/googletest-subbuild/CMakeLists.txt | 42 + build/_deps/googletest-subbuild/Makefile | 162 + .../googletest-subbuild/cmake_install.cmake | 56 + .../googletest-populate-build | 0 .../googletest-populate-configure | 0 .../googletest-populate-done | 0 .../googletest-populate-download | 0 .../googletest-populate-gitclone-lastrun.txt | 15 + .../googletest-populate-gitinfo.txt | 15 + .../googletest-populate-install | 0 .../googletest-populate-mkdir | 0 .../googletest-populate-patch | 0 .../googletest-populate-patch-info.txt | 6 + .../googletest-populate-test | 0 .../googletest-populate-update-info.txt | 7 + build/cmake_install.cmake | 86 + build/lib/libgtest.a | Bin 0 -> 2040984 bytes .../CMakeDirectoryInformation.cmake | 16 + .../platform_common.dir/DependInfo.cmake | 24 + .../CMakeFiles/platform_common.dir/build.make | 130 + .../platform_common.dir/cmake_clean.cmake | 13 + .../cmake_clean_target.cmake | 3 + .../platform_common.dir/compiler_depend.make | 2 + .../platform_common.dir/compiler_depend.ts | 2 + .../platform_common.dir/depend.make | 2 + .../CMakeFiles/platform_common.dir/flags.make | 12 + .../CMakeFiles/platform_common.dir/link.txt | 2 + .../platform_common.dir/progress.make | 4 + .../platform_common/CMakeFiles/progress.marks | 1 + build/lib/platform_common/Makefile | 269 ++ build/lib/platform_common/cmake_install.cmake | 45 + .../CMakeDirectoryInformation.cmake | 16 + .../platform_efi.dir/DependInfo.cmake | 26 + .../CMakeFiles/platform_efi.dir/build.make | 162 + .../platform_efi.dir/cmake_clean.cmake | 17 + .../platform_efi.dir/cmake_clean_target.cmake | 3 + .../platform_efi.dir/compiler_depend.make | 2 + .../platform_efi.dir/compiler_depend.ts | 2 + .../CMakeFiles/platform_efi.dir/depend.make | 2 + .../CMakeFiles/platform_efi.dir/flags.make | 21 + .../CMakeFiles/platform_efi.dir/link.txt | 2 + .../CMakeFiles/platform_efi.dir/progress.make | 6 + .../platform_efi/CMakeFiles/progress.marks | 1 + build/lib/platform_efi/Makefile | 323 ++ build/lib/platform_efi/cmake_install.cmake | 60 + .../CMakeDirectoryInformation.cmake | 16 + .../platform_os.dir/DependInfo.cmake | 24 + .../CMakeFiles/platform_os.dir/build.make | 130 + .../platform_os.dir/cmake_clean.cmake | 13 + .../platform_os.dir/cmake_clean_target.cmake | 3 + .../platform_os.dir/compiler_depend.make | 2 + .../platform_os.dir/compiler_depend.ts | 2 + .../CMakeFiles/platform_os.dir/depend.make | 2 + .../CMakeFiles/platform_os.dir/flags.make | 21 + .../CMakeFiles/platform_os.dir/link.txt | 2 + .../CMakeFiles/platform_os.dir/progress.make | 4 + .../lib/platform_os/CMakeFiles/progress.marks | 1 + build/lib/platform_os/CTestTestfile.cmake | 6 + build/lib/platform_os/Makefile | 269 ++ build/lib/platform_os/cmake_install.cmake | 53 + .../CMakeDirectoryInformation.cmake | 16 + .../src/CMakeFiles/Qubic.dir/DependInfo.cmake | 44 + build/src/CMakeFiles/Qubic.dir/build.make | 122 + .../CMakeFiles/Qubic.dir/cmake_clean.cmake | 12 + .../CMakeFiles/Qubic.dir/compiler_depend.make | 2 + .../CMakeFiles/Qubic.dir/compiler_depend.ts | 2 + build/src/CMakeFiles/Qubic.dir/depend.make | 2 + build/src/CMakeFiles/Qubic.dir/flags.make | 23 + build/src/CMakeFiles/Qubic.dir/link.txt | 1 + build/src/CMakeFiles/Qubic.dir/progress.make | 4 + build/src/CMakeFiles/progress.marks | 1 + build/src/CTestTestfile.cmake | 6 + build/src/Makefile | 251 ++ build/src/cmake_install.cmake | 45 + .../CMakeDirectoryInformation.cmake | 16 + build/test/CMakeFiles/progress.marks | 1 + .../qubic_core_tests.dir/DependInfo.cmake | 25 + .../qubic_core_tests.dir/build.make | 150 + .../qubic_core_tests.dir/cmake_clean.cmake | 16 + .../qubic_core_tests.dir/compiler_depend.make | 2 + .../qubic_core_tests.dir/compiler_depend.ts | 2 + .../qubic_core_tests.dir/depend.make | 2 + .../qubic_core_tests.dir/flags.make | 12 + .../CMakeFiles/qubic_core_tests.dir/link.txt | 1 + .../qubic_core_tests.dir/progress.make | 5 + build/test/CTestTestfile.cmake | 8 + build/test/Makefile | 296 ++ build/test/cmake_install.cmake | 50 + build/test/qubic_core_tests[1]_include.cmake | 5 + test_qrwa.sh | 173 +- 209 files changed, 14299 insertions(+), 18 deletions(-) create mode 100644 build/CMakeCache.txt create mode 100644 build/CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake create mode 100644 build/CMakeFiles/4.1.1/CMakeCCompiler.cmake create mode 100644 build/CMakeFiles/4.1.1/CMakeCXXCompiler.cmake create mode 100755 build/CMakeFiles/4.1.1/CMakeDetermineCompilerABI_C.bin create mode 100755 build/CMakeFiles/4.1.1/CMakeDetermineCompilerABI_CXX.bin create mode 100644 build/CMakeFiles/4.1.1/CMakeSystem.cmake create mode 100644 build/CMakeFiles/4.1.1/CompilerIdC/CMakeCCompilerId.c create mode 100755 build/CMakeFiles/4.1.1/CompilerIdC/a.out create mode 100644 build/CMakeFiles/4.1.1/CompilerIdC/apple-sdk.c create mode 100644 build/CMakeFiles/4.1.1/CompilerIdCXX/CMakeCXXCompilerId.cpp create mode 100755 build/CMakeFiles/4.1.1/CompilerIdCXX/a.out create mode 100644 build/CMakeFiles/4.1.1/CompilerIdCXX/apple-sdk.cpp create mode 100644 build/CMakeFiles/CMakeConfigureLog.yaml create mode 100644 build/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/CMakeFiles/InstallScripts.json create mode 100644 build/CMakeFiles/Makefile.cmake create mode 100644 build/CMakeFiles/Makefile2 create mode 100644 build/CMakeFiles/Progress/12 create mode 100644 build/CMakeFiles/Progress/13 create mode 100644 build/CMakeFiles/Progress/15 create mode 100644 build/CMakeFiles/Progress/16 create mode 100644 build/CMakeFiles/Progress/17 create mode 100644 build/CMakeFiles/Progress/20 create mode 100644 build/CMakeFiles/Progress/21 create mode 100644 build/CMakeFiles/Progress/8 create mode 100644 build/CMakeFiles/Progress/9 create mode 100644 build/CMakeFiles/Progress/count.txt create mode 100644 build/CMakeFiles/TargetDirectories.txt create mode 100644 build/CMakeFiles/cmake.check_cache create mode 100644 build/CMakeFiles/progress.marks create mode 100644 build/CTestTestfile.cmake create mode 100644 build/Makefile create mode 100644 build/_deps/googletest-build/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/_deps/googletest-build/CMakeFiles/progress.marks create mode 100644 build/_deps/googletest-build/CTestTestfile.cmake create mode 100644 build/_deps/googletest-build/Makefile create mode 100644 build/_deps/googletest-build/cmake_install.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/DependInfo.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean_target.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.ts create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/flags.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/link.txt create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/progress.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/DependInfo.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean_target.cmake create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.ts create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/flags.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/link.txt create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/progress.make create mode 100644 build/_deps/googletest-build/googlemock/CMakeFiles/progress.marks create mode 100644 build/_deps/googletest-build/googlemock/CTestTestfile.cmake create mode 100644 build/_deps/googletest-build/googlemock/Makefile create mode 100644 build/_deps/googletest-build/googlemock/cmake_install.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-release.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/DependInfo.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean_target.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.ts create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/flags.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/link.txt create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/progress.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o.d create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/DependInfo.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/cmake_clean.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/cmake_clean_target.cmake create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/compiler_depend.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/compiler_depend.ts create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/depend.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/flags.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/link.txt create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/progress.make create mode 100644 build/_deps/googletest-build/googletest/CMakeFiles/progress.marks create mode 100644 build/_deps/googletest-build/googletest/CTestTestfile.cmake create mode 100644 build/_deps/googletest-build/googletest/Makefile create mode 100644 build/_deps/googletest-build/googletest/cmake_install.cmake create mode 100644 build/_deps/googletest-build/googletest/generated/GTestConfig.cmake create mode 100644 build/_deps/googletest-build/googletest/generated/GTestConfigVersion.cmake create mode 100644 build/_deps/googletest-build/googletest/generated/gmock.pc create mode 100644 build/_deps/googletest-build/googletest/generated/gmock_main.pc create mode 100644 build/_deps/googletest-build/googletest/generated/gtest.pc create mode 100644 build/_deps/googletest-build/googletest/generated/gtest_main.pc create mode 160000 build/_deps/googletest-src create mode 100644 build/_deps/googletest-subbuild/CMakeCache.txt create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/4.1.1/CMakeSystem.cmake create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/CMakeConfigureLog.yaml create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/CMakeRuleHashes.txt create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/InstallScripts.json create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/Makefile.cmake create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/Makefile2 create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/TargetDirectories.txt create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/cmake.check_cache create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate-complete create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/DependInfo.cmake create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/Labels.json create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/Labels.txt create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/build.make create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/cmake_clean.cmake create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/compiler_depend.make create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/compiler_depend.ts create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/googletest-populate.dir/progress.make create mode 100644 build/_deps/googletest-subbuild/CMakeFiles/progress.marks create mode 100644 build/_deps/googletest-subbuild/CMakeLists.txt create mode 100644 build/_deps/googletest-subbuild/Makefile create mode 100644 build/_deps/googletest-subbuild/cmake_install.cmake create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-build create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-configure create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-done create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-download create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-gitclone-lastrun.txt create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-gitinfo.txt create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-install create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-mkdir create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-patch create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-patch-info.txt create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-test create mode 100644 build/_deps/googletest-subbuild/googletest-populate-prefix/src/googletest-populate-stamp/googletest-populate-update-info.txt create mode 100644 build/cmake_install.cmake create mode 100644 build/lib/libgtest.a create mode 100644 build/lib/platform_common/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/DependInfo.cmake create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/build.make create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/cmake_clean.cmake create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/cmake_clean_target.cmake create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/compiler_depend.make create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/compiler_depend.ts create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/depend.make create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/flags.make create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/link.txt create mode 100644 build/lib/platform_common/CMakeFiles/platform_common.dir/progress.make create mode 100644 build/lib/platform_common/CMakeFiles/progress.marks create mode 100644 build/lib/platform_common/Makefile create mode 100644 build/lib/platform_common/cmake_install.cmake create mode 100644 build/lib/platform_efi/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/DependInfo.cmake create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/build.make create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/cmake_clean.cmake create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/cmake_clean_target.cmake create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/compiler_depend.make create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/compiler_depend.ts create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/depend.make create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/flags.make create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/link.txt create mode 100644 build/lib/platform_efi/CMakeFiles/platform_efi.dir/progress.make create mode 100644 build/lib/platform_efi/CMakeFiles/progress.marks create mode 100644 build/lib/platform_efi/Makefile create mode 100644 build/lib/platform_efi/cmake_install.cmake create mode 100644 build/lib/platform_os/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/DependInfo.cmake create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/build.make create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/cmake_clean.cmake create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/cmake_clean_target.cmake create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/compiler_depend.make create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/compiler_depend.ts create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/depend.make create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/flags.make create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/link.txt create mode 100644 build/lib/platform_os/CMakeFiles/platform_os.dir/progress.make create mode 100644 build/lib/platform_os/CMakeFiles/progress.marks create mode 100644 build/lib/platform_os/CTestTestfile.cmake create mode 100644 build/lib/platform_os/Makefile create mode 100644 build/lib/platform_os/cmake_install.cmake create mode 100644 build/src/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/src/CMakeFiles/Qubic.dir/DependInfo.cmake create mode 100644 build/src/CMakeFiles/Qubic.dir/build.make create mode 100644 build/src/CMakeFiles/Qubic.dir/cmake_clean.cmake create mode 100644 build/src/CMakeFiles/Qubic.dir/compiler_depend.make create mode 100644 build/src/CMakeFiles/Qubic.dir/compiler_depend.ts create mode 100644 build/src/CMakeFiles/Qubic.dir/depend.make create mode 100644 build/src/CMakeFiles/Qubic.dir/flags.make create mode 100644 build/src/CMakeFiles/Qubic.dir/link.txt create mode 100644 build/src/CMakeFiles/Qubic.dir/progress.make create mode 100644 build/src/CMakeFiles/progress.marks create mode 100644 build/src/CTestTestfile.cmake create mode 100644 build/src/Makefile create mode 100644 build/src/cmake_install.cmake create mode 100644 build/test/CMakeFiles/CMakeDirectoryInformation.cmake create mode 100644 build/test/CMakeFiles/progress.marks create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/DependInfo.cmake create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/build.make create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/cmake_clean.cmake create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/compiler_depend.make create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/compiler_depend.ts create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/depend.make create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/flags.make create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/link.txt create mode 100644 build/test/CMakeFiles/qubic_core_tests.dir/progress.make create mode 100644 build/test/CTestTestfile.cmake create mode 100644 build/test/Makefile create mode 100644 build/test/cmake_install.cmake create mode 100644 build/test/qubic_core_tests[1]_include.cmake diff --git a/build/CMakeCache.txt b/build/CMakeCache.txt new file mode 100644 index 000000000..950c124ad --- /dev/null +++ b/build/CMakeCache.txt @@ -0,0 +1,686 @@ +# This is the CMakeCache file. +# For build in directory: /Users/graf/Documents/GitHub/qubic-core/core/build +# It was generated by CMake: /opt/homebrew/bin/cmake +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//Build the EFI benchmark application +BUILD_BENCHMARK:BOOL=OFF + +//Build the EFI application +BUILD_EFI:BOOL=ON + +//Builds the googlemock subproject +BUILD_GMOCK:BOOL=ON + +//Build the test suite +BUILD_TESTS:BOOL=ON + +//Path to a program. +CMAKE_ADDR2LINE:FILEPATH=CMAKE_ADDR2LINE-NOTFOUND + +//Path to a program. +CMAKE_AR:FILEPATH=/usr/bin/ar + +//ASM_MASM compiler +CMAKE_ASM_MASM_COMPILER:FILEPATH=ml + +//Flags used by the ASM_MASM compiler during all build types. +CMAKE_ASM_MASM_FLAGS:STRING= + +//Flags used by the ASM_MASM compiler during DEBUG builds. +CMAKE_ASM_MASM_FLAGS_DEBUG:STRING= + +//Flags used by the ASM_MASM compiler during MINSIZEREL builds. +CMAKE_ASM_MASM_FLAGS_MINSIZEREL:STRING= + +//Flags used by the ASM_MASM compiler during RELEASE builds. +CMAKE_ASM_MASM_FLAGS_RELEASE:STRING= + +//Flags used by the ASM_MASM compiler during RELWITHDEBINFO builds. +CMAKE_ASM_MASM_FLAGS_RELWITHDEBINFO:STRING= + +//Choose the type of build, options are: None Debug Release RelWithDebInfo +// MinSizeRel ... +CMAKE_BUILD_TYPE:STRING=Release + +//Enable/Disable color output during build. +CMAKE_COLOR_MAKEFILE:BOOL=ON + +//Available build types +CMAKE_CONFIGURATION_TYPES:STRING=Debug;Release + +//CXX compiler +CMAKE_CXX_COMPILER:FILEPATH=/usr/bin/c++ + +//Flags used by the CXX compiler during MINSIZEREL builds. +CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG + +//Flags used by the CXX compiler during RELWITHDEBINFO builds. +CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG + +//C compiler +CMAKE_C_COMPILER:FILEPATH=/usr/bin/cc + +//Flags used by the C compiler during MINSIZEREL builds. +CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG + +//Flags used by the C compiler during RELWITHDEBINFO builds. +CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG + +//Path to a program. +CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND + +//Flags used by the linker during MINSIZEREL builds. +CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during RELWITHDEBINFO builds. +CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Enable/Disable output of compile commands during generation. +CMAKE_EXPORT_COMPILE_COMMANDS:BOOL= + +//Value Computed by CMake. +CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/pkgRedirects + +//User executables (bin) +CMAKE_INSTALL_BINDIR:PATH=bin + +//Read-only architecture-independent data (DATAROOTDIR) +CMAKE_INSTALL_DATADIR:PATH= + +//Read-only architecture-independent data root (share) +CMAKE_INSTALL_DATAROOTDIR:PATH=share + +//Documentation root (DATAROOTDIR/doc/PROJECT_NAME) +CMAKE_INSTALL_DOCDIR:PATH= + +//C header files (include) +CMAKE_INSTALL_INCLUDEDIR:PATH=include + +//Info documentation (DATAROOTDIR/info) +CMAKE_INSTALL_INFODIR:PATH= + +//Object code libraries (lib) +CMAKE_INSTALL_LIBDIR:PATH=lib + +//Program executables (libexec) +CMAKE_INSTALL_LIBEXECDIR:PATH=libexec + +//Locale-dependent data (DATAROOTDIR/locale) +CMAKE_INSTALL_LOCALEDIR:PATH= + +//Modifiable single-machine data (var) +CMAKE_INSTALL_LOCALSTATEDIR:PATH=var + +//Man documentation (DATAROOTDIR/man) +CMAKE_INSTALL_MANDIR:PATH= + +//Path to a program. +CMAKE_INSTALL_NAME_TOOL:FILEPATH=/usr/bin/install_name_tool + +//C header files for non-gcc (/usr/include) +CMAKE_INSTALL_OLDINCLUDEDIR:PATH=/usr/include + +//Install path prefix, prepended onto install directories. +CMAKE_INSTALL_PREFIX:PATH=/usr/local + +//Run-time variable data (LOCALSTATEDIR/run) +CMAKE_INSTALL_RUNSTATEDIR:PATH= + +//System admin executables (sbin) +CMAKE_INSTALL_SBINDIR:PATH=sbin + +//Modifiable architecture-independent data (com) +CMAKE_INSTALL_SHAREDSTATEDIR:PATH=com + +//Read-only single-machine data (etc) +CMAKE_INSTALL_SYSCONFDIR:PATH=etc + +//Path to a program. +CMAKE_LINKER:FILEPATH=/usr/bin/ld + +//Path to a program. +CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/make + +//Flags used by the linker during the creation of modules during +// all build types. +CMAKE_MODULE_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of modules during +// DEBUG builds. +CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of modules during +// MINSIZEREL builds. +CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of modules during +// RELEASE builds. +CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of modules during +// RELWITHDEBINFO builds. +CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Path to a program. +CMAKE_NM:FILEPATH=/usr/bin/nm + +//Path to a program. +CMAKE_OBJCOPY:FILEPATH=CMAKE_OBJCOPY-NOTFOUND + +//Path to a program. +CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump + +//Build architectures for OSX +CMAKE_OSX_ARCHITECTURES:STRING= + +//Minimum OS X version to target for deployment (at runtime); newer +// APIs weak linked. Set to empty string for default value. +CMAKE_OSX_DEPLOYMENT_TARGET:STRING= + +//The product will be built against the headers and libraries located +// inside the indicated SDK. +CMAKE_OSX_SYSROOT:STRING= + +//Value Computed by CMake +CMAKE_PROJECT_COMPAT_VERSION:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_DESCRIPTION:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_HOMEPAGE_URL:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_NAME:STATIC=qubic + +//Value Computed by CMake +CMAKE_PROJECT_VERSION:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_MAJOR:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_MINOR:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_PATCH:STATIC= + +//Value Computed by CMake +CMAKE_PROJECT_VERSION_TWEAK:STATIC= + +//Path to a program. +CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib + +//Path to a program. +CMAKE_READELF:FILEPATH=CMAKE_READELF-NOTFOUND + +//Flags used by the linker during the creation of shared libraries +// during all build types. +CMAKE_SHARED_LINKER_FLAGS:STRING= + +//Flags used by the linker during the creation of shared libraries +// during DEBUG builds. +CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the linker during the creation of shared libraries +// during MINSIZEREL builds. +CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the linker during the creation of shared libraries +// during RELEASE builds. +CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the linker during the creation of shared libraries +// during RELWITHDEBINFO builds. +CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//If set, runtime paths are not added when installing shared libraries, +// but are added when building. +CMAKE_SKIP_INSTALL_RPATH:BOOL=NO + +//If set, runtime paths are not added when using shared libraries. +CMAKE_SKIP_RPATH:BOOL=NO + +//Flags used by the archiver during the creation of static libraries +// during all build types. +CMAKE_STATIC_LINKER_FLAGS:STRING= + +//Flags used by the archiver during the creation of static libraries +// during DEBUG builds. +CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING= + +//Flags used by the archiver during the creation of static libraries +// during MINSIZEREL builds. +CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING= + +//Flags used by the archiver during the creation of static libraries +// during RELEASE builds. +CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING= + +//Flags used by the archiver during the creation of static libraries +// during RELWITHDEBINFO builds. +CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING= + +//Path to a program. +CMAKE_STRIP:FILEPATH=/usr/bin/strip + +//Path to a program. +CMAKE_TAPI:FILEPATH=/Library/Developer/CommandLineTools/usr/bin/tapi + +//If this value is on, makefiles will be generated without the +// .SILENT directive, and all commands will be echoed to the console +// during the make. This is useful for debugging only. With Visual +// Studio IDE projects all commands are done without /nologo. +CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE + +//Enable AVX-512 instructions +ENABLE_AVX512:BOOL=ON + +//Directory under which to collect all populated content +FETCHCONTENT_BASE_DIR:PATH=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps + +//Disables all attempts to download or update content and assumes +// source dirs already exist +FETCHCONTENT_FULLY_DISCONNECTED:BOOL=OFF + +//Enables QUIET option for all content population +FETCHCONTENT_QUIET:BOOL=ON + +//When not empty, overrides where to find pre-populated content +// for googletest +FETCHCONTENT_SOURCE_DIR_GOOGLETEST:PATH= + +//Enables UPDATE_DISCONNECTED behavior for all content population +FETCHCONTENT_UPDATES_DISCONNECTED:BOOL=OFF + +//Enables UPDATE_DISCONNECTED behavior just for population of googletest +FETCHCONTENT_UPDATES_DISCONNECTED_GOOGLETEST:BOOL=OFF + +//Git command line client +GIT_EXECUTABLE:FILEPATH=/usr/bin/git + +//Use Abseil and RE2. Requires Abseil and RE2 to be separately +// added to the build. +GTEST_HAS_ABSL:BOOL=OFF + +//Enable installation of googletest. (Projects embedding googletest +// may want to turn this OFF.) +INSTALL_GTEST:BOOL=ON + +//Build test with sanitizer support (clang only) +USE_SANITIZER:BOOL=ON + +//Value Computed by CMake +gmock_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock + +//Value Computed by CMake +gmock_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +gmock_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock + +//Build all of Google Mock's own tests. +gmock_build_tests:BOOL=OFF + +//Value Computed by CMake +googletest-distribution_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build + +//Value Computed by CMake +googletest-distribution_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +googletest-distribution_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src + +//Value Computed by CMake +gtest_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest + +//Value Computed by CMake +gtest_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +gtest_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest + +//Build gtest's sample programs. +gtest_build_samples:BOOL=OFF + +//Build all of gtest's own tests. +gtest_build_tests:BOOL=OFF + +//Disable uses of pthreads in gtest. +gtest_disable_pthreads:BOOL=OFF + +//Use shared (DLL) run-time lib even when Google Test is built +// as static lib. +gtest_force_shared_crt:BOOL=ON + +//Build gtest with internal symbols hidden in shared libraries. +gtest_hide_internal_symbols:BOOL=OFF + +//Value Computed by CMake +platform_common_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common + +//Value Computed by CMake +platform_common_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +platform_common_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/lib/platform_common + +//Value Computed by CMake +platform_efi_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi + +//Value Computed by CMake +platform_efi_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +platform_efi_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/lib/platform_efi + +//Value Computed by CMake +platform_os_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os + +//Value Computed by CMake +platform_os_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +platform_os_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/lib/platform_os + +//Value Computed by CMake +qubic_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build + +//Value Computed by CMake +qubic_IS_TOP_LEVEL:STATIC=ON + +//Value Computed by CMake +qubic_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core + +//Value Computed by CMake +qubic_core_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/src + +//Value Computed by CMake +qubic_core_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +qubic_core_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/src + +//Value Computed by CMake +qubic_core_tests_BINARY_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/build/test + +//Value Computed by CMake +qubic_core_tests_IS_TOP_LEVEL:STATIC=OFF + +//Value Computed by CMake +qubic_core_tests_SOURCE_DIR:STATIC=/Users/graf/Documents/GitHub/qubic-core/core/test + + +######################## +# INTERNAL cache entries +######################## + +//Assembly language to use +ASM_LANG:INTERNAL= +//ADVANCED property for variable: CMAKE_ADDR2LINE +CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_AR +CMAKE_AR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_ASM_MASM_COMPILER +CMAKE_ASM_MASM_COMPILER-ADVANCED:INTERNAL=1 +CMAKE_ASM_MASM_COMPILER_WORKS:INTERNAL=1 +//ADVANCED property for variable: CMAKE_ASM_MASM_FLAGS +CMAKE_ASM_MASM_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_ASM_MASM_FLAGS_DEBUG +CMAKE_ASM_MASM_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_ASM_MASM_FLAGS_MINSIZEREL +CMAKE_ASM_MASM_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_ASM_MASM_FLAGS_RELEASE +CMAKE_ASM_MASM_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_ASM_MASM_FLAGS_RELWITHDEBINFO +CMAKE_ASM_MASM_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=/Users/graf/Documents/GitHub/qubic-core/core/build +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=4 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=1 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=1 +//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE +CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=/opt/homebrew/bin/cmake +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=/opt/homebrew/bin/cpack +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=/opt/homebrew/bin/ctest +//ADVANCED property for variable: CMAKE_CXX_COMPILER +CMAKE_CXX_COMPILER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS +CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1 +CMAKE_CXX_FLAGS:INTERNAL= +//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG +CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +CMAKE_CXX_FLAGS_DEBUG:INTERNAL= +//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL +CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE +CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +CMAKE_CXX_FLAGS_RELEASE:INTERNAL= +//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO +CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_COMPILER +CMAKE_C_COMPILER-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS +CMAKE_C_FLAGS-ADVANCED:INTERNAL=1 +CMAKE_C_FLAGS:INTERNAL= +//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG +CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +CMAKE_C_FLAGS_DEBUG:INTERNAL= +//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL +CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE +CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +CMAKE_C_FLAGS_RELEASE:INTERNAL= +//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO +CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_DLLTOOL +CMAKE_DLLTOOL-ADVANCED:INTERNAL=1 +//Path to cache edit program executable. +CMAKE_EDIT_COMMAND:INTERNAL=/opt/homebrew/bin/ccmake +//Executable file format +CMAKE_EXECUTABLE_FORMAT:INTERNAL=MACHO +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS +CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1 +CMAKE_EXE_LINKER_FLAGS:INTERNAL= +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG +CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +CMAKE_EXE_LINKER_FLAGS_DEBUG:INTERNAL= +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL +CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE +CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +CMAKE_EXE_LINKER_FLAGS_RELEASE:INTERNAL= +//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS +CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1 +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=Unix Makefiles +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Test CMAKE_HAVE_LIBC_PTHREAD +CMAKE_HAVE_LIBC_PTHREAD:INTERNAL=1 +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=/Users/graf/Documents/GitHub/qubic-core/core +//ADVANCED property for variable: CMAKE_INSTALL_BINDIR +CMAKE_INSTALL_BINDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_DATADIR +CMAKE_INSTALL_DATADIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_DATAROOTDIR +CMAKE_INSTALL_DATAROOTDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_DOCDIR +CMAKE_INSTALL_DOCDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_INCLUDEDIR +CMAKE_INSTALL_INCLUDEDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_INFODIR +CMAKE_INSTALL_INFODIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_LIBDIR +CMAKE_INSTALL_LIBDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_LIBEXECDIR +CMAKE_INSTALL_LIBEXECDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_LOCALEDIR +CMAKE_INSTALL_LOCALEDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_LOCALSTATEDIR +CMAKE_INSTALL_LOCALSTATEDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_MANDIR +CMAKE_INSTALL_MANDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_NAME_TOOL +CMAKE_INSTALL_NAME_TOOL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_OLDINCLUDEDIR +CMAKE_INSTALL_OLDINCLUDEDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_RUNSTATEDIR +CMAKE_INSTALL_RUNSTATEDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_SBINDIR +CMAKE_INSTALL_SBINDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_SHAREDSTATEDIR +CMAKE_INSTALL_SHAREDSTATEDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_INSTALL_SYSCONFDIR +CMAKE_INSTALL_SYSCONFDIR-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_LINKER +CMAKE_LINKER-ADVANCED:INTERNAL=1 +//Name of CMakeLists files to read +CMAKE_LIST_FILE_NAME:INTERNAL=CMakeLists.txt +//ADVANCED property for variable: CMAKE_MAKE_PROGRAM +CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS +CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG +CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL +CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE +CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_NM +CMAKE_NM-ADVANCED:INTERNAL=1 +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=9 +//ADVANCED property for variable: CMAKE_OBJCOPY +CMAKE_OBJCOPY-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_OBJDUMP +CMAKE_OBJDUMP-ADVANCED:INTERNAL=1 +//Platform information initialized +CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_RANLIB +CMAKE_RANLIB-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_READELF +CMAKE_READELF-ADVANCED:INTERNAL=1 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=/opt/homebrew/share/cmake +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS +CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG +CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL +CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE +CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH +CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_SKIP_RPATH +CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS +CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG +CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL +CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE +CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO +CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_STRIP +CMAKE_STRIP-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: CMAKE_TAPI +CMAKE_TAPI-ADVANCED:INTERNAL=1 +//uname command +CMAKE_UNAME:INTERNAL=/usr/bin/uname +//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE +CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1 +//Common C++ compiler flags +COMMON_CXX_FLAGS:INTERNAL=-Wall -Wextra -fshort-wchar +//Common C compiler flags +COMMON_C_FLAGS:INTERNAL=-Wall -Wextra -fshort-wchar +//Common debug compiler flags +COMMON_DEBUG_FLAGS:INTERNAL=-g +//Common linker flags +COMMON_LINK_FLAGS:INTERNAL= +//Common release compiler flags +COMMON_RELEASE_FLAGS:INTERNAL=-O2 -fomit-frame-pointer -fno-lto +//CPU instruction set flags +CPU_INSTRUCTION_FLAGS:INTERNAL=-mavx -mavx2 -mavx512f -mavx512cd -mavx512vl -mavx512bw -mavx512dq +//EFI-specific C++ compiler flags +EFI_CXX_FLAGS:INTERNAL=-ffreestanding -mno-red-zone -fno-stack-protector -fno-strict-aliasing -fno-builtin -fno-rtti -fno-exceptions +//EFI-specific C compiler flags +EFI_C_FLAGS:INTERNAL=-ffreestanding -mno-red-zone -fno-stack-protector -fno-strict-aliasing -fno-builtin +//EFI-specific compiler flags +EFI_SPECIFIC_FLAGS:INTERNAL=-fno-rtti -fno-exceptions +//Details about finding Threads +FIND_PACKAGE_MESSAGE_DETAILS_Threads:INTERNAL=[TRUE][v()] +//ADVANCED property for variable: GIT_EXECUTABLE +GIT_EXECUTABLE-ADVANCED:INTERNAL=1 +//Clang compiler detected +IS_CLANG:INTERNAL=TRUE +//GCC compiler detected +IS_GCC:INTERNAL=FALSE +//Linux platform detected +IS_LINUX:INTERNAL=FALSE +//MSVC compiler detected +IS_MSVC:INTERNAL=FALSE +//Windows platform detected +IS_WINDOWS:INTERNAL=FALSE +//OS-specific C++ compiler flags +OS_CXX_FLAGS:INTERNAL=-fno-stack-protector +//OS-specific C compiler flags +OS_C_FLAGS:INTERNAL=-fno-stack-protector +//Test-specific compiler flags +TEST_SPECIFIC_FLAGS:INTERNAL='-Wpedantic -Werror -mrdrnd -Wcast-align ' +//Test-specific linker flags +TEST_SPECIFIC_LINK_FLAGS:INTERNAL= +//CMAKE_INSTALL_PREFIX during last run +_GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX:INTERNAL=/usr/local +cmake_package_name:INTERNAL=GTest +generated_dir:INTERNAL=/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/generated +//ADVANCED property for variable: gmock_build_tests +gmock_build_tests-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: gtest_build_samples +gtest_build_samples-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: gtest_build_tests +gtest_build_tests-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: gtest_disable_pthreads +gtest_disable_pthreads-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: gtest_force_shared_crt +gtest_force_shared_crt-ADVANCED:INTERNAL=1 +//ADVANCED property for variable: gtest_hide_internal_symbols +gtest_hide_internal_symbols-ADVANCED:INTERNAL=1 +targets_export_name:INTERNAL=GTestTargets + diff --git a/build/CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake b/build/CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake new file mode 100644 index 000000000..4ac278a30 --- /dev/null +++ b/build/CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake @@ -0,0 +1,30 @@ +set(CMAKE_ASM_MASM_COMPILER "ml") +set(CMAKE_ASM_MASM_COMPILER_ARG1 "") +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_ASM_MASM_COMPILER_AR "") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_ASM_MASM_COMPILER_RANLIB "") +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_LINKER_LINK "") +set(CMAKE_LINKER_LLD "") +set(CMAKE_ASM_MASM_COMPILER_LINKER "") +set(CMAKE_ASM_MASM_COMPILER_LINKER_ID "") +set(CMAKE_ASM_MASM_COMPILER_LINKER_VERSION ) +set(CMAKE_ASM_MASM_COMPILER_LINKER_FRONTEND_VARIANT ) +set(CMAKE_MT "") +set(CMAKE_TAPI "/Library/Developer/CommandLineTools/usr/bin/tapi") +set(CMAKE_ASM_MASM_COMPILER_LOADED 1) +set(CMAKE_ASM_MASM_COMPILER_ID "") +set(CMAKE_ASM_MASM_COMPILER_VERSION "") +set(CMAKE_ASM_MASM_COMPILER_ENV_VAR "ASM_MASM") + +set(CMAKE_ASM_MASM_ARCHITECTURE_ID "") + + +set(CMAKE_ASM_MASM_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) +set(CMAKE_ASM_MASM_LINKER_PREFERENCE 0) +set(CMAKE_ASM_MASM_LINKER_DEPFILE_SUPPORTED ) +set(CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED ) +set(CMAKE_ASM_MASM_LINKER_PUSHPOP_STATE_SUPPORTED ) + + diff --git a/build/CMakeFiles/4.1.1/CMakeCCompiler.cmake b/build/CMakeFiles/4.1.1/CMakeCCompiler.cmake new file mode 100644 index 000000000..6dcc5d7a6 --- /dev/null +++ b/build/CMakeFiles/4.1.1/CMakeCCompiler.cmake @@ -0,0 +1,84 @@ +set(CMAKE_C_COMPILER "/usr/bin/cc") +set(CMAKE_C_COMPILER_ARG1 "") +set(CMAKE_C_COMPILER_ID "AppleClang") +set(CMAKE_C_COMPILER_VERSION "17.0.0.17000013") +set(CMAKE_C_COMPILER_VERSION_INTERNAL "") +set(CMAKE_C_COMPILER_WRAPPER "") +set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "17") +set(CMAKE_C_EXTENSIONS_COMPUTED_DEFAULT "ON") +set(CMAKE_C_STANDARD_LATEST "23") +set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert;c_std_17;c_std_23") +set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes") +set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros") +set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert") +set(CMAKE_C17_COMPILE_FEATURES "c_std_17") +set(CMAKE_C23_COMPILE_FEATURES "c_std_23") + +set(CMAKE_C_PLATFORM_ID "Darwin") +set(CMAKE_C_SIMULATE_ID "") +set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU") +set(CMAKE_C_COMPILER_APPLE_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") +set(CMAKE_C_SIMULATE_VERSION "") +set(CMAKE_C_COMPILER_ARCHITECTURE_ID "arm64") + + + +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_C_COMPILER_AR "") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_C_COMPILER_RANLIB "") +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_LINKER_LINK "") +set(CMAKE_LINKER_LLD "") +set(CMAKE_C_COMPILER_LINKER "/Library/Developer/CommandLineTools/usr/bin/ld") +set(CMAKE_C_COMPILER_LINKER_ID "AppleClang") +set(CMAKE_C_COMPILER_LINKER_VERSION 1167.5) +set(CMAKE_C_COMPILER_LINKER_FRONTEND_VARIANT GNU) +set(CMAKE_MT "") +set(CMAKE_TAPI "/Library/Developer/CommandLineTools/usr/bin/tapi") +set(CMAKE_COMPILER_IS_GNUCC ) +set(CMAKE_C_COMPILER_LOADED 1) +set(CMAKE_C_COMPILER_WORKS TRUE) +set(CMAKE_C_ABI_COMPILED TRUE) + +set(CMAKE_C_COMPILER_ENV_VAR "CC") + +set(CMAKE_C_COMPILER_ID_RUN 1) +set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m) +set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) +set(CMAKE_C_LINKER_PREFERENCE 10) +set(CMAKE_C_LINKER_DEPFILE_SUPPORTED ) +set(CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED ) +set(CMAKE_C_LINKER_PUSHPOP_STATE_SUPPORTED ) + +# Save compiler ABI information. +set(CMAKE_C_SIZEOF_DATA_PTR "8") +set(CMAKE_C_COMPILER_ABI "") +set(CMAKE_C_BYTE_ORDER "LITTLE_ENDIAN") +set(CMAKE_C_LIBRARY_ARCHITECTURE "") + +if(CMAKE_C_SIZEOF_DATA_PTR) + set(CMAKE_SIZEOF_VOID_P "${CMAKE_C_SIZEOF_DATA_PTR}") +endif() + +if(CMAKE_C_COMPILER_ABI) + set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_C_COMPILER_ABI}") +endif() + +if(CMAKE_C_LIBRARY_ARCHITECTURE) + set(CMAKE_LIBRARY_ARCHITECTURE "") +endif() + +set(CMAKE_C_CL_SHOWINCLUDES_PREFIX "") +if(CMAKE_C_CL_SHOWINCLUDES_PREFIX) + set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_C_CL_SHOWINCLUDES_PREFIX}") +endif() + + + + + +set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "/usr/local/include;/Library/Developer/CommandLineTools/usr/lib/clang/17/include;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include;/Library/Developer/CommandLineTools/usr/include") +set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "") +set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift") +set(CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks") diff --git a/build/CMakeFiles/4.1.1/CMakeCXXCompiler.cmake b/build/CMakeFiles/4.1.1/CMakeCXXCompiler.cmake new file mode 100644 index 000000000..5c5b62a40 --- /dev/null +++ b/build/CMakeFiles/4.1.1/CMakeCXXCompiler.cmake @@ -0,0 +1,104 @@ +set(CMAKE_CXX_COMPILER "/usr/bin/c++") +set(CMAKE_CXX_COMPILER_ARG1 "") +set(CMAKE_CXX_COMPILER_ID "AppleClang") +set(CMAKE_CXX_COMPILER_VERSION "17.0.0.17000013") +set(CMAKE_CXX_COMPILER_VERSION_INTERNAL "") +set(CMAKE_CXX_COMPILER_WRAPPER "") +set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "14") +set(CMAKE_CXX_EXTENSIONS_COMPUTED_DEFAULT "ON") +set(CMAKE_CXX_STANDARD_LATEST "23") +set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20;cxx_std_23") +set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters") +set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") +set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") +set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17") +set(CMAKE_CXX20_COMPILE_FEATURES "cxx_std_20") +set(CMAKE_CXX23_COMPILE_FEATURES "cxx_std_23") +set(CMAKE_CXX26_COMPILE_FEATURES "") + +set(CMAKE_CXX_PLATFORM_ID "Darwin") +set(CMAKE_CXX_SIMULATE_ID "") +set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "GNU") +set(CMAKE_CXX_COMPILER_APPLE_SYSROOT "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk") +set(CMAKE_CXX_SIMULATE_VERSION "") +set(CMAKE_CXX_COMPILER_ARCHITECTURE_ID "arm64") + + + +set(CMAKE_AR "/usr/bin/ar") +set(CMAKE_CXX_COMPILER_AR "") +set(CMAKE_RANLIB "/usr/bin/ranlib") +set(CMAKE_CXX_COMPILER_RANLIB "") +set(CMAKE_LINKER "/usr/bin/ld") +set(CMAKE_LINKER_LINK "") +set(CMAKE_LINKER_LLD "") +set(CMAKE_CXX_COMPILER_LINKER "/Library/Developer/CommandLineTools/usr/bin/ld") +set(CMAKE_CXX_COMPILER_LINKER_ID "AppleClang") +set(CMAKE_CXX_COMPILER_LINKER_VERSION 1167.5) +set(CMAKE_CXX_COMPILER_LINKER_FRONTEND_VARIANT GNU) +set(CMAKE_MT "") +set(CMAKE_TAPI "/Library/Developer/CommandLineTools/usr/bin/tapi") +set(CMAKE_COMPILER_IS_GNUCXX ) +set(CMAKE_CXX_COMPILER_LOADED 1) +set(CMAKE_CXX_COMPILER_WORKS TRUE) +set(CMAKE_CXX_ABI_COMPILED TRUE) + +set(CMAKE_CXX_COMPILER_ENV_VAR "CXX") + +set(CMAKE_CXX_COMPILER_ID_RUN 1) +set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP;ixx;cppm;ccm;cxxm;c++m) +set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC) + +foreach (lang IN ITEMS C OBJC OBJCXX) + if (CMAKE_${lang}_COMPILER_ID_RUN) + foreach(extension IN LISTS CMAKE_${lang}_SOURCE_FILE_EXTENSIONS) + list(REMOVE_ITEM CMAKE_CXX_SOURCE_FILE_EXTENSIONS ${extension}) + endforeach() + endif() +endforeach() + +set(CMAKE_CXX_LINKER_PREFERENCE 30) +set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1) +set(CMAKE_CXX_LINKER_DEPFILE_SUPPORTED ) +set(CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED ) +set(CMAKE_CXX_LINKER_PUSHPOP_STATE_SUPPORTED ) + +# Save compiler ABI information. +set(CMAKE_CXX_SIZEOF_DATA_PTR "8") +set(CMAKE_CXX_COMPILER_ABI "") +set(CMAKE_CXX_BYTE_ORDER "LITTLE_ENDIAN") +set(CMAKE_CXX_LIBRARY_ARCHITECTURE "") + +if(CMAKE_CXX_SIZEOF_DATA_PTR) + set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}") +endif() + +if(CMAKE_CXX_COMPILER_ABI) + set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}") +endif() + +if(CMAKE_CXX_LIBRARY_ARCHITECTURE) + set(CMAKE_LIBRARY_ARCHITECTURE "") +endif() + +set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "") +if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX) + set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}") +endif() + + + + + +set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "/usr/local/include;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1;/Library/Developer/CommandLineTools/usr/lib/clang/17/include;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include;/Library/Developer/CommandLineTools/usr/include") +set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "c++") +set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift") +set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks") +set(CMAKE_CXX_COMPILER_CLANG_RESOURCE_DIR "") + +set(CMAKE_CXX_COMPILER_IMPORT_STD "") +### Imported target for C++23 standard library +set(CMAKE_CXX23_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE "Unsupported generator: Unix Makefiles") + + + diff --git a/build/CMakeFiles/4.1.1/CMakeDetermineCompilerABI_C.bin b/build/CMakeFiles/4.1.1/CMakeDetermineCompilerABI_C.bin new file mode 100755 index 0000000000000000000000000000000000000000..70319f4187cbadaca8c5c1438072ad83258d575a GIT binary patch literal 33560 zcmeI5Uuau(6vux_Q%PuDTJg`cQzH&pMOvA4D`iMBlcm{0S~L$~o&3n3oA$CM(IlnK zsF*Paif+r;gL6;9irs@E&N0kGX9WN3L0F4pf)BDOlU4CS6to*Gp7Xmm%bykn_uzxy z1LyvJ=bZaH_xwKh=0{)3xp4iLo1H>f1c{Zjo780!;-GNROo;nQhe@Rz_4s|eeb0@s zw^`KUX4fi<^SnkkN*VS=!p&i|-rejIrrl0%Rw_w}lzD9?ZyGeT=5yPP&6Lbr*yerR z5}PRAn$twecqW~zCv45PXy&VxT(cW1jQkka{|qQFb%GK{H>(Ou}|+j>i1_9*(O#UQgK55OJFy0gXyp#x`=Y*50+0iD*8m z?o<~k*Um@y3~?LlYi8?M|IUJe&@P`hPy=3Usf%ou^;`@I*Hf#B?ExW*T|6lY!Azbq z?FiY!WHX!fq^HQfgGT#)A!caoARQ%TsqK|VzUSY_I$Go+rQeq5pUP$XC-qo=qA-~t zo27@8>-gFcIPukQ+m0OTy>Rv9R{!v3^0`QjJvr&)cdEJCZu=42;}lc-qmgA3?T_>D zcit%c+(A-~8Q+7SenyshEmY6*g__!D~);*=})k zTx*QIO|gPLvu3P&HD-yQYK!^XUCo# zd_jm%U}Q&ZC|{6THlroRvVrZ=AMypfA&;P_S`#beHJQmKwCq?g5Do`@(iiduJfT`V znvGA4MYE|b1LQHz=MCojZXEfn<+Iq$BPsvAz~WZd`i0N)PCAdf%#)rDeO?Ly0T2KI z5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X z009sH0T2KI5C8!X009sH0T2KI5C8#1p!NUHfdvFW00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1VG@wA>g9Na_@6imd3W;Hp8W^+)KPBYVM})=Zi-~?KEv0^}AS(2n;PT~^gv-gN!#P%@*Icq;s?-1>B4b5gwfS#ZynE7h0hzyG86*eBc0 z`_Eko?mfJ6anA?SZ~icTKKlME@12fhzF1CHY~L&`?X`|X+N-l?X7?TZcHxcT*Pocb z-T1}DbDw|sKyrHS*H3#EPV8E^+2!52VR`n|ON)mFDkr@+Za4owt)KgS%RRfjhll>O de*E*?)zm{3ZS?Hvb=8H_Epsq~Wi!$kpV$reT%L)pLqOK3C=iJ}#*Zz_f1^49q z9yq!8+;i^fxu4v^?L#?VE&X((N(h%Aag%N&?ePe4O!#Rg#Aed{q*C?-J43ypC%SpH zSk&g?rJF3yGev5O9aryJS2*%ms6A{0FO<|pHp?b1hJ@>}Yht@eh(Zld3PLcG-Ig6A zdnehyyEep0%AqG2R!Gz{s9hE#qiMK((< zDcAAo+wqCl7h1kNzhPbW#BXoRG}4-%)I5`qF27IB)p5&T^E^a8bv_zd9;EYe9=_*J zVUy3xK6M|?4Xx*_E45HP$sIfDchyr-dP3FN>Wmki^lMif|71R!(}wH!T3aYuhzCfy zo~eQm{9SLiZ05_dg_Qk;qjc}gL-8^M1V8`;KmY_l;D1P9c~ZNq}ECch3xI=c5{^)W5eFH?tdM&HiX=Y)uayE_tla(Ss487 zU2Vi&M)?Q866X z(}H?kasUhW#H^-0-2am6p#JU3-fG!V%YM|dN7<%M z;fA1GoheI~tLCWE6J&9DEO*ly9m6##+(h54j+CVJnDPoyA;i&w->q73;LTv(d9>&PPq?mg4|f^qu7z{MkNZ!KK9RP+3s-@QL^_JdgF z;$r`t=bQQYgYNEF`SSR=@v&nIpS-;PrH9|SRsYG|XK!?DdDqzfRGqha@nBs??R%3Q z6F-hzooPOQVDPi?Th0GZ`?vjZ#?$l6@87@J_{y)BPe1*5aQ){W?)&(Qi95zV+S>c= H{hP&KO4yv# literal 0 HcmV?d00001 diff --git a/build/CMakeFiles/4.1.1/CMakeSystem.cmake b/build/CMakeFiles/4.1.1/CMakeSystem.cmake new file mode 100644 index 000000000..0c52e24fd --- /dev/null +++ b/build/CMakeFiles/4.1.1/CMakeSystem.cmake @@ -0,0 +1,15 @@ +set(CMAKE_HOST_SYSTEM "Darwin-24.3.0") +set(CMAKE_HOST_SYSTEM_NAME "Darwin") +set(CMAKE_HOST_SYSTEM_VERSION "24.3.0") +set(CMAKE_HOST_SYSTEM_PROCESSOR "arm64") + + + +set(CMAKE_SYSTEM "Darwin-24.3.0") +set(CMAKE_SYSTEM_NAME "Darwin") +set(CMAKE_SYSTEM_VERSION "24.3.0") +set(CMAKE_SYSTEM_PROCESSOR "arm64") + +set(CMAKE_CROSSCOMPILING "FALSE") + +set(CMAKE_SYSTEM_LOADED 1) diff --git a/build/CMakeFiles/4.1.1/CompilerIdC/CMakeCCompilerId.c b/build/CMakeFiles/4.1.1/CompilerIdC/CMakeCCompilerId.c new file mode 100644 index 000000000..ab3c35931 --- /dev/null +++ b/build/CMakeFiles/4.1.1/CompilerIdC/CMakeCCompilerId.c @@ -0,0 +1,934 @@ +#ifdef __cplusplus +# error "A C++ compiler has been selected for C." +#endif + +#if defined(__18CXX) +# define ID_VOID_MAIN +#endif +#if defined(__CLASSIC_C__) +/* cv-qualifiers did not exist in K&R C */ +# define const +# define volatile +#endif + +#if !defined(__has_include) +/* If the compiler does not have __has_include, pretend the answer is + always no. */ +# define __has_include(x) 0 +#endif + + +/* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__INTEL_COMPILER) || defined(__ICC) +# define COMPILER_ID "Intel" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# if defined(__GNUC__) +# define SIMULATE_ID "GNU" +# endif + /* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later, + except that a few beta releases use the old format with V=2021. */ +# if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || __INTEL_COMPILER == 202111 +# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100) +# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10) +# if defined(__INTEL_COMPILER_UPDATE) +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) +# else +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) +# endif +# else +# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER) +# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE) + /* The third version component from --version is an update index, + but no macro is provided for it. */ +# define COMPILER_VERSION_PATCH DEC(0) +# endif +# if defined(__INTEL_COMPILER_BUILD_DATE) + /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ +# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) +# endif +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# if defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# elif defined(__GNUG__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || defined(__INTEL_LLVM_COMPILER) +# define COMPILER_ID "IntelLLVM" +#if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +#endif +#if defined(__GNUC__) +# define SIMULATE_ID "GNU" +#endif +/* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and + * later. Look for 6 digit vs. 8 digit version number to decide encoding. + * VVVV is no smaller than the current year when a version is released. + */ +#if __INTEL_LLVM_COMPILER < 1000000L +# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/100) +# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10) +#else +# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/10000) +# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/100 % 100) +# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100) +#endif +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif +#if defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +#elif defined(__GNUG__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +#endif +#if defined(__GNUC_MINOR__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +#endif +#if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +#endif + +#elif defined(__PATHCC__) +# define COMPILER_ID "PathScale" +# define COMPILER_VERSION_MAJOR DEC(__PATHCC__) +# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) +# if defined(__PATHCC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) +# endif + +#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) +# define COMPILER_ID "Embarcadero" +# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF) +# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF) +# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) + +#elif defined(__BORLANDC__) +# define COMPILER_ID "Borland" + /* __BORLANDC__ = 0xVRR */ +# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8) +# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) + +#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 +# define COMPILER_ID "Watcom" + /* __WATCOMC__ = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__WATCOMC__) +# define COMPILER_ID "OpenWatcom" + /* __WATCOMC__ = VVRP + 1100 */ +# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__SUNPRO_C) +# define COMPILER_ID "SunPro" +# if __SUNPRO_C >= 0x5100 + /* __SUNPRO_C = 0xVRRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_C>>12) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_C>>4 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_C & 0xF) +# else + /* __SUNPRO_CC = 0xVRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_C>>8) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_C>>4 & 0xF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_C & 0xF) +# endif + +#elif defined(__HP_cc) +# define COMPILER_ID "HP" + /* __HP_cc = VVRRPP */ +# define COMPILER_VERSION_MAJOR DEC(__HP_cc/10000) +# define COMPILER_VERSION_MINOR DEC(__HP_cc/100 % 100) +# define COMPILER_VERSION_PATCH DEC(__HP_cc % 100) + +#elif defined(__DECC) +# define COMPILER_ID "Compaq" + /* __DECC_VER = VVRRTPPPP */ +# define COMPILER_VERSION_MAJOR DEC(__DECC_VER/10000000) +# define COMPILER_VERSION_MINOR DEC(__DECC_VER/100000 % 100) +# define COMPILER_VERSION_PATCH DEC(__DECC_VER % 10000) + +#elif defined(__IBMC__) && defined(__COMPILER_VER__) +# define COMPILER_ID "zOS" + /* __IBMC__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10) + +#elif defined(__open_xl__) && defined(__clang__) +# define COMPILER_ID "IBMClang" +# define COMPILER_VERSION_MAJOR DEC(__open_xl_version__) +# define COMPILER_VERSION_MINOR DEC(__open_xl_release__) +# define COMPILER_VERSION_PATCH DEC(__open_xl_modification__) +# define COMPILER_VERSION_TWEAK DEC(__open_xl_ptf_fix_level__) +# define COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(__ibmxl__) && defined(__clang__) +# define COMPILER_ID "XLClang" +# define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) +# define COMPILER_VERSION_MINOR DEC(__ibmxl_release__) +# define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) +# define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) + + +#elif defined(__IBMC__) && !defined(__COMPILER_VER__) && __IBMC__ >= 800 +# define COMPILER_ID "XL" + /* __IBMC__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10) + +#elif defined(__IBMC__) && !defined(__COMPILER_VER__) && __IBMC__ < 800 +# define COMPILER_ID "VisualAge" + /* __IBMC__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10) + +#elif defined(__NVCOMPILER) +# define COMPILER_ID "NVHPC" +# define COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__) +# define COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__) +# if defined(__NVCOMPILER_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__) +# endif + +#elif defined(__PGI) +# define COMPILER_ID "PGI" +# define COMPILER_VERSION_MAJOR DEC(__PGIC__) +# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) +# if defined(__PGIC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) +# endif + +#elif defined(__clang__) && defined(__cray__) +# define COMPILER_ID "CrayClang" +# define COMPILER_VERSION_MAJOR DEC(__cray_major__) +# define COMPILER_VERSION_MINOR DEC(__cray_minor__) +# define COMPILER_VERSION_PATCH DEC(__cray_patchlevel__) +# define COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(_CRAYC) +# define COMPILER_ID "Cray" +# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) +# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) + +#elif defined(__TI_COMPILER_VERSION__) +# define COMPILER_ID "TI" + /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ +# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000) +# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000) +# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) + +#elif defined(__CLANG_FUJITSU) +# define COMPILER_ID "FujitsuClang" +# define COMPILER_VERSION_MAJOR DEC(__FCC_major__) +# define COMPILER_VERSION_MINOR DEC(__FCC_minor__) +# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +# define COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(__FUJITSU) +# define COMPILER_ID "Fujitsu" +# if defined(__FCC_version__) +# define COMPILER_VERSION __FCC_version__ +# elif defined(__FCC_major__) +# define COMPILER_VERSION_MAJOR DEC(__FCC_major__) +# define COMPILER_VERSION_MINOR DEC(__FCC_minor__) +# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +# endif +# if defined(__fcc_version) +# define COMPILER_VERSION_INTERNAL DEC(__fcc_version) +# elif defined(__FCC_VERSION) +# define COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION) +# endif + + +#elif defined(__ghs__) +# define COMPILER_ID "GHS" +/* __GHS_VERSION_NUMBER = VVVVRP */ +# ifdef __GHS_VERSION_NUMBER +# define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) +# define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) +# define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) +# endif + +#elif defined(__TASKING__) +# define COMPILER_ID "Tasking" + # define COMPILER_VERSION_MAJOR DEC(__VERSION__/1000) + # define COMPILER_VERSION_MINOR DEC(__VERSION__ % 100) +# define COMPILER_VERSION_INTERNAL DEC(__VERSION__) + +#elif defined(__ORANGEC__) +# define COMPILER_ID "OrangeC" +# define COMPILER_VERSION_MAJOR DEC(__ORANGEC_MAJOR__) +# define COMPILER_VERSION_MINOR DEC(__ORANGEC_MINOR__) +# define COMPILER_VERSION_PATCH DEC(__ORANGEC_PATCHLEVEL__) + +#elif defined(__RENESAS__) +# define COMPILER_ID "Renesas" +/* __RENESAS_VERSION__ = 0xVVRRPP00 */ +# define COMPILER_VERSION_MAJOR HEX(__RENESAS_VERSION__ >> 24 & 0xFF) +# define COMPILER_VERSION_MINOR HEX(__RENESAS_VERSION__ >> 16 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__RENESAS_VERSION__ >> 8 & 0xFF) + +#elif defined(__TINYC__) +# define COMPILER_ID "TinyCC" + +#elif defined(__BCC__) +# define COMPILER_ID "Bruce" + +#elif defined(__SCO_VERSION__) +# define COMPILER_ID "SCO" + +#elif defined(__ARMCC_VERSION) && !defined(__clang__) +# define COMPILER_ID "ARMCC" +#if __ARMCC_VERSION >= 1000000 + /* __ARMCC_VERSION = VRRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#else + /* __ARMCC_VERSION = VRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#endif + + +#elif defined(__clang__) && defined(__apple_build_version__) +# define COMPILER_ID "AppleClang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) + +#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) +# define COMPILER_ID "ARMClang" + # define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION/100 % 100) +# define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) + +#elif defined(__clang__) && defined(__ti__) +# define COMPILER_ID "TIClang" + # define COMPILER_VERSION_MAJOR DEC(__ti_major__) + # define COMPILER_VERSION_MINOR DEC(__ti_minor__) + # define COMPILER_VERSION_PATCH DEC(__ti_patchlevel__) +# define COMPILER_VERSION_INTERNAL DEC(__ti_version__) + +#elif defined(__clang__) +# define COMPILER_ID "Clang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif + +#elif defined(__LCC__) && (defined(__GNUC__) || defined(__GNUG__) || defined(__MCST__)) +# define COMPILER_ID "LCC" +# define COMPILER_VERSION_MAJOR DEC(__LCC__ / 100) +# define COMPILER_VERSION_MINOR DEC(__LCC__ % 100) +# if defined(__LCC_MINOR__) +# define COMPILER_VERSION_PATCH DEC(__LCC_MINOR__) +# endif +# if defined(__GNUC__) && defined(__GNUC_MINOR__) +# define SIMULATE_ID "GNU" +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif +# endif + +#elif defined(__GNUC__) +# define COMPILER_ID "GNU" +# define COMPILER_VERSION_MAJOR DEC(__GNUC__) +# if defined(__GNUC_MINOR__) +# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(_MSC_VER) +# define COMPILER_ID "MSVC" + /* _MSC_VER = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) +# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) +# if defined(_MSC_FULL_VER) +# if _MSC_VER >= 1400 + /* _MSC_FULL_VER = VVRRPPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) +# else + /* _MSC_FULL_VER = VVRRPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) +# endif +# endif +# if defined(_MSC_BUILD) +# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) +# endif + +#elif defined(_ADI_COMPILER) +# define COMPILER_ID "ADSP" +#if defined(__VERSIONNUM__) + /* __VERSIONNUM__ = 0xVVRRPPTT */ +# define COMPILER_VERSION_MAJOR DEC(__VERSIONNUM__ >> 24 & 0xFF) +# define COMPILER_VERSION_MINOR DEC(__VERSIONNUM__ >> 16 & 0xFF) +# define COMPILER_VERSION_PATCH DEC(__VERSIONNUM__ >> 8 & 0xFF) +# define COMPILER_VERSION_TWEAK DEC(__VERSIONNUM__ & 0xFF) +#endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# define COMPILER_ID "IAR" +# if defined(__VER__) && defined(__ICCARM__) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) +# define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) +# define COMPILER_VERSION_PATCH DEC((__VER__) % 1000) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__)) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 100) +# define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100)) +# define COMPILER_VERSION_PATCH DEC(__SUBVERSION__) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# endif + +#elif defined(__DCC__) && defined(_DIAB_TOOL) +# define COMPILER_ID "Diab" + # define COMPILER_VERSION_MAJOR DEC(__VERSION_MAJOR_NUMBER__) + # define COMPILER_VERSION_MINOR DEC(__VERSION_MINOR_NUMBER__) + # define COMPILER_VERSION_PATCH DEC(__VERSION_ARCH_FEATURE_NUMBER__) + # define COMPILER_VERSION_TWEAK DEC(__VERSION_BUG_FIX_NUMBER__) + + +#elif defined(__SDCC_VERSION_MAJOR) || defined(SDCC) +# define COMPILER_ID "SDCC" +# if defined(__SDCC_VERSION_MAJOR) +# define COMPILER_VERSION_MAJOR DEC(__SDCC_VERSION_MAJOR) +# define COMPILER_VERSION_MINOR DEC(__SDCC_VERSION_MINOR) +# define COMPILER_VERSION_PATCH DEC(__SDCC_VERSION_PATCH) +# else + /* SDCC = VRP */ +# define COMPILER_VERSION_MAJOR DEC(SDCC/100) +# define COMPILER_VERSION_MINOR DEC(SDCC/10 % 10) +# define COMPILER_VERSION_PATCH DEC(SDCC % 10) +# endif + + +/* These compilers are either not known or too old to define an + identification macro. Try to identify the platform and guess that + it is the native compiler. */ +#elif defined(__hpux) || defined(__hpua) +# define COMPILER_ID "HP" + +#else /* unknown compiler */ +# define COMPILER_ID "" +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]"; +#ifdef SIMULATE_ID +char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]"; +#endif + +#ifdef __QNXNTO__ +char const* qnxnto = "INFO" ":" "qnxnto[]"; +#endif + +#if defined(__CRAYXT_COMPUTE_LINUX_TARGET) +char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]"; +#endif + +#define STRINGIFY_HELPER(X) #X +#define STRINGIFY(X) STRINGIFY_HELPER(X) + +/* Identify known platforms by name. */ +#if defined(__linux) || defined(__linux__) || defined(linux) +# define PLATFORM_ID "Linux" + +#elif defined(__MSYS__) +# define PLATFORM_ID "MSYS" + +#elif defined(__CYGWIN__) +# define PLATFORM_ID "Cygwin" + +#elif defined(__MINGW32__) +# define PLATFORM_ID "MinGW" + +#elif defined(__APPLE__) +# define PLATFORM_ID "Darwin" + +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +# define PLATFORM_ID "Windows" + +#elif defined(__FreeBSD__) || defined(__FreeBSD) +# define PLATFORM_ID "FreeBSD" + +#elif defined(__NetBSD__) || defined(__NetBSD) +# define PLATFORM_ID "NetBSD" + +#elif defined(__OpenBSD__) || defined(__OPENBSD) +# define PLATFORM_ID "OpenBSD" + +#elif defined(__sun) || defined(sun) +# define PLATFORM_ID "SunOS" + +#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) +# define PLATFORM_ID "AIX" + +#elif defined(__hpux) || defined(__hpux__) +# define PLATFORM_ID "HP-UX" + +#elif defined(__HAIKU__) +# define PLATFORM_ID "Haiku" + +#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS) +# define PLATFORM_ID "BeOS" + +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_ID "QNX" + +#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__) +# define PLATFORM_ID "Tru64" + +#elif defined(__riscos) || defined(__riscos__) +# define PLATFORM_ID "RISCos" + +#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__) +# define PLATFORM_ID "SINIX" + +#elif defined(__UNIX_SV__) +# define PLATFORM_ID "UNIX_SV" + +#elif defined(__bsdos__) +# define PLATFORM_ID "BSDOS" + +#elif defined(_MPRAS) || defined(MPRAS) +# define PLATFORM_ID "MP-RAS" + +#elif defined(__osf) || defined(__osf__) +# define PLATFORM_ID "OSF1" + +#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv) +# define PLATFORM_ID "SCO_SV" + +#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX) +# define PLATFORM_ID "ULTRIX" + +#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX) +# define PLATFORM_ID "Xenix" + +#elif defined(__WATCOMC__) +# if defined(__LINUX__) +# define PLATFORM_ID "Linux" + +# elif defined(__DOS__) +# define PLATFORM_ID "DOS" + +# elif defined(__OS2__) +# define PLATFORM_ID "OS2" + +# elif defined(__WINDOWS__) +# define PLATFORM_ID "Windows3x" + +# elif defined(__VXWORKS__) +# define PLATFORM_ID "VxWorks" + +# else /* unknown platform */ +# define PLATFORM_ID +# endif + +#elif defined(__INTEGRITY) +# if defined(INT_178B) +# define PLATFORM_ID "Integrity178" + +# else /* regular Integrity */ +# define PLATFORM_ID "Integrity" +# endif + +# elif defined(_ADI_COMPILER) +# define PLATFORM_ID "ADSP" + +#else /* unknown platform */ +# define PLATFORM_ID + +#endif + +/* For windows compilers MSVC and Intel we can determine + the architecture of the compiler being used. This is because + the compilers do not have flags that can change the architecture, + but rather depend on which compiler is being used +*/ +#if defined(_WIN32) && defined(_MSC_VER) +# if defined(_M_IA64) +# define ARCHITECTURE_ID "IA64" + +# elif defined(_M_ARM64EC) +# define ARCHITECTURE_ID "ARM64EC" + +# elif defined(_M_X64) || defined(_M_AMD64) +# define ARCHITECTURE_ID "x64" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# elif defined(_M_ARM64) +# define ARCHITECTURE_ID "ARM64" + +# elif defined(_M_ARM) +# if _M_ARM == 4 +# define ARCHITECTURE_ID "ARMV4I" +# elif _M_ARM == 5 +# define ARCHITECTURE_ID "ARMV5I" +# else +# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM) +# endif + +# elif defined(_M_MIPS) +# define ARCHITECTURE_ID "MIPS" + +# elif defined(_M_SH) +# define ARCHITECTURE_ID "SHx" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__WATCOMC__) +# if defined(_M_I86) +# define ARCHITECTURE_ID "I86" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# if defined(__ICCARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__ICCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__ICCRH850__) +# define ARCHITECTURE_ID "RH850" + +# elif defined(__ICCRL78__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__ICCRISCV__) +# define ARCHITECTURE_ID "RISCV" + +# elif defined(__ICCAVR__) +# define ARCHITECTURE_ID "AVR" + +# elif defined(__ICC430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__ICCV850__) +# define ARCHITECTURE_ID "V850" + +# elif defined(__ICC8051__) +# define ARCHITECTURE_ID "8051" + +# elif defined(__ICCSTM8__) +# define ARCHITECTURE_ID "STM8" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__ghs__) +# if defined(__PPC64__) +# define ARCHITECTURE_ID "PPC64" + +# elif defined(__ppc__) +# define ARCHITECTURE_ID "PPC" + +# elif defined(__ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__x86_64__) +# define ARCHITECTURE_ID "x64" + +# elif defined(__i386__) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__clang__) && defined(__ti__) +# if defined(__ARM_ARCH) +# define ARCHITECTURE_ID "ARM" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__TI_COMPILER_VERSION__) +# if defined(__TI_ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__MSP430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__TMS320C28XX__) +# define ARCHITECTURE_ID "TMS320C28x" + +# elif defined(__TMS320C6X__) || defined(_TMS320C6X) +# define ARCHITECTURE_ID "TMS320C6x" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +# elif defined(__ADSPSHARC__) +# define ARCHITECTURE_ID "SHARC" + +# elif defined(__ADSPBLACKFIN__) +# define ARCHITECTURE_ID "Blackfin" + +#elif defined(__TASKING__) + +# if defined(__CTC__) || defined(__CPTC__) +# define ARCHITECTURE_ID "TriCore" + +# elif defined(__CMCS__) +# define ARCHITECTURE_ID "MCS" + +# elif defined(__CARM__) || defined(__CPARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__CARC__) +# define ARCHITECTURE_ID "ARC" + +# elif defined(__C51__) +# define ARCHITECTURE_ID "8051" + +# elif defined(__CPCP__) +# define ARCHITECTURE_ID "PCP" + +# else +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__RENESAS__) +# if defined(__CCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__CCRL__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__CCRH__) +# define ARCHITECTURE_ID "RH850" + +# else +# define ARCHITECTURE_ID "" +# endif + +#else +# define ARCHITECTURE_ID +#endif + +/* Convert integer to decimal digit literals. */ +#define DEC(n) \ + ('0' + (((n) / 10000000)%10)), \ + ('0' + (((n) / 1000000)%10)), \ + ('0' + (((n) / 100000)%10)), \ + ('0' + (((n) / 10000)%10)), \ + ('0' + (((n) / 1000)%10)), \ + ('0' + (((n) / 100)%10)), \ + ('0' + (((n) / 10)%10)), \ + ('0' + ((n) % 10)) + +/* Convert integer to hex digit literals. */ +#define HEX(n) \ + ('0' + ((n)>>28 & 0xF)), \ + ('0' + ((n)>>24 & 0xF)), \ + ('0' + ((n)>>20 & 0xF)), \ + ('0' + ((n)>>16 & 0xF)), \ + ('0' + ((n)>>12 & 0xF)), \ + ('0' + ((n)>>8 & 0xF)), \ + ('0' + ((n)>>4 & 0xF)), \ + ('0' + ((n) & 0xF)) + +/* Construct a string literal encoding the version number. */ +#ifdef COMPILER_VERSION +char const* info_version = "INFO" ":" "compiler_version[" COMPILER_VERSION "]"; + +/* Construct a string literal encoding the version number components. */ +#elif defined(COMPILER_VERSION_MAJOR) +char const info_version[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', + COMPILER_VERSION_MAJOR, +# ifdef COMPILER_VERSION_MINOR + '.', COMPILER_VERSION_MINOR, +# ifdef COMPILER_VERSION_PATCH + '.', COMPILER_VERSION_PATCH, +# ifdef COMPILER_VERSION_TWEAK + '.', COMPILER_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct a string literal encoding the internal version number. */ +#ifdef COMPILER_VERSION_INTERNAL +char const info_version_internal[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_', + 'i','n','t','e','r','n','a','l','[', + COMPILER_VERSION_INTERNAL,']','\0'}; +#elif defined(COMPILER_VERSION_INTERNAL_STR) +char const* info_version_internal = "INFO" ":" "compiler_version_internal[" COMPILER_VERSION_INTERNAL_STR "]"; +#endif + +/* Construct a string literal encoding the version number components. */ +#ifdef SIMULATE_VERSION_MAJOR +char const info_simulate_version[] = { + 'I', 'N', 'F', 'O', ':', + 's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[', + SIMULATE_VERSION_MAJOR, +# ifdef SIMULATE_VERSION_MINOR + '.', SIMULATE_VERSION_MINOR, +# ifdef SIMULATE_VERSION_PATCH + '.', SIMULATE_VERSION_PATCH, +# ifdef SIMULATE_VERSION_TWEAK + '.', SIMULATE_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]"; +char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]"; + + + +#define C_STD_99 199901L +#define C_STD_11 201112L +#define C_STD_17 201710L +#define C_STD_23 202311L + +#ifdef __STDC_VERSION__ +# define C_STD __STDC_VERSION__ +#endif + +#if !defined(__STDC__) && !defined(__clang__) && !defined(__RENESAS__) +# if defined(_MSC_VER) || defined(__ibmxl__) || defined(__IBMC__) +# define C_VERSION "90" +# else +# define C_VERSION +# endif +#elif C_STD > C_STD_17 +# define C_VERSION "23" +#elif C_STD > C_STD_11 +# define C_VERSION "17" +#elif C_STD > C_STD_99 +# define C_VERSION "11" +#elif C_STD >= C_STD_99 +# define C_VERSION "99" +#else +# define C_VERSION "90" +#endif +const char* info_language_standard_default = + "INFO" ":" "standard_default[" C_VERSION "]"; + +const char* info_language_extensions_default = "INFO" ":" "extensions_default[" +#if (defined(__clang__) || defined(__GNUC__) || defined(__xlC__) || \ + defined(__TI_COMPILER_VERSION__) || defined(__RENESAS__)) && \ + !defined(__STRICT_ANSI__) + "ON" +#else + "OFF" +#endif +"]"; + +/*--------------------------------------------------------------------------*/ + +#ifdef ID_VOID_MAIN +void main() {} +#else +# if defined(__CLASSIC_C__) +int main(argc, argv) int argc; char *argv[]; +# else +int main(int argc, char* argv[]) +# endif +{ + int require = 0; + require += info_compiler[argc]; + require += info_platform[argc]; + require += info_arch[argc]; +#ifdef COMPILER_VERSION_MAJOR + require += info_version[argc]; +#endif +#if defined(COMPILER_VERSION_INTERNAL) || defined(COMPILER_VERSION_INTERNAL_STR) + require += info_version_internal[argc]; +#endif +#ifdef SIMULATE_ID + require += info_simulate[argc]; +#endif +#ifdef SIMULATE_VERSION_MAJOR + require += info_simulate_version[argc]; +#endif +#if defined(__CRAYXT_COMPUTE_LINUX_TARGET) + require += info_cray[argc]; +#endif + require += info_language_standard_default[argc]; + require += info_language_extensions_default[argc]; + (void)argv; + return require; +} +#endif diff --git a/build/CMakeFiles/4.1.1/CompilerIdC/a.out b/build/CMakeFiles/4.1.1/CompilerIdC/a.out new file mode 100755 index 0000000000000000000000000000000000000000..a27d804924265c7ec7ca192bab4f890795f56aaa GIT binary patch literal 33736 zcmeI5Uuau(6vt1})F!qynU=Xx?c&39%1F~z>`=sXw%e*(Te>|&>-_DuxoxxMpGm5l z%r2V@>QvSWLhaMuhPu7XDJ?iCD$)l*`l3!y>R(ovAR^2Sv7U49FKL>_DQ=J71LywE z`JLbI+;czom%JtC^J~BSRYT+;7ANZ(R=~irgIFe91&+xxmtVQ##)xI*It#6QxEHFGLd& z6-^8#d0}b3uXMh!P9lD3O~v`;Jxp48+S~TD6-7e&5b$V8$ymXYqSm|OQK^WLHQ3Oi zRQY_H@(V)t{=8c{E}yfVon23Mw0GvuNUo$V_C@BT7#67~Uz7L`66JhicRHayj$V{#$38dCt%3U?uYM;rCj$^|+NMT@UcA^?X*Gi23Fu&ptlq#Ul6J z!YVQQJZ!g4N}(36XZN8@){F9r>xaE>JG~;%7sxReZmDh=R%Eu%Z z*E{ZxuAL~Gv$p&`tClt8V+&jt*FP~^p}y$s+SdK>Kr)_+#>{kITPhVZACDP{p}k~9 zQZXYtm`um}+Kn`ST=lGx9vJS^b|z~iB1Sr*BIckm63g~Awdx3epP7kFl)p9#vFD8QU(k}K)_`pUgUd!!tIRdE?3I@xujVfOR*FYC3vyOMJc{PO&%`bkId3(b z#ivgq`Smd>UIZHiKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1VG@X6R>~(>7%XI z@6de|)?-AEqta-A#cjofYD1zO+*|ZE(>Tudbje?$TZh?r^eL=%HaMd7#+P0(1rc3CYVWqTdc>D=bAR($?11YgMZ z@6o@~&xZ8o7T-^*OwG!w*&<+@1q`-Z zU{Qu^$%<8!?RWv9?dCZuixSs$?c17JuJ8>urA(1Jo?~2&QF3zeP7B?$JRi2AL>{7J zLBEsRiT^j5)5{!2AUTqysvdXew)f6?Kk!`q==`W*GzH%EK69Yz=Od?#U!QBZFn#vO znRDUfr;CHLt}o~34>-HSl}m@uAAa%p*B4&h@yh1YHyZ!V*|5;~{_XB3HlGN*6R+EO zcH;N`KU#V(Uhb%S=)RUedv7%VzwLkX diff --git a/build/CMakeFiles/4.1.1/CompilerIdCXX/CMakeCXXCompilerId.cpp b/build/CMakeFiles/4.1.1/CompilerIdCXX/CMakeCXXCompilerId.cpp new file mode 100644 index 000000000..b35f567c2 --- /dev/null +++ b/build/CMakeFiles/4.1.1/CompilerIdCXX/CMakeCXXCompilerId.cpp @@ -0,0 +1,949 @@ +/* This source file must have a .cpp extension so that all C++ compilers + recognize the extension without flags. Borland does not know .cxx for + example. */ +#ifndef __cplusplus +# error "A C compiler has been selected for C++." +#endif + +#if !defined(__has_include) +/* If the compiler does not have __has_include, pretend the answer is + always no. */ +# define __has_include(x) 0 +#endif + + +/* Version number components: V=Version, R=Revision, P=Patch + Version date components: YYYY=Year, MM=Month, DD=Day */ + +#if defined(__INTEL_COMPILER) || defined(__ICC) +# define COMPILER_ID "Intel" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# if defined(__GNUC__) +# define SIMULATE_ID "GNU" +# endif + /* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later, + except that a few beta releases use the old format with V=2021. */ +# if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || __INTEL_COMPILER == 202111 +# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100) +# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10) +# if defined(__INTEL_COMPILER_UPDATE) +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE) +# else +# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10) +# endif +# else +# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER) +# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE) + /* The third version component from --version is an update index, + but no macro is provided for it. */ +# define COMPILER_VERSION_PATCH DEC(0) +# endif +# if defined(__INTEL_COMPILER_BUILD_DATE) + /* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */ +# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE) +# endif +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# if defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# elif defined(__GNUG__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || defined(__INTEL_LLVM_COMPILER) +# define COMPILER_ID "IntelLLVM" +#if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +#endif +#if defined(__GNUC__) +# define SIMULATE_ID "GNU" +#endif +/* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and + * later. Look for 6 digit vs. 8 digit version number to decide encoding. + * VVVV is no smaller than the current year when a version is released. + */ +#if __INTEL_LLVM_COMPILER < 1000000L +# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/100) +# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10) +#else +# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/10000) +# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/100 % 100) +# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100) +#endif +#if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +#endif +#if defined(__GNUC__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +#elif defined(__GNUG__) +# define SIMULATE_VERSION_MAJOR DEC(__GNUG__) +#endif +#if defined(__GNUC_MINOR__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +#endif +#if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +#endif + +#elif defined(__PATHCC__) +# define COMPILER_ID "PathScale" +# define COMPILER_VERSION_MAJOR DEC(__PATHCC__) +# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__) +# if defined(__PATHCC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__) +# endif + +#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__) +# define COMPILER_ID "Embarcadero" +# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF) +# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF) +# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF) + +#elif defined(__BORLANDC__) +# define COMPILER_ID "Borland" + /* __BORLANDC__ = 0xVRR */ +# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8) +# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF) + +#elif defined(__WATCOMC__) && __WATCOMC__ < 1200 +# define COMPILER_ID "Watcom" + /* __WATCOMC__ = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__WATCOMC__) +# define COMPILER_ID "OpenWatcom" + /* __WATCOMC__ = VVRP + 1100 */ +# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100) +# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10) +# if (__WATCOMC__ % 10) > 0 +# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10) +# endif + +#elif defined(__SUNPRO_CC) +# define COMPILER_ID "SunPro" +# if __SUNPRO_CC >= 0x5100 + /* __SUNPRO_CC = 0xVRRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>12) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +# else + /* __SUNPRO_CC = 0xVRP */ +# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>8) +# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xF) +# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF) +# endif + +#elif defined(__HP_aCC) +# define COMPILER_ID "HP" + /* __HP_aCC = VVRRPP */ +# define COMPILER_VERSION_MAJOR DEC(__HP_aCC/10000) +# define COMPILER_VERSION_MINOR DEC(__HP_aCC/100 % 100) +# define COMPILER_VERSION_PATCH DEC(__HP_aCC % 100) + +#elif defined(__DECCXX) +# define COMPILER_ID "Compaq" + /* __DECCXX_VER = VVRRTPPPP */ +# define COMPILER_VERSION_MAJOR DEC(__DECCXX_VER/10000000) +# define COMPILER_VERSION_MINOR DEC(__DECCXX_VER/100000 % 100) +# define COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000) + +#elif defined(__IBMCPP__) && defined(__COMPILER_VER__) +# define COMPILER_ID "zOS" + /* __IBMCPP__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__open_xl__) && defined(__clang__) +# define COMPILER_ID "IBMClang" +# define COMPILER_VERSION_MAJOR DEC(__open_xl_version__) +# define COMPILER_VERSION_MINOR DEC(__open_xl_release__) +# define COMPILER_VERSION_PATCH DEC(__open_xl_modification__) +# define COMPILER_VERSION_TWEAK DEC(__open_xl_ptf_fix_level__) +# define COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(__ibmxl__) && defined(__clang__) +# define COMPILER_ID "XLClang" +# define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__) +# define COMPILER_VERSION_MINOR DEC(__ibmxl_release__) +# define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__) +# define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__) + + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800 +# define COMPILER_ID "XL" + /* __IBMCPP__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800 +# define COMPILER_ID "VisualAge" + /* __IBMCPP__ = VRP */ +# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100) +# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10) +# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10) + +#elif defined(__NVCOMPILER) +# define COMPILER_ID "NVHPC" +# define COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__) +# define COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__) +# if defined(__NVCOMPILER_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__) +# endif + +#elif defined(__PGI) +# define COMPILER_ID "PGI" +# define COMPILER_VERSION_MAJOR DEC(__PGIC__) +# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__) +# if defined(__PGIC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__) +# endif + +#elif defined(__clang__) && defined(__cray__) +# define COMPILER_ID "CrayClang" +# define COMPILER_VERSION_MAJOR DEC(__cray_major__) +# define COMPILER_VERSION_MINOR DEC(__cray_minor__) +# define COMPILER_VERSION_PATCH DEC(__cray_patchlevel__) +# define COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(_CRAYC) +# define COMPILER_ID "Cray" +# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR) +# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR) + +#elif defined(__TI_COMPILER_VERSION__) +# define COMPILER_ID "TI" + /* __TI_COMPILER_VERSION__ = VVVRRRPPP */ +# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000) +# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000) +# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000) + +#elif defined(__CLANG_FUJITSU) +# define COMPILER_ID "FujitsuClang" +# define COMPILER_VERSION_MAJOR DEC(__FCC_major__) +# define COMPILER_VERSION_MINOR DEC(__FCC_minor__) +# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +# define COMPILER_VERSION_INTERNAL_STR __clang_version__ + + +#elif defined(__FUJITSU) +# define COMPILER_ID "Fujitsu" +# if defined(__FCC_version__) +# define COMPILER_VERSION __FCC_version__ +# elif defined(__FCC_major__) +# define COMPILER_VERSION_MAJOR DEC(__FCC_major__) +# define COMPILER_VERSION_MINOR DEC(__FCC_minor__) +# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__) +# endif +# if defined(__fcc_version) +# define COMPILER_VERSION_INTERNAL DEC(__fcc_version) +# elif defined(__FCC_VERSION) +# define COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION) +# endif + + +#elif defined(__ghs__) +# define COMPILER_ID "GHS" +/* __GHS_VERSION_NUMBER = VVVVRP */ +# ifdef __GHS_VERSION_NUMBER +# define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100) +# define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10) +# define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10) +# endif + +#elif defined(__TASKING__) +# define COMPILER_ID "Tasking" + # define COMPILER_VERSION_MAJOR DEC(__VERSION__/1000) + # define COMPILER_VERSION_MINOR DEC(__VERSION__ % 100) +# define COMPILER_VERSION_INTERNAL DEC(__VERSION__) + +#elif defined(__ORANGEC__) +# define COMPILER_ID "OrangeC" +# define COMPILER_VERSION_MAJOR DEC(__ORANGEC_MAJOR__) +# define COMPILER_VERSION_MINOR DEC(__ORANGEC_MINOR__) +# define COMPILER_VERSION_PATCH DEC(__ORANGEC_PATCHLEVEL__) + +#elif defined(__RENESAS__) +# define COMPILER_ID "Renesas" +/* __RENESAS_VERSION__ = 0xVVRRPP00 */ +# define COMPILER_VERSION_MAJOR HEX(__RENESAS_VERSION__ >> 24 & 0xFF) +# define COMPILER_VERSION_MINOR HEX(__RENESAS_VERSION__ >> 16 & 0xFF) +# define COMPILER_VERSION_PATCH HEX(__RENESAS_VERSION__ >> 8 & 0xFF) + +#elif defined(__SCO_VERSION__) +# define COMPILER_ID "SCO" + +#elif defined(__ARMCC_VERSION) && !defined(__clang__) +# define COMPILER_ID "ARMCC" +#if __ARMCC_VERSION >= 1000000 + /* __ARMCC_VERSION = VRRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#else + /* __ARMCC_VERSION = VRPPPP */ + # define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000) + # define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10) + # define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000) +#endif + + +#elif defined(__clang__) && defined(__apple_build_version__) +# define COMPILER_ID "AppleClang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif +# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__) + +#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION) +# define COMPILER_ID "ARMClang" + # define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000) + # define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100) + # define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION/100 % 100) +# define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION) + +#elif defined(__clang__) && defined(__ti__) +# define COMPILER_ID "TIClang" + # define COMPILER_VERSION_MAJOR DEC(__ti_major__) + # define COMPILER_VERSION_MINOR DEC(__ti_minor__) + # define COMPILER_VERSION_PATCH DEC(__ti_patchlevel__) +# define COMPILER_VERSION_INTERNAL DEC(__ti_version__) + +#elif defined(__clang__) +# define COMPILER_ID "Clang" +# if defined(_MSC_VER) +# define SIMULATE_ID "MSVC" +# endif +# define COMPILER_VERSION_MAJOR DEC(__clang_major__) +# define COMPILER_VERSION_MINOR DEC(__clang_minor__) +# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__) +# if defined(_MSC_VER) + /* _MSC_VER = VVRR */ +# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100) +# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100) +# endif + +#elif defined(__LCC__) && (defined(__GNUC__) || defined(__GNUG__) || defined(__MCST__)) +# define COMPILER_ID "LCC" +# define COMPILER_VERSION_MAJOR DEC(__LCC__ / 100) +# define COMPILER_VERSION_MINOR DEC(__LCC__ % 100) +# if defined(__LCC_MINOR__) +# define COMPILER_VERSION_PATCH DEC(__LCC_MINOR__) +# endif +# if defined(__GNUC__) && defined(__GNUC_MINOR__) +# define SIMULATE_ID "GNU" +# define SIMULATE_VERSION_MAJOR DEC(__GNUC__) +# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__) +# if defined(__GNUC_PATCHLEVEL__) +# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif +# endif + +#elif defined(__GNUC__) || defined(__GNUG__) +# define COMPILER_ID "GNU" +# if defined(__GNUC__) +# define COMPILER_VERSION_MAJOR DEC(__GNUC__) +# else +# define COMPILER_VERSION_MAJOR DEC(__GNUG__) +# endif +# if defined(__GNUC_MINOR__) +# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__) +# endif +# if defined(__GNUC_PATCHLEVEL__) +# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__) +# endif + +#elif defined(_MSC_VER) +# define COMPILER_ID "MSVC" + /* _MSC_VER = VVRR */ +# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100) +# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100) +# if defined(_MSC_FULL_VER) +# if _MSC_VER >= 1400 + /* _MSC_FULL_VER = VVRRPPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000) +# else + /* _MSC_FULL_VER = VVRRPPPP */ +# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000) +# endif +# endif +# if defined(_MSC_BUILD) +# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD) +# endif + +#elif defined(_ADI_COMPILER) +# define COMPILER_ID "ADSP" +#if defined(__VERSIONNUM__) + /* __VERSIONNUM__ = 0xVVRRPPTT */ +# define COMPILER_VERSION_MAJOR DEC(__VERSIONNUM__ >> 24 & 0xFF) +# define COMPILER_VERSION_MINOR DEC(__VERSIONNUM__ >> 16 & 0xFF) +# define COMPILER_VERSION_PATCH DEC(__VERSIONNUM__ >> 8 & 0xFF) +# define COMPILER_VERSION_TWEAK DEC(__VERSIONNUM__ & 0xFF) +#endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# define COMPILER_ID "IAR" +# if defined(__VER__) && defined(__ICCARM__) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000) +# define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000) +# define COMPILER_VERSION_PATCH DEC((__VER__) % 1000) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__)) +# define COMPILER_VERSION_MAJOR DEC((__VER__) / 100) +# define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100)) +# define COMPILER_VERSION_PATCH DEC(__SUBVERSION__) +# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__) +# endif + +#elif defined(__DCC__) && defined(_DIAB_TOOL) +# define COMPILER_ID "Diab" + # define COMPILER_VERSION_MAJOR DEC(__VERSION_MAJOR_NUMBER__) + # define COMPILER_VERSION_MINOR DEC(__VERSION_MINOR_NUMBER__) + # define COMPILER_VERSION_PATCH DEC(__VERSION_ARCH_FEATURE_NUMBER__) + # define COMPILER_VERSION_TWEAK DEC(__VERSION_BUG_FIX_NUMBER__) + + + +/* These compilers are either not known or too old to define an + identification macro. Try to identify the platform and guess that + it is the native compiler. */ +#elif defined(__hpux) || defined(__hpua) +# define COMPILER_ID "HP" + +#else /* unknown compiler */ +# define COMPILER_ID "" +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]"; +#ifdef SIMULATE_ID +char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]"; +#endif + +#ifdef __QNXNTO__ +char const* qnxnto = "INFO" ":" "qnxnto[]"; +#endif + +#if defined(__CRAYXT_COMPUTE_LINUX_TARGET) +char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]"; +#endif + +#define STRINGIFY_HELPER(X) #X +#define STRINGIFY(X) STRINGIFY_HELPER(X) + +/* Identify known platforms by name. */ +#if defined(__linux) || defined(__linux__) || defined(linux) +# define PLATFORM_ID "Linux" + +#elif defined(__MSYS__) +# define PLATFORM_ID "MSYS" + +#elif defined(__CYGWIN__) +# define PLATFORM_ID "Cygwin" + +#elif defined(__MINGW32__) +# define PLATFORM_ID "MinGW" + +#elif defined(__APPLE__) +# define PLATFORM_ID "Darwin" + +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +# define PLATFORM_ID "Windows" + +#elif defined(__FreeBSD__) || defined(__FreeBSD) +# define PLATFORM_ID "FreeBSD" + +#elif defined(__NetBSD__) || defined(__NetBSD) +# define PLATFORM_ID "NetBSD" + +#elif defined(__OpenBSD__) || defined(__OPENBSD) +# define PLATFORM_ID "OpenBSD" + +#elif defined(__sun) || defined(sun) +# define PLATFORM_ID "SunOS" + +#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__) +# define PLATFORM_ID "AIX" + +#elif defined(__hpux) || defined(__hpux__) +# define PLATFORM_ID "HP-UX" + +#elif defined(__HAIKU__) +# define PLATFORM_ID "Haiku" + +#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS) +# define PLATFORM_ID "BeOS" + +#elif defined(__QNX__) || defined(__QNXNTO__) +# define PLATFORM_ID "QNX" + +#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__) +# define PLATFORM_ID "Tru64" + +#elif defined(__riscos) || defined(__riscos__) +# define PLATFORM_ID "RISCos" + +#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__) +# define PLATFORM_ID "SINIX" + +#elif defined(__UNIX_SV__) +# define PLATFORM_ID "UNIX_SV" + +#elif defined(__bsdos__) +# define PLATFORM_ID "BSDOS" + +#elif defined(_MPRAS) || defined(MPRAS) +# define PLATFORM_ID "MP-RAS" + +#elif defined(__osf) || defined(__osf__) +# define PLATFORM_ID "OSF1" + +#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv) +# define PLATFORM_ID "SCO_SV" + +#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX) +# define PLATFORM_ID "ULTRIX" + +#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX) +# define PLATFORM_ID "Xenix" + +#elif defined(__WATCOMC__) +# if defined(__LINUX__) +# define PLATFORM_ID "Linux" + +# elif defined(__DOS__) +# define PLATFORM_ID "DOS" + +# elif defined(__OS2__) +# define PLATFORM_ID "OS2" + +# elif defined(__WINDOWS__) +# define PLATFORM_ID "Windows3x" + +# elif defined(__VXWORKS__) +# define PLATFORM_ID "VxWorks" + +# else /* unknown platform */ +# define PLATFORM_ID +# endif + +#elif defined(__INTEGRITY) +# if defined(INT_178B) +# define PLATFORM_ID "Integrity178" + +# else /* regular Integrity */ +# define PLATFORM_ID "Integrity" +# endif + +# elif defined(_ADI_COMPILER) +# define PLATFORM_ID "ADSP" + +#else /* unknown platform */ +# define PLATFORM_ID + +#endif + +/* For windows compilers MSVC and Intel we can determine + the architecture of the compiler being used. This is because + the compilers do not have flags that can change the architecture, + but rather depend on which compiler is being used +*/ +#if defined(_WIN32) && defined(_MSC_VER) +# if defined(_M_IA64) +# define ARCHITECTURE_ID "IA64" + +# elif defined(_M_ARM64EC) +# define ARCHITECTURE_ID "ARM64EC" + +# elif defined(_M_X64) || defined(_M_AMD64) +# define ARCHITECTURE_ID "x64" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# elif defined(_M_ARM64) +# define ARCHITECTURE_ID "ARM64" + +# elif defined(_M_ARM) +# if _M_ARM == 4 +# define ARCHITECTURE_ID "ARMV4I" +# elif _M_ARM == 5 +# define ARCHITECTURE_ID "ARMV5I" +# else +# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM) +# endif + +# elif defined(_M_MIPS) +# define ARCHITECTURE_ID "MIPS" + +# elif defined(_M_SH) +# define ARCHITECTURE_ID "SHx" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__WATCOMC__) +# if defined(_M_I86) +# define ARCHITECTURE_ID "I86" + +# elif defined(_M_IX86) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC) +# if defined(__ICCARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__ICCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__ICCRH850__) +# define ARCHITECTURE_ID "RH850" + +# elif defined(__ICCRL78__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__ICCRISCV__) +# define ARCHITECTURE_ID "RISCV" + +# elif defined(__ICCAVR__) +# define ARCHITECTURE_ID "AVR" + +# elif defined(__ICC430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__ICCV850__) +# define ARCHITECTURE_ID "V850" + +# elif defined(__ICC8051__) +# define ARCHITECTURE_ID "8051" + +# elif defined(__ICCSTM8__) +# define ARCHITECTURE_ID "STM8" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__ghs__) +# if defined(__PPC64__) +# define ARCHITECTURE_ID "PPC64" + +# elif defined(__ppc__) +# define ARCHITECTURE_ID "PPC" + +# elif defined(__ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__x86_64__) +# define ARCHITECTURE_ID "x64" + +# elif defined(__i386__) +# define ARCHITECTURE_ID "X86" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__clang__) && defined(__ti__) +# if defined(__ARM_ARCH) +# define ARCHITECTURE_ID "ARM" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__TI_COMPILER_VERSION__) +# if defined(__TI_ARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__MSP430__) +# define ARCHITECTURE_ID "MSP430" + +# elif defined(__TMS320C28XX__) +# define ARCHITECTURE_ID "TMS320C28x" + +# elif defined(__TMS320C6X__) || defined(_TMS320C6X) +# define ARCHITECTURE_ID "TMS320C6x" + +# else /* unknown architecture */ +# define ARCHITECTURE_ID "" +# endif + +# elif defined(__ADSPSHARC__) +# define ARCHITECTURE_ID "SHARC" + +# elif defined(__ADSPBLACKFIN__) +# define ARCHITECTURE_ID "Blackfin" + +#elif defined(__TASKING__) + +# if defined(__CTC__) || defined(__CPTC__) +# define ARCHITECTURE_ID "TriCore" + +# elif defined(__CMCS__) +# define ARCHITECTURE_ID "MCS" + +# elif defined(__CARM__) || defined(__CPARM__) +# define ARCHITECTURE_ID "ARM" + +# elif defined(__CARC__) +# define ARCHITECTURE_ID "ARC" + +# elif defined(__C51__) +# define ARCHITECTURE_ID "8051" + +# elif defined(__CPCP__) +# define ARCHITECTURE_ID "PCP" + +# else +# define ARCHITECTURE_ID "" +# endif + +#elif defined(__RENESAS__) +# if defined(__CCRX__) +# define ARCHITECTURE_ID "RX" + +# elif defined(__CCRL__) +# define ARCHITECTURE_ID "RL78" + +# elif defined(__CCRH__) +# define ARCHITECTURE_ID "RH850" + +# else +# define ARCHITECTURE_ID "" +# endif + +#else +# define ARCHITECTURE_ID +#endif + +/* Convert integer to decimal digit literals. */ +#define DEC(n) \ + ('0' + (((n) / 10000000)%10)), \ + ('0' + (((n) / 1000000)%10)), \ + ('0' + (((n) / 100000)%10)), \ + ('0' + (((n) / 10000)%10)), \ + ('0' + (((n) / 1000)%10)), \ + ('0' + (((n) / 100)%10)), \ + ('0' + (((n) / 10)%10)), \ + ('0' + ((n) % 10)) + +/* Convert integer to hex digit literals. */ +#define HEX(n) \ + ('0' + ((n)>>28 & 0xF)), \ + ('0' + ((n)>>24 & 0xF)), \ + ('0' + ((n)>>20 & 0xF)), \ + ('0' + ((n)>>16 & 0xF)), \ + ('0' + ((n)>>12 & 0xF)), \ + ('0' + ((n)>>8 & 0xF)), \ + ('0' + ((n)>>4 & 0xF)), \ + ('0' + ((n) & 0xF)) + +/* Construct a string literal encoding the version number. */ +#ifdef COMPILER_VERSION +char const* info_version = "INFO" ":" "compiler_version[" COMPILER_VERSION "]"; + +/* Construct a string literal encoding the version number components. */ +#elif defined(COMPILER_VERSION_MAJOR) +char const info_version[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[', + COMPILER_VERSION_MAJOR, +# ifdef COMPILER_VERSION_MINOR + '.', COMPILER_VERSION_MINOR, +# ifdef COMPILER_VERSION_PATCH + '.', COMPILER_VERSION_PATCH, +# ifdef COMPILER_VERSION_TWEAK + '.', COMPILER_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct a string literal encoding the internal version number. */ +#ifdef COMPILER_VERSION_INTERNAL +char const info_version_internal[] = { + 'I', 'N', 'F', 'O', ':', + 'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_', + 'i','n','t','e','r','n','a','l','[', + COMPILER_VERSION_INTERNAL,']','\0'}; +#elif defined(COMPILER_VERSION_INTERNAL_STR) +char const* info_version_internal = "INFO" ":" "compiler_version_internal[" COMPILER_VERSION_INTERNAL_STR "]"; +#endif + +/* Construct a string literal encoding the version number components. */ +#ifdef SIMULATE_VERSION_MAJOR +char const info_simulate_version[] = { + 'I', 'N', 'F', 'O', ':', + 's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[', + SIMULATE_VERSION_MAJOR, +# ifdef SIMULATE_VERSION_MINOR + '.', SIMULATE_VERSION_MINOR, +# ifdef SIMULATE_VERSION_PATCH + '.', SIMULATE_VERSION_PATCH, +# ifdef SIMULATE_VERSION_TWEAK + '.', SIMULATE_VERSION_TWEAK, +# endif +# endif +# endif + ']','\0'}; +#endif + +/* Construct the string literal in pieces to prevent the source from + getting matched. Store it in a pointer rather than an array + because some compilers will just produce instructions to fill the + array rather than assigning a pointer to a static array. */ +char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]"; +char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]"; + + + +#define CXX_STD_98 199711L +#define CXX_STD_11 201103L +#define CXX_STD_14 201402L +#define CXX_STD_17 201703L +#define CXX_STD_20 202002L +#define CXX_STD_23 202302L + +#if defined(__INTEL_COMPILER) && defined(_MSVC_LANG) +# if _MSVC_LANG > CXX_STD_17 +# define CXX_STD _MSVC_LANG +# elif _MSVC_LANG == CXX_STD_17 && defined(__cpp_aggregate_paren_init) +# define CXX_STD CXX_STD_20 +# elif _MSVC_LANG > CXX_STD_14 && __cplusplus > CXX_STD_17 +# define CXX_STD CXX_STD_20 +# elif _MSVC_LANG > CXX_STD_14 +# define CXX_STD CXX_STD_17 +# elif defined(__INTEL_CXX11_MODE__) && defined(__cpp_aggregate_nsdmi) +# define CXX_STD CXX_STD_14 +# elif defined(__INTEL_CXX11_MODE__) +# define CXX_STD CXX_STD_11 +# else +# define CXX_STD CXX_STD_98 +# endif +#elif defined(_MSC_VER) && defined(_MSVC_LANG) +# if _MSVC_LANG > __cplusplus +# define CXX_STD _MSVC_LANG +# else +# define CXX_STD __cplusplus +# endif +#elif defined(__NVCOMPILER) +# if __cplusplus == CXX_STD_17 && defined(__cpp_aggregate_paren_init) +# define CXX_STD CXX_STD_20 +# else +# define CXX_STD __cplusplus +# endif +#elif defined(__INTEL_COMPILER) || defined(__PGI) +# if __cplusplus == CXX_STD_11 && defined(__cpp_namespace_attributes) +# define CXX_STD CXX_STD_17 +# elif __cplusplus == CXX_STD_11 && defined(__cpp_aggregate_nsdmi) +# define CXX_STD CXX_STD_14 +# else +# define CXX_STD __cplusplus +# endif +#elif (defined(__IBMCPP__) || defined(__ibmxl__)) && defined(__linux__) +# if __cplusplus == CXX_STD_11 && defined(__cpp_aggregate_nsdmi) +# define CXX_STD CXX_STD_14 +# else +# define CXX_STD __cplusplus +# endif +#elif __cplusplus == 1 && defined(__GXX_EXPERIMENTAL_CXX0X__) +# define CXX_STD CXX_STD_11 +#else +# define CXX_STD __cplusplus +#endif + +const char* info_language_standard_default = "INFO" ":" "standard_default[" +#if CXX_STD > CXX_STD_23 + "26" +#elif CXX_STD > CXX_STD_20 + "23" +#elif CXX_STD > CXX_STD_17 + "20" +#elif CXX_STD > CXX_STD_14 + "17" +#elif CXX_STD > CXX_STD_11 + "14" +#elif CXX_STD >= CXX_STD_11 + "11" +#else + "98" +#endif +"]"; + +const char* info_language_extensions_default = "INFO" ":" "extensions_default[" +#if (defined(__clang__) || defined(__GNUC__) || defined(__xlC__) || \ + defined(__TI_COMPILER_VERSION__) || defined(__RENESAS__)) && \ + !defined(__STRICT_ANSI__) + "ON" +#else + "OFF" +#endif +"]"; + +/*--------------------------------------------------------------------------*/ + +int main(int argc, char* argv[]) +{ + int require = 0; + require += info_compiler[argc]; + require += info_platform[argc]; + require += info_arch[argc]; +#ifdef COMPILER_VERSION_MAJOR + require += info_version[argc]; +#endif +#if defined(COMPILER_VERSION_INTERNAL) || defined(COMPILER_VERSION_INTERNAL_STR) + require += info_version_internal[argc]; +#endif +#ifdef SIMULATE_ID + require += info_simulate[argc]; +#endif +#ifdef SIMULATE_VERSION_MAJOR + require += info_simulate_version[argc]; +#endif +#if defined(__CRAYXT_COMPUTE_LINUX_TARGET) + require += info_cray[argc]; +#endif + require += info_language_standard_default[argc]; + require += info_language_extensions_default[argc]; + (void)argv; + return require; +} diff --git a/build/CMakeFiles/4.1.1/CompilerIdCXX/a.out b/build/CMakeFiles/4.1.1/CompilerIdCXX/a.out new file mode 100755 index 0000000000000000000000000000000000000000..b9b2fb453a9e4bec85a4815557e6889af6d718c5 GIT binary patch literal 33736 zcmeI5Uu@G=6vt1yPFMM}`X>tFLV0izA)|#UL{02;6Bg$-VbQt;uRqq`Mnl)mt`#;> zIt)0)=s-=T(S&F+G=T>Vnv96q3xpRi>;Z@-I${(^MB{@oi?IJ(Vh!(Q0V^ylPZf!$*!}=ES7K+|n z=(@_1JVU%tskVl2TVYu54;A{z*yZdN<&v~0m9n>`jKM>3z7bw1`A(Q6V#~R5;-O4# z&J(3N`%=khyxTa7^S!_imwZVR7du}Ha&v(5CE|TE1s3O%fNyMH_luF09M&l6` zjdvw@VR1g+O-_AblSJ&|n)36@dl=Dq>g(F-@**K~33${}G?uYbRKF`8m5Nv?izh~u zDxL4X?1GT9KWi3`Gxw~ivH8h{`o`=X$rRPaw#W%7hDGYh)+BZ5GIft_5Vb0~3NSEgU1)6z+xnm(7MYF$yGS3&er*k=rPiJDo?_9t9HJF0{2!H?xfB*=900@8p z2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?x zfB*=900@8p2!H?xfB*=900>;0fb(~pJ{r(}hwh_cGk#>oul0EC{fK}72!H?xfB*=9 z00@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*=900@8p z2!H?xfB*=900@8p2!H?xfB*=900@8p2!H?xfB*>mKLq@orq!rq%x}}3-&S0DiDyO6 zL)>gWl=T$uBbTBb5|YNz9vaTL_%owF_v_CQ{v`L+J*;VAamQ;~?4co9?UBCNG%wrR znr@dy+G(rXR&;N+kv2V5Mk8MCHLrR$D|?&WnNHbivu#D}BzdFpu7siuJytYM15Vwt zR^)Zo`P{sEg8$!voL|bWbm`9K+P+h4j`>1Pe2KB;{IWh^Y`?MB8+(DVlg6$v_Mq6j zQF?02em|)a6)Rn_KDIN>o!v2W07`HyT5%OsC!XV`G!>=Vn}RF#Q%&b^t}S1#{5W@< zE8fRGDO= diff --git a/build/CMakeFiles/CMakeConfigureLog.yaml b/build/CMakeFiles/CMakeConfigureLog.yaml new file mode 100644 index 000000000..ca9c0fa91 --- /dev/null +++ b/build/CMakeFiles/CMakeConfigureLog.yaml @@ -0,0 +1,2787 @@ + +--- +events: + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineSystem.cmake:12 (find_program)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_UNAME" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "uname" + candidate_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/uname" + - "/opt/homebrew/opt/openjdk@17/bin/uname" + - "/Users/graf/Library/Python/3.9/bin/uname" + - "/Users/graf/Library/Python/3.x/bin/uname" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/uname" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/uname" + - "/opt/homebrew/bin/uname" + - "/opt/homebrew/sbin/uname" + - "/usr/local/bin/uname" + - "/System/Cryptexes/App/usr/bin/uname" + found: "/usr/bin/uname" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineSystem.cmake:212 (message)" + - "CMakeLists.txt:2 (project)" + message: | + The system is: Darwin - 24.3.0 - arm64 + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeUnixFindMake.cmake:5 (find_program)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_MAKE_PROGRAM" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "gmake" + - "make" + - "smake" + candidate_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/gmake" + - "/opt/homebrew/opt/openjdk@17/bin/gmake" + - "/Users/graf/Library/Python/3.9/bin/gmake" + - "/Users/graf/Library/Python/3.x/bin/gmake" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/gmake" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/gmake" + - "/opt/homebrew/bin/gmake" + - "/opt/homebrew/sbin/gmake" + - "/usr/local/bin/gmake" + - "/System/Cryptexes/App/usr/bin/gmake" + - "/usr/bin/gmake" + - "/bin/gmake" + - "/usr/sbin/gmake" + - "/sbin/gmake" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/gmake" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/gmake" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/gmake" + - "/Library/Apple/usr/bin/gmake" + - "/Users/graf/.dotnet/tools/gmake" + - "/Users/graf/.sdkman/candidates/java/current/bin/gmake" + - "/Users/graf/.cargo/bin/gmake" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/gmake" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/gmake" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/gmake" + - "/Users/graf/.npm-global/bin/gmake" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/gmake" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/gmake" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/gmake" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/make" + - "/opt/homebrew/opt/openjdk@17/bin/make" + - "/Users/graf/Library/Python/3.9/bin/make" + - "/Users/graf/Library/Python/3.x/bin/make" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/make" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/make" + - "/opt/homebrew/bin/make" + - "/opt/homebrew/sbin/make" + - "/usr/local/bin/make" + - "/System/Cryptexes/App/usr/bin/make" + found: "/usr/bin/make" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompiler.cmake:73 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:69 (_cmake_find_compiler)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_CXX_COMPILER" + description: "CXX compiler" + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "c++" + - "g++" + - "cl" + - "bcc" + - "icpx" + - "icx" + - "clang++" + candidate_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/c++" + - "/opt/homebrew/opt/openjdk@17/bin/c++" + - "/Users/graf/Library/Python/3.9/bin/c++" + - "/Users/graf/Library/Python/3.x/bin/c++" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/c++" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/c++" + - "/opt/homebrew/bin/c++" + - "/opt/homebrew/sbin/c++" + - "/usr/local/bin/c++" + - "/System/Cryptexes/App/usr/bin/c++" + found: "/usr/bin/c++" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:462 (find_file)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:500 (CMAKE_DETERMINE_COMPILER_ID_WRITE)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:8 (CMAKE_DETERMINE_COMPILER_ID_BUILD)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:64 (__determine_compiler_id_test)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:125 (CMAKE_DETERMINE_COMPILER_ID)" + - "CMakeLists.txt:2 (project)" + mode: "file" + variable: "src_in" + description: "Path to a file." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "CMakeCXXCompilerId.cpp.in" + candidate_directories: + - "/opt/homebrew/share/cmake/Modules/" + found: "/opt/homebrew/share/cmake/Modules/CMakeCXXCompilerId.cpp.in" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:17 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:64 (__determine_compiler_id_test)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:125 (CMAKE_DETERMINE_COMPILER_ID)" + - "CMakeLists.txt:2 (project)" + message: | + Compiling the CXX compiler identification source file "CMakeCXXCompilerId.cpp" succeeded. + Compiler: /usr/bin/c++ + Build flags: + Id flags: + + The output was: + 0 + + + Compilation of the CXX compiler identification source "CMakeCXXCompilerId.cpp" produced "a.out" + + The CXX compiler identification is AppleClang, found in: + /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/4.1.1/CompilerIdCXX/a.out + + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:290 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:125 (CMAKE_DETERMINE_COMPILER_ID)" + - "CMakeLists.txt:2 (project)" + message: | + Detecting CXX compiler apple sysroot: "/usr/bin/c++" "-E" "apple-sdk.cpp" + # 1 "apple-sdk.cpp" + # 1 "" 1 + # 1 "" 3 + # 513 "" 3 + # 1 "" 1 + # 1 "" 2 + # 1 "apple-sdk.cpp" 2 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 1 3 4 + # 89 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4 + # 90 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 2 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/TargetConditionals.h" 1 3 4 + # 91 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 2 3 4 + # 207 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 1 3 4 + # 196 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4 + # 197 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 1 3 4 + # 33 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4 + # 34 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 2 3 4 + # 198 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternalLegacy.h" 1 3 4 + # 34 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternalLegacy.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 1 3 4 + # 35 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternalLegacy.h" 2 3 4 + # 199 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4 + # 208 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 2 3 4 + # 2 "apple-sdk.cpp" 2 + + + Found apple sysroot: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_AR" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "ar" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + found: "/usr/bin/ar" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_RANLIB" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "ranlib" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + found: "/usr/bin/ranlib" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_STRIP" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "strip" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + found: "/usr/bin/strip" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_LINKER" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "ld" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + found: "/usr/bin/ld" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_NM" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "nm" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + found: "/usr/bin/nm" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_OBJDUMP" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "objdump" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + found: "/usr/bin/objdump" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_OBJCOPY" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "objcopy" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/usr/bin/objcopy" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/objcopy" + - "/opt/homebrew/opt/openjdk@17/bin/objcopy" + - "/Users/graf/Library/Python/3.9/bin/objcopy" + - "/Users/graf/Library/Python/3.x/bin/objcopy" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/objcopy" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/objcopy" + - "/opt/homebrew/bin/objcopy" + - "/opt/homebrew/sbin/objcopy" + - "/usr/local/bin/objcopy" + - "/System/Cryptexes/App/usr/bin/objcopy" + - "/bin/objcopy" + - "/usr/sbin/objcopy" + - "/sbin/objcopy" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/objcopy" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/objcopy" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/objcopy" + - "/Library/Apple/usr/bin/objcopy" + - "/Users/graf/.dotnet/tools/objcopy" + - "/Users/graf/.sdkman/candidates/java/current/bin/objcopy" + - "/Users/graf/.cargo/bin/objcopy" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/objcopy" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/objcopy" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/objcopy" + - "/Users/graf/.npm-global/bin/objcopy" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/objcopy" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/objcopy" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/objcopy" + found: false + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_READELF" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "readelf" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/usr/bin/readelf" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/readelf" + - "/opt/homebrew/opt/openjdk@17/bin/readelf" + - "/Users/graf/Library/Python/3.9/bin/readelf" + - "/Users/graf/Library/Python/3.x/bin/readelf" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/readelf" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/readelf" + - "/opt/homebrew/bin/readelf" + - "/opt/homebrew/sbin/readelf" + - "/usr/local/bin/readelf" + - "/System/Cryptexes/App/usr/bin/readelf" + - "/bin/readelf" + - "/usr/sbin/readelf" + - "/sbin/readelf" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/readelf" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/readelf" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/readelf" + - "/Library/Apple/usr/bin/readelf" + - "/Users/graf/.dotnet/tools/readelf" + - "/Users/graf/.sdkman/candidates/java/current/bin/readelf" + - "/Users/graf/.cargo/bin/readelf" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/readelf" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/readelf" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/readelf" + - "/Users/graf/.npm-global/bin/readelf" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/readelf" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/readelf" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/readelf" + found: false + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_DLLTOOL" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "dlltool" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/usr/bin/dlltool" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/dlltool" + - "/opt/homebrew/opt/openjdk@17/bin/dlltool" + - "/Users/graf/Library/Python/3.9/bin/dlltool" + - "/Users/graf/Library/Python/3.x/bin/dlltool" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/dlltool" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/dlltool" + - "/opt/homebrew/bin/dlltool" + - "/opt/homebrew/sbin/dlltool" + - "/usr/local/bin/dlltool" + - "/System/Cryptexes/App/usr/bin/dlltool" + - "/bin/dlltool" + - "/usr/sbin/dlltool" + - "/sbin/dlltool" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/dlltool" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/dlltool" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/dlltool" + - "/Library/Apple/usr/bin/dlltool" + - "/Users/graf/.dotnet/tools/dlltool" + - "/Users/graf/.sdkman/candidates/java/current/bin/dlltool" + - "/Users/graf/.cargo/bin/dlltool" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/dlltool" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/dlltool" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/dlltool" + - "/Users/graf/.npm-global/bin/dlltool" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/dlltool" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/dlltool" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/dlltool" + found: false + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_ADDR2LINE" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "addr2line" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/usr/bin/addr2line" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/addr2line" + - "/opt/homebrew/opt/openjdk@17/bin/addr2line" + - "/Users/graf/Library/Python/3.9/bin/addr2line" + - "/Users/graf/Library/Python/3.x/bin/addr2line" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/addr2line" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/addr2line" + - "/opt/homebrew/bin/addr2line" + - "/opt/homebrew/sbin/addr2line" + - "/usr/local/bin/addr2line" + - "/System/Cryptexes/App/usr/bin/addr2line" + - "/bin/addr2line" + - "/usr/sbin/addr2line" + - "/sbin/addr2line" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/addr2line" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/addr2line" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/addr2line" + - "/Library/Apple/usr/bin/addr2line" + - "/Users/graf/.dotnet/tools/addr2line" + - "/Users/graf/.sdkman/candidates/java/current/bin/addr2line" + - "/Users/graf/.cargo/bin/addr2line" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/addr2line" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/addr2line" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/addr2line" + - "/Users/graf/.npm-global/bin/addr2line" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/addr2line" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/addr2line" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/addr2line" + found: false + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake:238 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake:206 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_TAPI" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: false + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: false + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "tapi" + candidate_directories: + - "/usr/bin/" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/usr/bin/tapi" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/tapi" + - "/opt/homebrew/opt/openjdk@17/bin/tapi" + - "/Users/graf/Library/Python/3.9/bin/tapi" + - "/Users/graf/Library/Python/3.x/bin/tapi" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/tapi" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/tapi" + - "/opt/homebrew/bin/tapi" + - "/opt/homebrew/sbin/tapi" + - "/usr/local/bin/tapi" + - "/System/Cryptexes/App/usr/bin/tapi" + - "/bin/tapi" + - "/usr/sbin/tapi" + - "/sbin/tapi" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/tapi" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/tapi" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/tapi" + - "/Library/Apple/usr/bin/tapi" + - "/Users/graf/.dotnet/tools/tapi" + - "/Users/graf/.sdkman/candidates/java/current/bin/tapi" + - "/Users/graf/.cargo/bin/tapi" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/tapi" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/tapi" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/tapi" + - "/Users/graf/.npm-global/bin/tapi" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/tapi" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/tapi" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/tapi" + found: false + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/Platform/Darwin.cmake:76 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeSystemSpecificInformation.cmake:32 (include)" + - "CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_INSTALL_NAME_TOOL" + description: "Path to a program." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "install_name_tool" + candidate_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + searched_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/install_name_tool" + - "/opt/homebrew/opt/openjdk@17/bin/install_name_tool" + - "/Users/graf/Library/Python/3.9/bin/install_name_tool" + - "/Users/graf/Library/Python/3.x/bin/install_name_tool" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/install_name_tool" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/install_name_tool" + - "/opt/homebrew/bin/install_name_tool" + - "/opt/homebrew/sbin/install_name_tool" + - "/usr/local/bin/install_name_tool" + - "/System/Cryptexes/App/usr/bin/install_name_tool" + found: "/usr/bin/install_name_tool" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + CMAKE_INSTALL_PREFIX: "/usr/local" + - + kind: "try_compile-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:83 (try_compile)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCXXCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "CMakeLists.txt:2 (project)" + checks: + - "Detecting CXX compiler ABI info" + directories: + source: "/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7" + binary: "/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7" + cmakeVariables: + CMAKE_CXX_FLAGS: "" + CMAKE_CXX_FLAGS_DEBUG: "-g" + CMAKE_CXX_SCAN_FOR_MODULES: "OFF" + CMAKE_EXE_LINKER_FLAGS: "" + CMAKE_OSX_ARCHITECTURES: "" + CMAKE_OSX_DEPLOYMENT_TARGET: "" + CMAKE_OSX_SYSROOT: "" + buildResult: + variable: "CMAKE_CXX_ABI_COMPILED" + cached: true + stdout: | + Change Dir: '/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7' + + Run Build Command(s): /opt/homebrew/bin/cmake -E env VERBOSE=1 /usr/bin/make -f Makefile cmTC_97b76/fast + /Library/Developer/CommandLineTools/usr/bin/make -f CMakeFiles/cmTC_97b76.dir/build.make CMakeFiles/cmTC_97b76.dir/build + Building CXX object CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o + /usr/bin/c++ -arch arm64 -v -Wl,-v -MD -MT CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -MF CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o.d -o CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -c /opt/homebrew/share/cmake/Modules/CMakeCXXCompilerABI.cpp + Apple clang version 17.0.0 (clang-1700.0.13.5) + Target: arm64-apple-darwin24.3.0 + Thread model: posix + InstalledDir: /Library/Developer/CommandLineTools/usr/bin + clang++: warning: -Wl,-v: 'linker' input unused [-Wunused-command-line-argument] + ignoring nonexistent directory "/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1" + "/Library/Developer/CommandLineTools/usr/bin/clang" -cc1 -triple arm64-apple-macosx15.0.0 -Wundef-prefix=TARGET_OS_ -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name CMakeCXXCompilerABI.cpp -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -fno-strict-return -ffp-contract=on -fno-rounding-math -funwind-tables=1 -fobjc-msgsend-selector-stubs -target-sdk-version=15.5 -fvisibility-inlines-hidden-static-local-var -fdefine-target-os-macros -fno-assume-unique-vtables -fno-modulemap-allow-subdirectory-search -target-cpu apple-m1 -target-feature +zcm -target-feature +zcz -target-feature +v8.5a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -debugger-tuning=lldb -fdebug-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7 -target-linker-version 1167.5 -v -fcoverage-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7 -resource-dir /Library/Developer/CommandLineTools/usr/lib/clang/17 -dependency-file CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o.d -skip-unused-modulemap-deps -MT CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -sys-header-deps -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1 -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/usr/lib/clang/17/include -internal-externc-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include -internal-externc-isystem /Library/Developer/CommandLineTools/usr/include -Wno-reorder-init-list -Wno-implicit-int-float-conversion -Wno-c99-designator -Wno-final-dtor-non-final-class -Wno-extra-semi-stmt -Wno-misleading-indentation -Wno-quoted-include-in-framework-header -Wno-implicit-fallthrough -Wno-enum-enum-conversion -Wno-enum-float-conversion -Wno-elaborated-enum-base -Wno-reserved-identifier -Wno-gnu-folding-constant -fdeprecated-macro -ferror-limit 19 -stack-protector 1 -fstack-check -mdarwin-stkchk-strong-link -fblocks -fencode-extended-block-signature -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fno-cxx-modules -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fmax-type-align=16 -fcommon -clang-vendor-feature=+disableNonDependentMemberExprInCurrentInstantiation -fno-odr-hash-protocols -clang-vendor-feature=+enableAggressiveVLAFolding -clang-vendor-feature=+revert09abecef7bbf -clang-vendor-feature=+thisNoAlignAttr -clang-vendor-feature=+thisNoNullAttr -clang-vendor-feature=+disableAtImportPrivateFrameworkInImplementationError -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -x c++ /opt/homebrew/share/cmake/Modules/CMakeCXXCompilerABI.cpp + clang -cc1 version 17.0.0 (clang-1700.0.13.5) default target arm64-apple-darwin24.3.0 + ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include" + ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/SubFrameworks" + ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Library/Frameworks" + #include "..." search starts here: + #include <...> search starts here: + /usr/local/include + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1 + /Library/Developer/CommandLineTools/usr/lib/clang/17/include + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include + /Library/Developer/CommandLineTools/usr/include + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory) + End of search list. + Linking CXX executable cmTC_97b76 + /opt/homebrew/bin/cmake -E cmake_link_script CMakeFiles/cmTC_97b76.dir/link.txt --verbose=1 + Apple clang version 17.0.0 (clang-1700.0.13.5) + Target: arm64-apple-darwin24.3.0 + Thread model: posix + InstalledDir: /Library/Developer/CommandLineTools/usr/bin + "/Library/Developer/CommandLineTools/usr/bin/ld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch arm64 -platform_version macos 15.0.0 15.5 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -mllvm -enable-linkonceodr-outlining -o cmTC_97b76 -L/usr/local/lib -search_paths_first -headerpad_max_install_names -v CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -lc++ -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a + @(#)PROGRAM:ld PROJECT:ld-1167.5 + BUILD 01:45:05 Apr 30 2025 + configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em + will use ld-classic for: armv6 armv7 armv7s i386 armv6m armv7k armv7m armv7em + LTO support using: LLVM version 17.0.0 (static support for 29, runtime is 29) + TAPI support using: Apple TAPI version 17.0.0 (tapi-1700.0.3.5) + Library search paths: + /usr/local/lib + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift + Framework search paths: + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks + /usr/bin/c++ -arch arm64 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -v -Wl,-v CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -o cmTC_97b76 + + exitCode: 0 + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:122 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCXXCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "CMakeLists.txt:2 (project)" + message: | + Effective list of requested architectures (possibly empty) : "" + Effective list of architectures found in the ABI info binary: "arm64" + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:217 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCXXCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "CMakeLists.txt:2 (project)" + message: | + Parsed CXX implicit include dir info: rv=done + found start of include info + found start of implicit include info + add: [/usr/local/include] + add: [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1] + add: [/Library/Developer/CommandLineTools/usr/lib/clang/17/include] + add: [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] + add: [/Library/Developer/CommandLineTools/usr/include] + end of search list found + collapse include dir [/usr/local/include] ==> [/usr/local/include] + collapse include dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1] + collapse include dir [/Library/Developer/CommandLineTools/usr/lib/clang/17/include] ==> [/Library/Developer/CommandLineTools/usr/lib/clang/17/include] + collapse include dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] + collapse include dir [/Library/Developer/CommandLineTools/usr/include] ==> [/Library/Developer/CommandLineTools/usr/include] + implicit include dirs: [/usr/local/include;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1;/Library/Developer/CommandLineTools/usr/lib/clang/17/include;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include;/Library/Developer/CommandLineTools/usr/include] + + + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:253 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCXXCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "CMakeLists.txt:2 (project)" + message: | + Parsed CXX implicit link information: + link line regex: [^( *|.*[/\\])(ld[0-9]*(|\\.[a-rt-z][a-z]*|\\.s[a-np-z][a-z]*|\\.so[a-z]+)|CMAKE_LINK_STARTFILE-NOTFOUND|([^/\\]+-)?ld|collect2)[^/\\]*( |$)] + linker tool regex: [^[ ]*(->|")?[ ]*(([^"]*[/\\])?(ld[0-9]*(|\\.[a-rt-z][a-z]*|\\.s[a-np-z][a-z]*|\\.so[a-z]+)))("|,| |$)] + ignore line: [Change Dir: '/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7'] + ignore line: [] + ignore line: [Run Build Command(s): /opt/homebrew/bin/cmake -E env VERBOSE=1 /usr/bin/make -f Makefile cmTC_97b76/fast] + ignore line: [/Library/Developer/CommandLineTools/usr/bin/make -f CMakeFiles/cmTC_97b76.dir/build.make CMakeFiles/cmTC_97b76.dir/build] + ignore line: [Building CXX object CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o] + ignore line: [/usr/bin/c++ -arch arm64 -v -Wl -v -MD -MT CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -MF CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o.d -o CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -c /opt/homebrew/share/cmake/Modules/CMakeCXXCompilerABI.cpp] + ignore line: [Apple clang version 17.0.0 (clang-1700.0.13.5)] + ignore line: [Target: arm64-apple-darwin24.3.0] + ignore line: [Thread model: posix] + ignore line: [InstalledDir: /Library/Developer/CommandLineTools/usr/bin] + ignore line: [clang++: warning: -Wl -v: 'linker' input unused [-Wunused-command-line-argument]] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/usr/bin/../include/c++/v1"] + ignore line: [ "/Library/Developer/CommandLineTools/usr/bin/clang" -cc1 -triple arm64-apple-macosx15.0.0 -Wundef-prefix=TARGET_OS_ -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name CMakeCXXCompilerABI.cpp -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -fno-strict-return -ffp-contract=on -fno-rounding-math -funwind-tables=1 -fobjc-msgsend-selector-stubs -target-sdk-version=15.5 -fvisibility-inlines-hidden-static-local-var -fdefine-target-os-macros -fno-assume-unique-vtables -fno-modulemap-allow-subdirectory-search -target-cpu apple-m1 -target-feature +zcm -target-feature +zcz -target-feature +v8.5a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -debugger-tuning=lldb -fdebug-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7 -target-linker-version 1167.5 -v -fcoverage-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-8d9Xe7 -resource-dir /Library/Developer/CommandLineTools/usr/lib/clang/17 -dependency-file CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o.d -skip-unused-modulemap-deps -MT CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -sys-header-deps -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1 -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/usr/lib/clang/17/include -internal-externc-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include -internal-externc-isystem /Library/Developer/CommandLineTools/usr/include -Wno-reorder-init-list -Wno-implicit-int-float-conversion -Wno-c99-designator -Wno-final-dtor-non-final-class -Wno-extra-semi-stmt -Wno-misleading-indentation -Wno-quoted-include-in-framework-header -Wno-implicit-fallthrough -Wno-enum-enum-conversion -Wno-enum-float-conversion -Wno-elaborated-enum-base -Wno-reserved-identifier -Wno-gnu-folding-constant -fdeprecated-macro -ferror-limit 19 -stack-protector 1 -fstack-check -mdarwin-stkchk-strong-link -fblocks -fencode-extended-block-signature -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fno-cxx-modules -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -fmax-type-align=16 -fcommon -clang-vendor-feature=+disableNonDependentMemberExprInCurrentInstantiation -fno-odr-hash-protocols -clang-vendor-feature=+enableAggressiveVLAFolding -clang-vendor-feature=+revert09abecef7bbf -clang-vendor-feature=+thisNoAlignAttr -clang-vendor-feature=+thisNoNullAttr -clang-vendor-feature=+disableAtImportPrivateFrameworkInImplementationError -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -x c++ /opt/homebrew/share/cmake/Modules/CMakeCXXCompilerABI.cpp] + ignore line: [clang -cc1 version 17.0.0 (clang-1700.0.13.5) default target arm64-apple-darwin24.3.0] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include"] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/SubFrameworks"] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Library/Frameworks"] + ignore line: [#include "..." search starts here:] + ignore line: [#include <...> search starts here:] + ignore line: [ /usr/local/include] + ignore line: [ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1] + ignore line: [ /Library/Developer/CommandLineTools/usr/lib/clang/17/include] + ignore line: [ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] + ignore line: [ /Library/Developer/CommandLineTools/usr/include] + ignore line: [ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)] + ignore line: [End of search list.] + ignore line: [Linking CXX executable cmTC_97b76] + ignore line: [/opt/homebrew/bin/cmake -E cmake_link_script CMakeFiles/cmTC_97b76.dir/link.txt --verbose=1] + ignore line: [Apple clang version 17.0.0 (clang-1700.0.13.5)] + ignore line: [Target: arm64-apple-darwin24.3.0] + ignore line: [Thread model: posix] + ignore line: [InstalledDir: /Library/Developer/CommandLineTools/usr/bin] + link line: [ "/Library/Developer/CommandLineTools/usr/bin/ld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch arm64 -platform_version macos 15.0.0 15.5 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -mllvm -enable-linkonceodr-outlining -o cmTC_97b76 -L/usr/local/lib -search_paths_first -headerpad_max_install_names -v CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o -lc++ -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] + arg [/Library/Developer/CommandLineTools/usr/bin/ld] ==> ignore + arg [-demangle] ==> ignore + arg [-lto_library] ==> ignore, skip following value + arg [/Library/Developer/CommandLineTools/usr/lib/libLTO.dylib] ==> skip value of -lto_library + arg [-dynamic] ==> ignore + arg [-arch] ==> ignore + arg [arm64] ==> ignore + arg [-platform_version] ==> ignore + arg [macos] ==> ignore + arg [15.0.0] ==> ignore + arg [15.5] ==> ignore + arg [-syslibroot] ==> ignore + arg [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk] ==> ignore + arg [-mllvm] ==> ignore + arg [-enable-linkonceodr-outlining] ==> ignore + arg [-o] ==> ignore + arg [cmTC_97b76] ==> ignore + arg [-L/usr/local/lib] ==> dir [/usr/local/lib] + arg [-search_paths_first] ==> ignore + arg [-headerpad_max_install_names] ==> ignore + arg [-v] ==> ignore + arg [CMakeFiles/cmTC_97b76.dir/CMakeCXXCompilerABI.cpp.o] ==> ignore + arg [-lc++] ==> lib [c++] + arg [-lSystem] ==> lib [System] + arg [/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] ==> lib [/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] + linker tool for 'CXX': /Library/Developer/CommandLineTools/usr/bin/ld + Library search paths: [;/usr/local/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] + Framework search paths: [;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] + remove lib [System] + remove lib [/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] + collapse library dir [/usr/local/lib] ==> [/usr/local/lib] + collapse library dir [/usr/local/lib] ==> [/usr/local/lib] + collapse library dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib] + collapse library dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] + collapse framework dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] + implicit libs: [c++] + implicit objs: [] + implicit dirs: [/usr/local/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] + implicit fwks: [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] + + + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/Internal/CMakeDetermineLinkerId.cmake:36 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:299 (cmake_determine_linker_id)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCXXCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "CMakeLists.txt:2 (project)" + message: | + Running the CXX compiler's linker: "/Library/Developer/CommandLineTools/usr/bin/ld" "-v" + @(#)PROGRAM:ld PROJECT:ld-1167.5 + BUILD 01:45:05 Apr 30 2025 + configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em + will use ld-classic for: armv6 armv7 armv7s i386 armv6m armv7k armv7m armv7em + LTO support using: LLVM version 17.0.0 (static support for 29, runtime is 29) + TAPI support using: Apple TAPI version 17.0.0 (tapi-1700.0.3.5) + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompiler.cmake:54 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCCompiler.cmake:64 (_cmake_find_compiler)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + mode: "program" + variable: "CMAKE_C_COMPILER" + description: "C compiler" + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "cc" + - "gcc" + - "cl" + - "bcc" + - "xlc" + - "icx" + - "clang" + candidate_directories: + - "/usr/bin/" + found: "/usr/bin/cc" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + CMAKE_INSTALL_PREFIX: "/usr/local" + CMAKE_SYSTEM_PREFIX_PATH: + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr" + - "/opt/homebrew" + - "/usr/local" + - "/usr" + - "/" + - "/opt/homebrew" + - "/usr/local" + - "/usr/X11R6" + - "/usr/pkg" + - "/opt" + - "/sw" + - "/opt/local" + CMAKE_SYSTEM_APPBUNDLE_PATH: + - "/Users/graf/Applications" + - "/Applications" + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:462 (find_file)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:500 (CMAKE_DETERMINE_COMPILER_ID_WRITE)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:8 (CMAKE_DETERMINE_COMPILER_ID_BUILD)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:64 (__determine_compiler_id_test)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCCompiler.cmake:122 (CMAKE_DETERMINE_COMPILER_ID)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + mode: "file" + variable: "src_in" + description: "Path to a file." + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "CMakeCCompilerId.c.in" + candidate_directories: + - "/opt/homebrew/share/cmake/Modules/" + - "/Users/graf/Documents/GitHub/qubic-core/core/cmake/" + found: "/opt/homebrew/share/cmake/Modules/CMakeCCompilerId.c.in" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + CMAKE_INSTALL_PREFIX: "/usr/local" + CMAKE_SYSTEM_PREFIX_PATH: + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr" + - "/opt/homebrew" + - "/usr/local" + - "/usr" + - "/" + - "/opt/homebrew" + - "/usr/local" + - "/usr/X11R6" + - "/usr/pkg" + - "/opt" + - "/sw" + - "/opt/local" + CMAKE_SYSTEM_INCLUDE_PATH: + - "/usr/include/X11" + CMAKE_SYSTEM_FRAMEWORK_PATH: + - "~/Library/Frameworks" + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Library/Frameworks" + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Network/Library/Frameworks" + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks" + - "/Library/Developer/CommandLineTools/Library/Frameworks" + - "/Library/Developer/CommandLineTools/Library/Frameworks" + - "/Library/Frameworks" + - "/Network/Library/Frameworks" + - "/System/Library/Frameworks" + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:17 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:64 (__determine_compiler_id_test)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCCompiler.cmake:122 (CMAKE_DETERMINE_COMPILER_ID)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + message: | + Compiling the C compiler identification source file "CMakeCCompilerId.c" succeeded. + Compiler: /usr/bin/cc + Build flags: + Id flags: + + The output was: + 0 + + + Compilation of the C compiler identification source "CMakeCCompilerId.c" produced "a.out" + + The C compiler identification is AppleClang, found in: + /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/4.1.1/CompilerIdC/a.out + + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:290 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCCompiler.cmake:122 (CMAKE_DETERMINE_COMPILER_ID)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + message: | + Detecting C compiler apple sysroot: "/usr/bin/cc" "-E" "apple-sdk.c" + # 1 "apple-sdk.c" + # 1 "" 1 + # 1 "" 3 + # 465 "" 3 + # 1 "" 1 + # 1 "" 2 + # 1 "apple-sdk.c" 2 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 1 3 4 + # 89 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4 + # 90 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 2 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/TargetConditionals.h" 1 3 4 + # 91 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 2 3 4 + # 207 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 1 3 4 + # 196 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4 + # 197 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 1 3 4 + # 33 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityVersions.h" 1 3 4 + # 34 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 2 3 4 + # 198 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternalLegacy.h" 1 3 4 + # 34 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternalLegacy.h" 3 4 + # 1 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternal.h" 1 3 4 + # 35 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityInternalLegacy.h" 2 3 4 + # 199 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/Availability.h" 2 3 4 + # 208 "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/AvailabilityMacros.h" 2 3 4 + # 2 "apple-sdk.c" 2 + + + Found apple sysroot: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk + - + kind: "try_compile-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:83 (try_compile)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + checks: + - "Detecting C compiler ABI info" + directories: + source: "/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla" + binary: "/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla" + cmakeVariables: + CMAKE_C_FLAGS: "" + CMAKE_C_FLAGS_DEBUG: "" + CMAKE_EXE_LINKER_FLAGS: "" + CMAKE_MODULE_PATH: "/Users/graf/Documents/GitHub/qubic-core/core/cmake" + CMAKE_OSX_ARCHITECTURES: "" + CMAKE_OSX_DEPLOYMENT_TARGET: "" + CMAKE_OSX_SYSROOT: "" + buildResult: + variable: "CMAKE_C_ABI_COMPILED" + cached: true + stdout: | + Change Dir: '/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla' + + Run Build Command(s): /opt/homebrew/bin/cmake -E env VERBOSE=1 /usr/bin/make -f Makefile cmTC_75d2f/fast + /Library/Developer/CommandLineTools/usr/bin/make -f CMakeFiles/cmTC_75d2f.dir/build.make CMakeFiles/cmTC_75d2f.dir/build + Building C object CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o + /usr/bin/cc -arch arm64 -v -Wl,-v -MD -MT CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -MF CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o.d -o CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -c /opt/homebrew/share/cmake/Modules/CMakeCCompilerABI.c + Apple clang version 17.0.0 (clang-1700.0.13.5) + Target: arm64-apple-darwin24.3.0 + Thread model: posix + InstalledDir: /Library/Developer/CommandLineTools/usr/bin + clang: warning: -Wl,-v: 'linker' input unused [-Wunused-command-line-argument] + "/Library/Developer/CommandLineTools/usr/bin/clang" -cc1 -triple arm64-apple-macosx15.0.0 -Wundef-prefix=TARGET_OS_ -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name CMakeCCompilerABI.c -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -fno-strict-return -ffp-contract=on -fno-rounding-math -funwind-tables=1 -fobjc-msgsend-selector-stubs -target-sdk-version=15.5 -fvisibility-inlines-hidden-static-local-var -fdefine-target-os-macros -fno-assume-unique-vtables -fno-modulemap-allow-subdirectory-search -target-cpu apple-m1 -target-feature +zcm -target-feature +zcz -target-feature +v8.5a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -debugger-tuning=lldb -fdebug-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla -target-linker-version 1167.5 -v -fcoverage-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla -resource-dir /Library/Developer/CommandLineTools/usr/lib/clang/17 -dependency-file CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o.d -skip-unused-modulemap-deps -MT CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -sys-header-deps -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/usr/lib/clang/17/include -internal-externc-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include -internal-externc-isystem /Library/Developer/CommandLineTools/usr/include -Wno-reorder-init-list -Wno-implicit-int-float-conversion -Wno-c99-designator -Wno-final-dtor-non-final-class -Wno-extra-semi-stmt -Wno-misleading-indentation -Wno-quoted-include-in-framework-header -Wno-implicit-fallthrough -Wno-enum-enum-conversion -Wno-enum-float-conversion -Wno-elaborated-enum-base -Wno-reserved-identifier -Wno-gnu-folding-constant -ferror-limit 19 -stack-protector 1 -fstack-check -mdarwin-stkchk-strong-link -fblocks -fencode-extended-block-signature -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fmax-type-align=16 -fcommon -clang-vendor-feature=+disableNonDependentMemberExprInCurrentInstantiation -fno-odr-hash-protocols -clang-vendor-feature=+enableAggressiveVLAFolding -clang-vendor-feature=+revert09abecef7bbf -clang-vendor-feature=+thisNoAlignAttr -clang-vendor-feature=+thisNoNullAttr -clang-vendor-feature=+disableAtImportPrivateFrameworkInImplementationError -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -x c /opt/homebrew/share/cmake/Modules/CMakeCCompilerABI.c + clang -cc1 version 17.0.0 (clang-1700.0.13.5) default target arm64-apple-darwin24.3.0 + ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include" + ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/SubFrameworks" + ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Library/Frameworks" + #include "..." search starts here: + #include <...> search starts here: + /usr/local/include + /Library/Developer/CommandLineTools/usr/lib/clang/17/include + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include + /Library/Developer/CommandLineTools/usr/include + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory) + End of search list. + Linking C executable cmTC_75d2f + /opt/homebrew/bin/cmake -E cmake_link_script CMakeFiles/cmTC_75d2f.dir/link.txt --verbose=1 + Apple clang version 17.0.0 (clang-1700.0.13.5) + Target: arm64-apple-darwin24.3.0 + Thread model: posix + InstalledDir: /Library/Developer/CommandLineTools/usr/bin + "/Library/Developer/CommandLineTools/usr/bin/ld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch arm64 -platform_version macos 15.0.0 15.5 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -mllvm -enable-linkonceodr-outlining -o cmTC_75d2f -L/usr/local/lib -search_paths_first -headerpad_max_install_names -v CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a + @(#)PROGRAM:ld PROJECT:ld-1167.5 + BUILD 01:45:05 Apr 30 2025 + configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em + will use ld-classic for: armv6 armv7 armv7s i386 armv6m armv7k armv7m armv7em + LTO support using: LLVM version 17.0.0 (static support for 29, runtime is 29) + TAPI support using: Apple TAPI version 17.0.0 (tapi-1700.0.3.5) + Library search paths: + /usr/local/lib + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift + Framework search paths: + /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks + /usr/bin/cc -arch arm64 -Wl,-search_paths_first -Wl,-headerpad_max_install_names -v -Wl,-v CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -o cmTC_75d2f + + exitCode: 0 + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:122 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + message: | + Effective list of requested architectures (possibly empty) : "" + Effective list of architectures found in the ABI info binary: "arm64" + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:217 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + message: | + Parsed C implicit include dir info: rv=done + found start of include info + found start of implicit include info + add: [/usr/local/include] + add: [/Library/Developer/CommandLineTools/usr/lib/clang/17/include] + add: [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] + add: [/Library/Developer/CommandLineTools/usr/include] + end of search list found + collapse include dir [/usr/local/include] ==> [/usr/local/include] + collapse include dir [/Library/Developer/CommandLineTools/usr/lib/clang/17/include] ==> [/Library/Developer/CommandLineTools/usr/lib/clang/17/include] + collapse include dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] + collapse include dir [/Library/Developer/CommandLineTools/usr/include] ==> [/Library/Developer/CommandLineTools/usr/include] + implicit include dirs: [/usr/local/include;/Library/Developer/CommandLineTools/usr/lib/clang/17/include;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include;/Library/Developer/CommandLineTools/usr/include] + + + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:253 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + message: | + Parsed C implicit link information: + link line regex: [^( *|.*[/\\])(ld[0-9]*(|\\.[a-rt-z][a-z]*|\\.s[a-np-z][a-z]*|\\.so[a-z]+)|CMAKE_LINK_STARTFILE-NOTFOUND|([^/\\]+-)?ld|collect2)[^/\\]*( |$)] + linker tool regex: [^[ ]*(->|")?[ ]*(([^"]*[/\\])?(ld[0-9]*(|\\.[a-rt-z][a-z]*|\\.s[a-np-z][a-z]*|\\.so[a-z]+)))("|,| |$)] + ignore line: [Change Dir: '/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla'] + ignore line: [] + ignore line: [Run Build Command(s): /opt/homebrew/bin/cmake -E env VERBOSE=1 /usr/bin/make -f Makefile cmTC_75d2f/fast] + ignore line: [/Library/Developer/CommandLineTools/usr/bin/make -f CMakeFiles/cmTC_75d2f.dir/build.make CMakeFiles/cmTC_75d2f.dir/build] + ignore line: [Building C object CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o] + ignore line: [/usr/bin/cc -arch arm64 -v -Wl -v -MD -MT CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -MF CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o.d -o CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -c /opt/homebrew/share/cmake/Modules/CMakeCCompilerABI.c] + ignore line: [Apple clang version 17.0.0 (clang-1700.0.13.5)] + ignore line: [Target: arm64-apple-darwin24.3.0] + ignore line: [Thread model: posix] + ignore line: [InstalledDir: /Library/Developer/CommandLineTools/usr/bin] + ignore line: [clang: warning: -Wl -v: 'linker' input unused [-Wunused-command-line-argument]] + ignore line: [ "/Library/Developer/CommandLineTools/usr/bin/clang" -cc1 -triple arm64-apple-macosx15.0.0 -Wundef-prefix=TARGET_OS_ -Wdeprecated-objc-isa-usage -Werror=deprecated-objc-isa-usage -Werror=implicit-function-declaration -emit-obj -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name CMakeCCompilerABI.c -mrelocation-model pic -pic-level 2 -mframe-pointer=non-leaf -fno-strict-return -ffp-contract=on -fno-rounding-math -funwind-tables=1 -fobjc-msgsend-selector-stubs -target-sdk-version=15.5 -fvisibility-inlines-hidden-static-local-var -fdefine-target-os-macros -fno-assume-unique-vtables -fno-modulemap-allow-subdirectory-search -target-cpu apple-m1 -target-feature +zcm -target-feature +zcz -target-feature +v8.5a -target-feature +aes -target-feature +altnzcv -target-feature +ccdp -target-feature +complxnum -target-feature +crc -target-feature +dotprod -target-feature +fp-armv8 -target-feature +fp16fml -target-feature +fptoint -target-feature +fullfp16 -target-feature +jsconv -target-feature +lse -target-feature +neon -target-feature +pauth -target-feature +perfmon -target-feature +predres -target-feature +ras -target-feature +rcpc -target-feature +rdm -target-feature +sb -target-feature +sha2 -target-feature +sha3 -target-feature +specrestrict -target-feature +ssbs -target-abi darwinpcs -debugger-tuning=lldb -fdebug-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla -target-linker-version 1167.5 -v -fcoverage-compilation-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-qTCSla -resource-dir /Library/Developer/CommandLineTools/usr/lib/clang/17 -dependency-file CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o.d -skip-unused-modulemap-deps -MT CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -sys-header-deps -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -I/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include -internal-isystem /Library/Developer/CommandLineTools/usr/lib/clang/17/include -internal-externc-isystem /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include -internal-externc-isystem /Library/Developer/CommandLineTools/usr/include -Wno-reorder-init-list -Wno-implicit-int-float-conversion -Wno-c99-designator -Wno-final-dtor-non-final-class -Wno-extra-semi-stmt -Wno-misleading-indentation -Wno-quoted-include-in-framework-header -Wno-implicit-fallthrough -Wno-enum-enum-conversion -Wno-enum-float-conversion -Wno-elaborated-enum-base -Wno-reserved-identifier -Wno-gnu-folding-constant -ferror-limit 19 -stack-protector 1 -fstack-check -mdarwin-stkchk-strong-link -fblocks -fencode-extended-block-signature -fregister-global-dtors-with-atexit -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fmax-type-align=16 -fcommon -clang-vendor-feature=+disableNonDependentMemberExprInCurrentInstantiation -fno-odr-hash-protocols -clang-vendor-feature=+enableAggressiveVLAFolding -clang-vendor-feature=+revert09abecef7bbf -clang-vendor-feature=+thisNoAlignAttr -clang-vendor-feature=+thisNoNullAttr -clang-vendor-feature=+disableAtImportPrivateFrameworkInImplementationError -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -x c /opt/homebrew/share/cmake/Modules/CMakeCCompilerABI.c] + ignore line: [clang -cc1 version 17.0.0 (clang-1700.0.13.5) default target arm64-apple-darwin24.3.0] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/local/include"] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/SubFrameworks"] + ignore line: [ignoring nonexistent directory "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/Library/Frameworks"] + ignore line: [#include "..." search starts here:] + ignore line: [#include <...> search starts here:] + ignore line: [ /usr/local/include] + ignore line: [ /Library/Developer/CommandLineTools/usr/lib/clang/17/include] + ignore line: [ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include] + ignore line: [ /Library/Developer/CommandLineTools/usr/include] + ignore line: [ /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)] + ignore line: [End of search list.] + ignore line: [Linking C executable cmTC_75d2f] + ignore line: [/opt/homebrew/bin/cmake -E cmake_link_script CMakeFiles/cmTC_75d2f.dir/link.txt --verbose=1] + ignore line: [Apple clang version 17.0.0 (clang-1700.0.13.5)] + ignore line: [Target: arm64-apple-darwin24.3.0] + ignore line: [Thread model: posix] + ignore line: [InstalledDir: /Library/Developer/CommandLineTools/usr/bin] + link line: [ "/Library/Developer/CommandLineTools/usr/bin/ld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch arm64 -platform_version macos 15.0.0 15.5 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -mllvm -enable-linkonceodr-outlining -o cmTC_75d2f -L/usr/local/lib -search_paths_first -headerpad_max_install_names -v CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] + arg [/Library/Developer/CommandLineTools/usr/bin/ld] ==> ignore + arg [-demangle] ==> ignore + arg [-lto_library] ==> ignore, skip following value + arg [/Library/Developer/CommandLineTools/usr/lib/libLTO.dylib] ==> skip value of -lto_library + arg [-dynamic] ==> ignore + arg [-arch] ==> ignore + arg [arm64] ==> ignore + arg [-platform_version] ==> ignore + arg [macos] ==> ignore + arg [15.0.0] ==> ignore + arg [15.5] ==> ignore + arg [-syslibroot] ==> ignore + arg [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk] ==> ignore + arg [-mllvm] ==> ignore + arg [-enable-linkonceodr-outlining] ==> ignore + arg [-o] ==> ignore + arg [cmTC_75d2f] ==> ignore + arg [-L/usr/local/lib] ==> dir [/usr/local/lib] + arg [-search_paths_first] ==> ignore + arg [-headerpad_max_install_names] ==> ignore + arg [-v] ==> ignore + arg [CMakeFiles/cmTC_75d2f.dir/CMakeCCompilerABI.c.o] ==> ignore + arg [-lSystem] ==> lib [System] + arg [/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] ==> lib [/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] + linker tool for 'C': /Library/Developer/CommandLineTools/usr/bin/ld + Library search paths: [;/usr/local/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] + Framework search paths: [;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] + remove lib [System] + remove lib [/Library/Developer/CommandLineTools/usr/lib/clang/17/lib/darwin/libclang_rt.osx.a] + collapse library dir [/usr/local/lib] ==> [/usr/local/lib] + collapse library dir [/usr/local/lib] ==> [/usr/local/lib] + collapse library dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib] + collapse library dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] + collapse framework dir [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] ==> [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] + implicit libs: [] + implicit objs: [] + implicit dirs: [/usr/local/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib;/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib/swift] + implicit fwks: [/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks] + + + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/Internal/CMakeDetermineLinkerId.cmake:36 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake:299 (cmake_determine_linker_id)" + - "/opt/homebrew/share/cmake/Modules/CMakeTestCCompiler.cmake:26 (CMAKE_DETERMINE_COMPILER_ABI)" + - "lib/platform_common/CMakeLists.txt:4 (project)" + message: | + Running the C compiler's linker: "/Library/Developer/CommandLineTools/usr/bin/ld" "-v" + @(#)PROGRAM:ld PROJECT:ld-1167.5 + BUILD 01:45:05 Apr 30 2025 + configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em + will use ld-classic for: armv6 armv7 armv7s i386 armv6m armv7k armv7m armv7em + LTO support using: LLVM version 17.0.0 (static support for 29, runtime is 29) + TAPI support using: Apple TAPI version 17.0.0 (tapi-1700.0.3.5) + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/FindGit.cmake:86 (find_program)" + - "/opt/homebrew/share/cmake/Modules/FetchContent.cmake:1874 (find_package)" + - "/opt/homebrew/share/cmake/Modules/FetchContent.cmake:1609 (__FetchContent_populateSubbuild)" + - "/opt/homebrew/share/cmake/Modules/FetchContent.cmake:2145:EVAL:2 (__FetchContent_doPopulation)" + - "/opt/homebrew/share/cmake/Modules/FetchContent.cmake:2145 (cmake_language)" + - "/opt/homebrew/share/cmake/Modules/FetchContent.cmake:2384 (__FetchContent_Populate)" + - "test/CMakeLists.txt:16 (FetchContent_MakeAvailable)" + mode: "program" + variable: "GIT_EXECUTABLE" + description: "Git command line client" + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "git" + candidate_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/" + - "/opt/homebrew/opt/openjdk@17/bin/" + - "/Users/graf/Library/Python/3.9/bin/" + - "/Users/graf/Library/Python/3.x/bin/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/usr/local/bin/" + - "/System/Cryptexes/App/usr/bin/" + - "/usr/bin/" + - "/bin/" + - "/usr/sbin/" + - "/sbin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin/" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin/" + - "/Library/Apple/usr/bin/" + - "/Users/graf/.dotnet/tools/" + - "/Users/graf/.sdkman/candidates/java/current/bin/" + - "/Users/graf/.cargo/bin/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/Unknown command/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ bin To see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ npm help/" + - "/Users/graf/.npm-global/bin/" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/ \"bin\"\n\nTo see a list of supported npm commands, run/" + - "/Users/graf/Documents/GitHub/qubic-core/core/build/\n npm help/" + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/bin/" + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/sbin/" + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/" + - "/opt/homebrew/bin/" + - "/opt/homebrew/sbin/" + - "/opt/homebrew/" + - "/usr/local/bin/" + - "/usr/local/sbin/" + - "/usr/local/" + - "/usr/bin/" + - "/usr/sbin/" + - "/usr/" + - "/bin/" + - "/sbin/" + - "/usr/X11R6/bin/" + - "/usr/X11R6/sbin/" + - "/usr/X11R6/" + - "/usr/pkg/bin/" + - "/usr/pkg/sbin/" + - "/usr/pkg/" + - "/opt/bin/" + - "/opt/sbin/" + - "/opt/" + - "/sw/bin/" + - "/sw/sbin/" + - "/sw/" + - "/opt/local/bin/" + - "/opt/local/sbin/" + - "/opt/local/" + - "/Users/graf/Applications/" + - "/Applications/" + searched_directories: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin/git" + - "/opt/homebrew/opt/openjdk@17/bin/git" + - "/Users/graf/Library/Python/3.9/bin/git" + - "/Users/graf/Library/Python/3.x/bin/git" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand/git" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli/git" + - "/opt/homebrew/bin/git" + - "/opt/homebrew/sbin/git" + - "/usr/local/bin/git" + - "/System/Cryptexes/App/usr/bin/git" + found: "/usr/bin/git" + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + CMAKE_INSTALL_PREFIX: "/usr/local" + CMAKE_SYSTEM_PREFIX_PATH: + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr" + - "/opt/homebrew" + - "/usr/local" + - "/usr" + - "/" + - "/opt/homebrew" + - "/usr/local" + - "/usr/X11R6" + - "/usr/pkg" + - "/opt" + - "/sw" + - "/opt/local" + CMAKE_SYSTEM_APPBUNDLE_PATH: + - "/Users/graf/Applications" + - "/Applications" + - + kind: "try_compile-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/Internal/CheckSourceCompiles.cmake:104 (try_compile)" + - "/opt/homebrew/share/cmake/Modules/CheckCSourceCompiles.cmake:103 (cmake_check_source_compiles)" + - "/opt/homebrew/share/cmake/Modules/FindThreads.cmake:97 (check_c_source_compiles)" + - "/opt/homebrew/share/cmake/Modules/FindThreads.cmake:163 (_threads_check_libc)" + - "build/_deps/googletest-src/googletest/cmake/internal_utils.cmake:66 (find_package)" + - "build/_deps/googletest-src/googletest/CMakeLists.txt:83 (config_compiler_and_linker)" + checks: + - "Performing Test CMAKE_HAVE_LIBC_PTHREAD" + directories: + source: "/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-r0kzSB" + binary: "/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-r0kzSB" + cmakeVariables: + CMAKE_C_FLAGS: "" + CMAKE_C_FLAGS_DEBUG: "" + CMAKE_EXE_LINKER_FLAGS: "" + CMAKE_MODULE_PATH: "/Users/graf/Documents/GitHub/qubic-core/core/cmake" + CMAKE_OSX_ARCHITECTURES: "" + CMAKE_OSX_DEPLOYMENT_TARGET: "" + CMAKE_OSX_SYSROOT: "" + buildResult: + variable: "CMAKE_HAVE_LIBC_PTHREAD" + cached: true + stdout: | + Change Dir: '/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-r0kzSB' + + Run Build Command(s): /opt/homebrew/bin/cmake -E env VERBOSE=1 /usr/bin/make -f Makefile cmTC_e7cbe/fast + /Library/Developer/CommandLineTools/usr/bin/make -f CMakeFiles/cmTC_e7cbe.dir/build.make CMakeFiles/cmTC_e7cbe.dir/build + Building C object CMakeFiles/cmTC_e7cbe.dir/src.c.o + /usr/bin/cc -DCMAKE_HAVE_LIBC_PTHREAD -arch arm64 -MD -MT CMakeFiles/cmTC_e7cbe.dir/src.c.o -MF CMakeFiles/cmTC_e7cbe.dir/src.c.o.d -o CMakeFiles/cmTC_e7cbe.dir/src.c.o -c /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/CMakeScratch/TryCompile-r0kzSB/src.c + Linking C executable cmTC_e7cbe + /opt/homebrew/bin/cmake -E cmake_link_script CMakeFiles/cmTC_e7cbe.dir/link.txt --verbose=1 + /usr/bin/cc -arch arm64 -Wl,-search_paths_first -Wl,-headerpad_max_install_names CMakeFiles/cmTC_e7cbe.dir/src.c.o -o cmTC_e7cbe + + exitCode: 0 + - + kind: "find-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompiler.cmake:54 (find_program)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:61 (_cmake_find_compiler)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + mode: "program" + variable: "CMAKE_ASM_MASM_COMPILER" + description: "ASM_MASM compiler" + settings: + SearchFramework: "FIRST" + SearchAppBundle: "FIRST" + CMAKE_FIND_USE_CMAKE_PATH: true + CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH: true + CMAKE_FIND_USE_CMAKE_SYSTEM_PATH: true + CMAKE_FIND_USE_INSTALL_PREFIX: true + names: + - "ml" + candidate_directories: + - "/usr/bin/" + searched_directories: + - "/usr/bin/ml" + found: false + search_context: + ENV{PATH}: + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/opt/homebrew/bin" + - "/opt/homebrew/bin" + - "/opt/homebrew/sbin" + - "/usr/local/bin" + - "/System/Cryptexes/App/usr/bin" + - "/usr/bin" + - "/bin" + - "/usr/sbin" + - "/sbin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin" + - "/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin" + - "/Library/Apple/usr/bin" + - "~/.dotnet/tools" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/debugCommand" + - "/Users/graf/Library/Application Support/Code/User/globalStorage/github.copilot-chat/copilotCli" + - "/Users/graf/.nvm/versions/node/v20.19.5/bin" + - "/opt/homebrew/opt/openjdk@17/bin" + - "/Users/graf/.sdkman/candidates/java/current/bin" + - "/Users/graf/Library/Python/3.9/bin" + - "/Users/graf/Library/Python/3.x/bin" + - "/Users/graf/.cargo/bin" + - "Unknown command" + - " bin To see a list of supported npm commands, run" + - " npm help" + - "/Users/graf/.npm-global/bin" + - "/Users/graf/.vscode/extensions/ms-python.debugpy-2025.18.0-darwin-arm64/bundled/scripts/noConfigScripts" + - "Unknown command" + - " \"bin\"\n\nTo see a list of supported npm commands, run" + - "\n npm help" + - "/Users/graf/.npm-global/bin" + CMAKE_INSTALL_PREFIX: "/usr/local" + CMAKE_SYSTEM_PREFIX_PATH: + - "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr" + - "/opt/homebrew" + - "/usr/local" + - "/usr" + - "/" + - "/opt/homebrew" + - "/usr/local" + - "/usr/X11R6" + - "/usr/pkg" + - "/opt" + - "/sw" + - "/opt/local" + CMAKE_SYSTEM_APPBUNDLE_PATH: + - "/Users/graf/Applications" + - "/Applications" + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is GNU using "--version" did not match "(GNU assembler)|(GCC)|(Free Software Foundation)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is AppleClang using "--version" did not match "(Apple (clang|LLVM) version)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is Clang using "--version" did not match "(clang version)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is ARMClang using "--version" did not match "armclang": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is OrangeC using "--version" did not match "occ \\(OrangeC\\) Version": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is HP using "-V" did not match "HP C": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is Intel using "--version" did not match "(ICC)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is IntelLLVM using "--version" did not match "(Intel[^ + ]+oneAPI)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is SunPro using "-V" did not match "Sun C": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is XL using "-qversion" did not match "XL C": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is MSVC using "-?" did not match "Microsoft": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is TI using "-h" did not match "Texas Instruments": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is TIClang using "--version" did not match "(TI (.*) Clang Compiler)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is IAR using "" did not match "IAR Assembler": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is Diab using "-V" did not match "Wind River Systems": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is ARMCC using "" did not match "(ARM Compiler)|(ARM Assembler)|(Arm Compiler)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is NASM using "-v" did not match "(NASM version)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is YASM using "--version" did not match "(yasm)": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is ADSP using "-version" did not match "Analog Devices": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is QCC using "-V" did not match "gcc_nto": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is Tasking using "--version" did not match "TASKING": + - + kind: "message-v1" + backtrace: + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake:1303 (message)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake:170 (CMAKE_DETERMINE_COMPILER_ID_VENDOR)" + - "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake:17 (include)" + - "src/CMakeLists.txt:2 (project)" + message: | + Checking whether the ASM_MASM compiler is Renesas using "-v" did not match "(RX Family C/C\\+\\+ Compiler)|(RL78 Family Compiler)|(RH850 Family Compiler)": +... diff --git a/build/CMakeFiles/CMakeDirectoryInformation.cmake b/build/CMakeFiles/CMakeDirectoryInformation.cmake new file mode 100644 index 000000000..e2771e437 --- /dev/null +++ b/build/CMakeFiles/CMakeDirectoryInformation.cmake @@ -0,0 +1,16 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Relative path conversion top directories. +set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/Users/graf/Documents/GitHub/qubic-core/core") +set(CMAKE_RELATIVE_PATH_TOP_BINARY "/Users/graf/Documents/GitHub/qubic-core/core/build") + +# Force unix paths in dependencies. +set(CMAKE_FORCE_UNIX_PATHS 1) + + +# The C and CXX include file regular expressions for this directory. +set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$") +set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$") +set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN}) +set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN}) diff --git a/build/CMakeFiles/InstallScripts.json b/build/CMakeFiles/InstallScripts.json new file mode 100644 index 000000000..3fcc1b5e9 --- /dev/null +++ b/build/CMakeFiles/InstallScripts.json @@ -0,0 +1,15 @@ +{ + "InstallScripts" : + [ + "/Users/graf/Documents/GitHub/qubic-core/core/build/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/test/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/cmake_install.cmake", + "/Users/graf/Documents/GitHub/qubic-core/core/build/src/cmake_install.cmake" + ], + "Parallel" : false +} diff --git a/build/CMakeFiles/Makefile.cmake b/build/CMakeFiles/Makefile.cmake new file mode 100644 index 000000000..7fcb21d01 --- /dev/null +++ b/build/CMakeFiles/Makefile.cmake @@ -0,0 +1,217 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# The generator used is: +set(CMAKE_DEPENDS_GENERATOR "Unix Makefiles") + +# The top level Makefile was generated from the following files: +set(CMAKE_MAKEFILE_DEPENDS + "CMakeCache.txt" + "/Users/graf/Documents/GitHub/qubic-core/core/CMakeLists.txt" + "CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake" + "CMakeFiles/4.1.1/CMakeCCompiler.cmake" + "CMakeFiles/4.1.1/CMakeCXXCompiler.cmake" + "CMakeFiles/4.1.1/CMakeSystem.cmake" + "_deps/googletest-src/CMakeLists.txt" + "_deps/googletest-src/googlemock/CMakeLists.txt" + "_deps/googletest-src/googlemock/cmake/gmock.pc.in" + "_deps/googletest-src/googlemock/cmake/gmock_main.pc.in" + "_deps/googletest-src/googletest/CMakeLists.txt" + "_deps/googletest-src/googletest/cmake/Config.cmake.in" + "_deps/googletest-src/googletest/cmake/gtest.pc.in" + "_deps/googletest-src/googletest/cmake/gtest_main.pc.in" + "_deps/googletest-src/googletest/cmake/internal_utils.cmake" + "/Users/graf/Documents/GitHub/qubic-core/core/cmake/CompilerSetup.cmake" + "/Users/graf/Documents/GitHub/qubic-core/core/lib/platform_common/CMakeLists.txt" + "/Users/graf/Documents/GitHub/qubic-core/core/lib/platform_efi/CMakeLists.txt" + "/Users/graf/Documents/GitHub/qubic-core/core/lib/platform_os/CMakeLists.txt" + "/Users/graf/Documents/GitHub/qubic-core/core/src/CMakeLists.txt" + "/Users/graf/Documents/GitHub/qubic-core/core/test/CMakeLists.txt" + "/opt/homebrew/share/cmake/Modules/BasicConfigVersion-AnyNewerVersion.cmake.in" + "/opt/homebrew/share/cmake/Modules/CMakeASMCompiler.cmake.in" + "/opt/homebrew/share/cmake/Modules/CMakeASMInformation.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeASM_MASMInformation.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeCCompiler.cmake.in" + "/opt/homebrew/share/cmake/Modules/CMakeCCompilerABI.c" + "/opt/homebrew/share/cmake/Modules/CMakeCInformation.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeCXXCompiler.cmake.in" + "/opt/homebrew/share/cmake/Modules/CMakeCXXCompilerABI.cpp" + "/opt/homebrew/share/cmake/Modules/CMakeCXXInformation.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeCommonLanguageInclude.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeCompilerIdDetection.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDependentOption.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineASMCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineASM_MASMCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineCCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineCXXCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerABI.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerId.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineCompilerSupport.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeDetermineSystem.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeFindBinUtils.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeGenericSystem.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeInitializeConfigs.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeLanguageInformation.cmake" + "/opt/homebrew/share/cmake/Modules/CMakePackageConfigHelpers.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeParseImplicitIncludeInfo.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeParseImplicitLinkInfo.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeParseLibraryArchitecture.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeSystem.cmake.in" + "/opt/homebrew/share/cmake/Modules/CMakeSystemSpecificInformation.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeSystemSpecificInitialize.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeTestASMCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeTestASM_MASMCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeTestCCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeTestCXXCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeTestCompilerCommon.cmake" + "/opt/homebrew/share/cmake/Modules/CMakeUnixFindMake.cmake" + "/opt/homebrew/share/cmake/Modules/CheckCSourceCompiles.cmake" + "/opt/homebrew/share/cmake/Modules/CheckIncludeFile.cmake" + "/opt/homebrew/share/cmake/Modules/CheckLibraryExists.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/ADSP-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/ARMCC-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/ARMClang-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/AppleClang-C.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/AppleClang-CXX.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/AppleClang-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Borland-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Bruce-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/CMakeCommonCompilerMacros.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Clang-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Clang-DetermineCompilerInternal.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Clang.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Compaq-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Compaq-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Cray-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/CrayClang-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Diab-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Embarcadero-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Fujitsu-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/FujitsuClang-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/GHS-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/GNU-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/GNU-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/GNU.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/HP-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/HP-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/IAR-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/IBMCPP-C-DetermineVersionInternal.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/IBMClang-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/IBMClang-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Intel-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/IntelLLVM-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/LCC-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/LCC-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/MSVC-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/NVHPC-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/NVIDIA-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/OpenWatcom-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/OrangeC-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/PGI-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/PathScale-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Renesas-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/SCO-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/SDCC-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/SunPro-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/SunPro-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/TI-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/TIClang-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Tasking-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/TinyCC-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/VisualAge-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/VisualAge-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/Watcom-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/XL-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/XL-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/XLClang-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/XLClang-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/zOS-C-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/Compiler/zOS-CXX-DetermineCompiler.cmake" + "/opt/homebrew/share/cmake/Modules/ExternalProject/shared_internal_commands.cmake" + "/opt/homebrew/share/cmake/Modules/FetchContent.cmake" + "/opt/homebrew/share/cmake/Modules/FetchContent/CMakeLists.cmake.in" + "/opt/homebrew/share/cmake/Modules/FindGit.cmake" + "/opt/homebrew/share/cmake/Modules/FindPackageHandleStandardArgs.cmake" + "/opt/homebrew/share/cmake/Modules/FindPackageMessage.cmake" + "/opt/homebrew/share/cmake/Modules/FindThreads.cmake" + "/opt/homebrew/share/cmake/Modules/GNUInstallDirs.cmake" + "/opt/homebrew/share/cmake/Modules/GoogleTest.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeASMLinkerInformation.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeASM_MASMLinkerInformation.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeCLinkerInformation.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeCXXLinkerInformation.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeCommonLinkerInformation.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeDetermineLinkerId.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeInspectASMLinker.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeInspectASM_MASMLinker.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeInspectCLinker.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CMakeInspectCXXLinker.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/CheckSourceCompiles.cmake" + "/opt/homebrew/share/cmake/Modules/Internal/FeatureTesting.cmake" + "/opt/homebrew/share/cmake/Modules/Linker/AppleClang-C.cmake" + "/opt/homebrew/share/cmake/Modules/Linker/AppleClang-CXX.cmake" + "/opt/homebrew/share/cmake/Modules/Linker/AppleClang.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Apple-AppleClang-C.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Apple-AppleClang-CXX.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Apple-Clang-C.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Apple-Clang-CXX.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Apple-Clang.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Darwin-Determine-CXX.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Darwin-Initialize.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Darwin.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Linker/Apple-AppleClang-C.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Linker/Apple-AppleClang-CXX.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/Linker/Apple-AppleClang.cmake" + "/opt/homebrew/share/cmake/Modules/Platform/UnixPaths.cmake" + "/opt/homebrew/share/cmake/Modules/WriteBasicConfigVersionFile.cmake" + ) + +# The corresponding makefile is: +set(CMAKE_MAKEFILE_OUTPUTS + "Makefile" + "CMakeFiles/cmake.check_cache" + ) + +# Byproducts of CMake generate step: +set(CMAKE_MAKEFILE_PRODUCTS + "CMakeFiles/4.1.1/CMakeSystem.cmake" + "CMakeFiles/4.1.1/CMakeCXXCompiler.cmake" + "CMakeFiles/4.1.1/CMakeCXXCompiler.cmake" + "CMakeFiles/4.1.1/CMakeCXXCompiler.cmake" + "CMakeFiles/CMakeDirectoryInformation.cmake" + "CMakeFiles/4.1.1/CMakeCCompiler.cmake" + "CMakeFiles/4.1.1/CMakeCCompiler.cmake" + "CMakeFiles/4.1.1/CMakeCCompiler.cmake" + "lib/platform_common/CMakeFiles/CMakeDirectoryInformation.cmake" + "lib/platform_efi/CMakeFiles/CMakeDirectoryInformation.cmake" + "lib/platform_os/CMakeFiles/CMakeDirectoryInformation.cmake" + "_deps/googletest-subbuild/CMakeLists.txt" + "test/CMakeFiles/CMakeDirectoryInformation.cmake" + "_deps/googletest-build/CMakeFiles/CMakeDirectoryInformation.cmake" + "_deps/googletest-build/googletest/generated/gmock.pc" + "_deps/googletest-build/googletest/generated/gmock_main.pc" + "_deps/googletest-build/googlemock/CMakeFiles/CMakeDirectoryInformation.cmake" + "_deps/googletest-build/googletest/generated/GTestConfigVersion.cmake" + "_deps/googletest-build/googletest/generated/GTestConfig.cmake" + "_deps/googletest-build/googletest/generated/gtest.pc" + "_deps/googletest-build/googletest/generated/gtest_main.pc" + "_deps/googletest-build/googletest/CMakeFiles/CMakeDirectoryInformation.cmake" + "CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake" + "CMakeFiles/4.1.1/CMakeASM_MASMCompiler.cmake" + "src/CMakeFiles/CMakeDirectoryInformation.cmake" + ) + +# Dependency information for all targets: +set(CMAKE_DEPEND_INFO_FILES + "lib/platform_common/CMakeFiles/platform_common.dir/DependInfo.cmake" + "lib/platform_efi/CMakeFiles/platform_efi.dir/DependInfo.cmake" + "lib/platform_os/CMakeFiles/platform_os.dir/DependInfo.cmake" + "test/CMakeFiles/qubic_core_tests.dir/DependInfo.cmake" + "_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/DependInfo.cmake" + "_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/DependInfo.cmake" + "_deps/googletest-build/googletest/CMakeFiles/gtest.dir/DependInfo.cmake" + "_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/DependInfo.cmake" + "src/CMakeFiles/Qubic.dir/DependInfo.cmake" + ) diff --git a/build/CMakeFiles/Makefile2 b/build/CMakeFiles/Makefile2 new file mode 100644 index 000000000..7ec751abb --- /dev/null +++ b/build/CMakeFiles/Makefile2 @@ -0,0 +1,563 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +#============================================================================= +# Directory level rules for the build root directory + +# The main recursive "all" target. +all: lib/platform_common/all +all: lib/platform_efi/all +all: lib/platform_os/all +all: test/all +all: src/all +.PHONY : all + +# The main recursive "codegen" target. +codegen: lib/platform_common/codegen +codegen: lib/platform_efi/codegen +codegen: lib/platform_os/codegen +codegen: test/codegen +codegen: src/codegen +.PHONY : codegen + +# The main recursive "preinstall" target. +preinstall: lib/platform_common/preinstall +preinstall: lib/platform_efi/preinstall +preinstall: lib/platform_os/preinstall +preinstall: test/preinstall +preinstall: src/preinstall +.PHONY : preinstall + +# The main recursive "clean" target. +clean: lib/platform_common/clean +clean: lib/platform_efi/clean +clean: lib/platform_os/clean +clean: test/clean +clean: src/clean +.PHONY : clean + +#============================================================================= +# Directory level rules for directory _deps/googletest-build + +# Recursive "all" directory target. +_deps/googletest-build/all: _deps/googletest-build/googlemock/all +.PHONY : _deps/googletest-build/all + +# Recursive "codegen" directory target. +_deps/googletest-build/codegen: _deps/googletest-build/googlemock/codegen +.PHONY : _deps/googletest-build/codegen + +# Recursive "preinstall" directory target. +_deps/googletest-build/preinstall: _deps/googletest-build/googlemock/preinstall +.PHONY : _deps/googletest-build/preinstall + +# Recursive "clean" directory target. +_deps/googletest-build/clean: _deps/googletest-build/googlemock/clean +.PHONY : _deps/googletest-build/clean + +#============================================================================= +# Directory level rules for directory _deps/googletest-build/googlemock + +# Recursive "all" directory target. +_deps/googletest-build/googlemock/all: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/all +_deps/googletest-build/googlemock/all: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/all +_deps/googletest-build/googlemock/all: _deps/googletest-build/googletest/all +.PHONY : _deps/googletest-build/googlemock/all + +# Recursive "codegen" directory target. +_deps/googletest-build/googlemock/codegen: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/codegen +_deps/googletest-build/googlemock/codegen: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/codegen +_deps/googletest-build/googlemock/codegen: _deps/googletest-build/googletest/codegen +.PHONY : _deps/googletest-build/googlemock/codegen + +# Recursive "preinstall" directory target. +_deps/googletest-build/googlemock/preinstall: _deps/googletest-build/googletest/preinstall +.PHONY : _deps/googletest-build/googlemock/preinstall + +# Recursive "clean" directory target. +_deps/googletest-build/googlemock/clean: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/clean +_deps/googletest-build/googlemock/clean: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/clean +_deps/googletest-build/googlemock/clean: _deps/googletest-build/googletest/clean +.PHONY : _deps/googletest-build/googlemock/clean + +#============================================================================= +# Directory level rules for directory _deps/googletest-build/googletest + +# Recursive "all" directory target. +_deps/googletest-build/googletest/all: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all +_deps/googletest-build/googletest/all: _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/all +.PHONY : _deps/googletest-build/googletest/all + +# Recursive "codegen" directory target. +_deps/googletest-build/googletest/codegen: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/codegen +_deps/googletest-build/googletest/codegen: _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/codegen +.PHONY : _deps/googletest-build/googletest/codegen + +# Recursive "preinstall" directory target. +_deps/googletest-build/googletest/preinstall: +.PHONY : _deps/googletest-build/googletest/preinstall + +# Recursive "clean" directory target. +_deps/googletest-build/googletest/clean: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/clean +_deps/googletest-build/googletest/clean: _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/clean +.PHONY : _deps/googletest-build/googletest/clean + +#============================================================================= +# Directory level rules for directory lib/platform_common + +# Recursive "all" directory target. +lib/platform_common/all: lib/platform_common/CMakeFiles/platform_common.dir/all +.PHONY : lib/platform_common/all + +# Recursive "codegen" directory target. +lib/platform_common/codegen: lib/platform_common/CMakeFiles/platform_common.dir/codegen +.PHONY : lib/platform_common/codegen + +# Recursive "preinstall" directory target. +lib/platform_common/preinstall: +.PHONY : lib/platform_common/preinstall + +# Recursive "clean" directory target. +lib/platform_common/clean: lib/platform_common/CMakeFiles/platform_common.dir/clean +.PHONY : lib/platform_common/clean + +#============================================================================= +# Directory level rules for directory lib/platform_efi + +# Recursive "all" directory target. +lib/platform_efi/all: lib/platform_efi/CMakeFiles/platform_efi.dir/all +.PHONY : lib/platform_efi/all + +# Recursive "codegen" directory target. +lib/platform_efi/codegen: lib/platform_efi/CMakeFiles/platform_efi.dir/codegen +.PHONY : lib/platform_efi/codegen + +# Recursive "preinstall" directory target. +lib/platform_efi/preinstall: +.PHONY : lib/platform_efi/preinstall + +# Recursive "clean" directory target. +lib/platform_efi/clean: lib/platform_efi/CMakeFiles/platform_efi.dir/clean +.PHONY : lib/platform_efi/clean + +#============================================================================= +# Directory level rules for directory lib/platform_os + +# Recursive "all" directory target. +lib/platform_os/all: lib/platform_os/CMakeFiles/platform_os.dir/all +.PHONY : lib/platform_os/all + +# Recursive "codegen" directory target. +lib/platform_os/codegen: lib/platform_os/CMakeFiles/platform_os.dir/codegen +.PHONY : lib/platform_os/codegen + +# Recursive "preinstall" directory target. +lib/platform_os/preinstall: +.PHONY : lib/platform_os/preinstall + +# Recursive "clean" directory target. +lib/platform_os/clean: lib/platform_os/CMakeFiles/platform_os.dir/clean +.PHONY : lib/platform_os/clean + +#============================================================================= +# Directory level rules for directory src + +# Recursive "all" directory target. +src/all: src/CMakeFiles/Qubic.dir/all +.PHONY : src/all + +# Recursive "codegen" directory target. +src/codegen: src/CMakeFiles/Qubic.dir/codegen +.PHONY : src/codegen + +# Recursive "preinstall" directory target. +src/preinstall: +.PHONY : src/preinstall + +# Recursive "clean" directory target. +src/clean: src/CMakeFiles/Qubic.dir/clean +.PHONY : src/clean + +#============================================================================= +# Directory level rules for directory test + +# Recursive "all" directory target. +test/all: test/CMakeFiles/qubic_core_tests.dir/all +test/all: _deps/googletest-build/all +.PHONY : test/all + +# Recursive "codegen" directory target. +test/codegen: test/CMakeFiles/qubic_core_tests.dir/codegen +test/codegen: _deps/googletest-build/codegen +.PHONY : test/codegen + +# Recursive "preinstall" directory target. +test/preinstall: _deps/googletest-build/preinstall +.PHONY : test/preinstall + +# Recursive "clean" directory target. +test/clean: test/CMakeFiles/qubic_core_tests.dir/clean +test/clean: _deps/googletest-build/clean +.PHONY : test/clean + +#============================================================================= +# Target rules for target lib/platform_common/CMakeFiles/platform_common.dir + +# All Build rule for target. +lib/platform_common/CMakeFiles/platform_common.dir/all: + $(MAKE) $(MAKESILENT) -f lib/platform_common/CMakeFiles/platform_common.dir/build.make lib/platform_common/CMakeFiles/platform_common.dir/depend + $(MAKE) $(MAKESILENT) -f lib/platform_common/CMakeFiles/platform_common.dir/build.make lib/platform_common/CMakeFiles/platform_common.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=12,13,14 "Built target platform_common" +.PHONY : lib/platform_common/CMakeFiles/platform_common.dir/all + +# Build rule for subdir invocation for target. +lib/platform_common/CMakeFiles/platform_common.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 3 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 lib/platform_common/CMakeFiles/platform_common.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : lib/platform_common/CMakeFiles/platform_common.dir/rule + +# Convenience name for target. +platform_common: lib/platform_common/CMakeFiles/platform_common.dir/rule +.PHONY : platform_common + +# codegen rule for target. +lib/platform_common/CMakeFiles/platform_common.dir/codegen: + $(MAKE) $(MAKESILENT) -f lib/platform_common/CMakeFiles/platform_common.dir/build.make lib/platform_common/CMakeFiles/platform_common.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=12,13,14 "Finished codegen for target platform_common" +.PHONY : lib/platform_common/CMakeFiles/platform_common.dir/codegen + +# clean rule for target. +lib/platform_common/CMakeFiles/platform_common.dir/clean: + $(MAKE) $(MAKESILENT) -f lib/platform_common/CMakeFiles/platform_common.dir/build.make lib/platform_common/CMakeFiles/platform_common.dir/clean +.PHONY : lib/platform_common/CMakeFiles/platform_common.dir/clean + +#============================================================================= +# Target rules for target lib/platform_efi/CMakeFiles/platform_efi.dir + +# All Build rule for target. +lib/platform_efi/CMakeFiles/platform_efi.dir/all: + $(MAKE) $(MAKESILENT) -f lib/platform_efi/CMakeFiles/platform_efi.dir/build.make lib/platform_efi/CMakeFiles/platform_efi.dir/depend + $(MAKE) $(MAKESILENT) -f lib/platform_efi/CMakeFiles/platform_efi.dir/build.make lib/platform_efi/CMakeFiles/platform_efi.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=15,16,17,18,19 "Built target platform_efi" +.PHONY : lib/platform_efi/CMakeFiles/platform_efi.dir/all + +# Build rule for subdir invocation for target. +lib/platform_efi/CMakeFiles/platform_efi.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 5 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 lib/platform_efi/CMakeFiles/platform_efi.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : lib/platform_efi/CMakeFiles/platform_efi.dir/rule + +# Convenience name for target. +platform_efi: lib/platform_efi/CMakeFiles/platform_efi.dir/rule +.PHONY : platform_efi + +# codegen rule for target. +lib/platform_efi/CMakeFiles/platform_efi.dir/codegen: + $(MAKE) $(MAKESILENT) -f lib/platform_efi/CMakeFiles/platform_efi.dir/build.make lib/platform_efi/CMakeFiles/platform_efi.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=15,16,17,18,19 "Finished codegen for target platform_efi" +.PHONY : lib/platform_efi/CMakeFiles/platform_efi.dir/codegen + +# clean rule for target. +lib/platform_efi/CMakeFiles/platform_efi.dir/clean: + $(MAKE) $(MAKESILENT) -f lib/platform_efi/CMakeFiles/platform_efi.dir/build.make lib/platform_efi/CMakeFiles/platform_efi.dir/clean +.PHONY : lib/platform_efi/CMakeFiles/platform_efi.dir/clean + +#============================================================================= +# Target rules for target lib/platform_os/CMakeFiles/platform_os.dir + +# All Build rule for target. +lib/platform_os/CMakeFiles/platform_os.dir/all: + $(MAKE) $(MAKESILENT) -f lib/platform_os/CMakeFiles/platform_os.dir/build.make lib/platform_os/CMakeFiles/platform_os.dir/depend + $(MAKE) $(MAKESILENT) -f lib/platform_os/CMakeFiles/platform_os.dir/build.make lib/platform_os/CMakeFiles/platform_os.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=20,21,22 "Built target platform_os" +.PHONY : lib/platform_os/CMakeFiles/platform_os.dir/all + +# Build rule for subdir invocation for target. +lib/platform_os/CMakeFiles/platform_os.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 3 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 lib/platform_os/CMakeFiles/platform_os.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : lib/platform_os/CMakeFiles/platform_os.dir/rule + +# Convenience name for target. +platform_os: lib/platform_os/CMakeFiles/platform_os.dir/rule +.PHONY : platform_os + +# codegen rule for target. +lib/platform_os/CMakeFiles/platform_os.dir/codegen: + $(MAKE) $(MAKESILENT) -f lib/platform_os/CMakeFiles/platform_os.dir/build.make lib/platform_os/CMakeFiles/platform_os.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=20,21,22 "Finished codegen for target platform_os" +.PHONY : lib/platform_os/CMakeFiles/platform_os.dir/codegen + +# clean rule for target. +lib/platform_os/CMakeFiles/platform_os.dir/clean: + $(MAKE) $(MAKESILENT) -f lib/platform_os/CMakeFiles/platform_os.dir/build.make lib/platform_os/CMakeFiles/platform_os.dir/clean +.PHONY : lib/platform_os/CMakeFiles/platform_os.dir/clean + +#============================================================================= +# Target rules for target test/CMakeFiles/qubic_core_tests.dir + +# All Build rule for target. +test/CMakeFiles/qubic_core_tests.dir/all: lib/platform_os/CMakeFiles/platform_os.dir/all +test/CMakeFiles/qubic_core_tests.dir/all: lib/platform_common/CMakeFiles/platform_common.dir/all +test/CMakeFiles/qubic_core_tests.dir/all: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all +test/CMakeFiles/qubic_core_tests.dir/all: _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/all + $(MAKE) $(MAKESILENT) -f test/CMakeFiles/qubic_core_tests.dir/build.make test/CMakeFiles/qubic_core_tests.dir/depend + $(MAKE) $(MAKESILENT) -f test/CMakeFiles/qubic_core_tests.dir/build.make test/CMakeFiles/qubic_core_tests.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=23,24,25,26 "Built target qubic_core_tests" +.PHONY : test/CMakeFiles/qubic_core_tests.dir/all + +# Build rule for subdir invocation for target. +test/CMakeFiles/qubic_core_tests.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 14 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 test/CMakeFiles/qubic_core_tests.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : test/CMakeFiles/qubic_core_tests.dir/rule + +# Convenience name for target. +qubic_core_tests: test/CMakeFiles/qubic_core_tests.dir/rule +.PHONY : qubic_core_tests + +# codegen rule for target. +test/CMakeFiles/qubic_core_tests.dir/codegen: + $(MAKE) $(MAKESILENT) -f test/CMakeFiles/qubic_core_tests.dir/build.make test/CMakeFiles/qubic_core_tests.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=23,24,25,26 "Finished codegen for target qubic_core_tests" +.PHONY : test/CMakeFiles/qubic_core_tests.dir/codegen + +# clean rule for target. +test/CMakeFiles/qubic_core_tests.dir/clean: + $(MAKE) $(MAKESILENT) -f test/CMakeFiles/qubic_core_tests.dir/build.make test/CMakeFiles/qubic_core_tests.dir/clean +.PHONY : test/CMakeFiles/qubic_core_tests.dir/clean + +#============================================================================= +# Target rules for target _deps/googletest-build/googlemock/CMakeFiles/gmock.dir + +# All Build rule for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/all: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=4,5 "Built target gmock" +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/all + +# Build rule for subdir invocation for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 4 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule + +# Convenience name for target. +gmock: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule +.PHONY : gmock + +# codegen rule for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/codegen: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=4,5 "Finished codegen for target gmock" +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/codegen + +# clean rule for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/clean: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/clean +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/clean + +#============================================================================= +# Target rules for target _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir + +# All Build rule for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/all: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/all +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/all: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=6,7 "Built target gmock_main" +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/all + +# Build rule for subdir invocation for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 6 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule + +# Convenience name for target. +gmock_main: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule +.PHONY : gmock_main + +# codegen rule for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/codegen: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=6,7 "Finished codegen for target gmock_main" +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/codegen + +# clean rule for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/clean: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/clean +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/clean + +#============================================================================= +# Target rules for target _deps/googletest-build/googletest/CMakeFiles/gtest.dir + +# All Build rule for target. +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/all: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=8,9 "Built target gtest" +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all + +# Build rule for subdir invocation for target. +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 2 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/rule + +# Convenience name for target. +gtest: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/rule +.PHONY : gtest + +# codegen rule for target. +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/codegen: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=8,9 "Finished codegen for target gtest" +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/codegen + +# clean rule for target. +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/clean: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest.dir/clean +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/clean + +#============================================================================= +# Target rules for target _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir + +# All Build rule for target. +_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/all: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/all + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/depend + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=10,11 "Built target gtest_main" +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/all + +# Build rule for subdir invocation for target. +_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 4 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/rule + +# Convenience name for target. +gtest_main: _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/rule +.PHONY : gtest_main + +# codegen rule for target. +_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/codegen: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=10,11 "Finished codegen for target gtest_main" +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/codegen + +# clean rule for target. +_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/clean: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/clean +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/clean + +#============================================================================= +# Target rules for target src/CMakeFiles/Qubic.dir + +# All Build rule for target. +src/CMakeFiles/Qubic.dir/all: lib/platform_efi/CMakeFiles/platform_efi.dir/all +src/CMakeFiles/Qubic.dir/all: lib/platform_common/CMakeFiles/platform_common.dir/all + $(MAKE) $(MAKESILENT) -f src/CMakeFiles/Qubic.dir/build.make src/CMakeFiles/Qubic.dir/depend + $(MAKE) $(MAKESILENT) -f src/CMakeFiles/Qubic.dir/build.make src/CMakeFiles/Qubic.dir/build + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=1,2,3 "Built target Qubic" +.PHONY : src/CMakeFiles/Qubic.dir/all + +# Build rule for subdir invocation for target. +src/CMakeFiles/Qubic.dir/rule: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 11 + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 src/CMakeFiles/Qubic.dir/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : src/CMakeFiles/Qubic.dir/rule + +# Convenience name for target. +Qubic: src/CMakeFiles/Qubic.dir/rule +.PHONY : Qubic + +# codegen rule for target. +src/CMakeFiles/Qubic.dir/codegen: + $(MAKE) $(MAKESILENT) -f src/CMakeFiles/Qubic.dir/build.make src/CMakeFiles/Qubic.dir/codegen + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=1,2,3 "Finished codegen for target Qubic" +.PHONY : src/CMakeFiles/Qubic.dir/codegen + +# clean rule for target. +src/CMakeFiles/Qubic.dir/clean: + $(MAKE) $(MAKESILENT) -f src/CMakeFiles/Qubic.dir/build.make src/CMakeFiles/Qubic.dir/clean +.PHONY : src/CMakeFiles/Qubic.dir/clean + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/build/CMakeFiles/Progress/12 b/build/CMakeFiles/Progress/12 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/12 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/13 b/build/CMakeFiles/Progress/13 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/13 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/15 b/build/CMakeFiles/Progress/15 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/15 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/16 b/build/CMakeFiles/Progress/16 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/16 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/17 b/build/CMakeFiles/Progress/17 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/17 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/20 b/build/CMakeFiles/Progress/20 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/20 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/21 b/build/CMakeFiles/Progress/21 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/21 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/8 b/build/CMakeFiles/Progress/8 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/8 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/9 b/build/CMakeFiles/Progress/9 new file mode 100644 index 000000000..7b4d68d70 --- /dev/null +++ b/build/CMakeFiles/Progress/9 @@ -0,0 +1 @@ +empty \ No newline at end of file diff --git a/build/CMakeFiles/Progress/count.txt b/build/CMakeFiles/Progress/count.txt new file mode 100644 index 000000000..6f4247a62 --- /dev/null +++ b/build/CMakeFiles/Progress/count.txt @@ -0,0 +1 @@ +26 diff --git a/build/CMakeFiles/TargetDirectories.txt b/build/CMakeFiles/TargetDirectories.txt new file mode 100644 index 000000000..2d02b50f0 --- /dev/null +++ b/build/CMakeFiles/TargetDirectories.txt @@ -0,0 +1,72 @@ +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/platform_common.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_common/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/platform_efi.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_efi/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/platform_os.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/lib/platform_os/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/qubic_core_tests.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/test/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/gtest_main.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/install/strip.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/Qubic.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/test.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/edit_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/rebuild_cache.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/list_install_components.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/install.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/install/local.dir +/Users/graf/Documents/GitHub/qubic-core/core/build/src/CMakeFiles/install/strip.dir diff --git a/build/CMakeFiles/cmake.check_cache b/build/CMakeFiles/cmake.check_cache new file mode 100644 index 000000000..3dccd7317 --- /dev/null +++ b/build/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/build/CMakeFiles/progress.marks b/build/CMakeFiles/progress.marks new file mode 100644 index 000000000..6f4247a62 --- /dev/null +++ b/build/CMakeFiles/progress.marks @@ -0,0 +1 @@ +26 diff --git a/build/CTestTestfile.cmake b/build/CTestTestfile.cmake new file mode 100644 index 000000000..ba79b65ba --- /dev/null +++ b/build/CTestTestfile.cmake @@ -0,0 +1,11 @@ +# CMake generated Testfile for +# Source directory: /Users/graf/Documents/GitHub/qubic-core/core +# Build directory: /Users/graf/Documents/GitHub/qubic-core/core/build +# +# This file includes the relevant testing commands required for +# testing this directory and lists subdirectories to be tested as well. +subdirs("lib/platform_common") +subdirs("lib/platform_efi") +subdirs("lib/platform_os") +subdirs("test") +subdirs("src") diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 000000000..4395db464 --- /dev/null +++ b/build/Makefile @@ -0,0 +1,326 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target test +test: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running tests..." + /opt/homebrew/bin/ctest $(ARGS) +.PHONY : test + +# Special rule for the target test +test/fast: test +.PHONY : test/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..." + /opt/homebrew/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..." + /opt/homebrew/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache +.PHONY : rebuild_cache/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Available install components are: \"Unspecified\" \"gmock\" \"gtest\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components +.PHONY : list_install_components/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /opt/homebrew/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /opt/homebrew/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles /Users/graf/Documents/GitHub/qubic-core/core/build//CMakeFiles/progress.marks + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +#============================================================================= +# Target rules for targets named platform_common + +# Build rule for target. +platform_common: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 platform_common +.PHONY : platform_common + +# fast build rule for target. +platform_common/fast: + $(MAKE) $(MAKESILENT) -f lib/platform_common/CMakeFiles/platform_common.dir/build.make lib/platform_common/CMakeFiles/platform_common.dir/build +.PHONY : platform_common/fast + +#============================================================================= +# Target rules for targets named platform_efi + +# Build rule for target. +platform_efi: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 platform_efi +.PHONY : platform_efi + +# fast build rule for target. +platform_efi/fast: + $(MAKE) $(MAKESILENT) -f lib/platform_efi/CMakeFiles/platform_efi.dir/build.make lib/platform_efi/CMakeFiles/platform_efi.dir/build +.PHONY : platform_efi/fast + +#============================================================================= +# Target rules for targets named platform_os + +# Build rule for target. +platform_os: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 platform_os +.PHONY : platform_os + +# fast build rule for target. +platform_os/fast: + $(MAKE) $(MAKESILENT) -f lib/platform_os/CMakeFiles/platform_os.dir/build.make lib/platform_os/CMakeFiles/platform_os.dir/build +.PHONY : platform_os/fast + +#============================================================================= +# Target rules for targets named qubic_core_tests + +# Build rule for target. +qubic_core_tests: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 qubic_core_tests +.PHONY : qubic_core_tests + +# fast build rule for target. +qubic_core_tests/fast: + $(MAKE) $(MAKESILENT) -f test/CMakeFiles/qubic_core_tests.dir/build.make test/CMakeFiles/qubic_core_tests.dir/build +.PHONY : qubic_core_tests/fast + +#============================================================================= +# Target rules for targets named gmock + +# Build rule for target. +gmock: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 gmock +.PHONY : gmock + +# fast build rule for target. +gmock/fast: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build +.PHONY : gmock/fast + +#============================================================================= +# Target rules for targets named gmock_main + +# Build rule for target. +gmock_main: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 gmock_main +.PHONY : gmock_main + +# fast build rule for target. +gmock_main/fast: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build +.PHONY : gmock_main/fast + +#============================================================================= +# Target rules for targets named gtest + +# Build rule for target. +gtest: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 gtest +.PHONY : gtest + +# fast build rule for target. +gtest/fast: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build +.PHONY : gtest/fast + +#============================================================================= +# Target rules for targets named gtest_main + +# Build rule for target. +gtest_main: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 gtest_main +.PHONY : gtest_main + +# fast build rule for target. +gtest_main/fast: + $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build.make _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/build +.PHONY : gtest_main/fast + +#============================================================================= +# Target rules for targets named Qubic + +# Build rule for target. +Qubic: cmake_check_build_system + $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 Qubic +.PHONY : Qubic + +# fast build rule for target. +Qubic/fast: + $(MAKE) $(MAKESILENT) -f src/CMakeFiles/Qubic.dir/build.make src/CMakeFiles/Qubic.dir/build +.PHONY : Qubic/fast + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... install" + @echo "... install/local" + @echo "... install/strip" + @echo "... list_install_components" + @echo "... rebuild_cache" + @echo "... test" + @echo "... Qubic" + @echo "... gmock" + @echo "... gmock_main" + @echo "... gtest" + @echo "... gtest_main" + @echo "... platform_common" + @echo "... platform_efi" + @echo "... platform_os" + @echo "... qubic_core_tests" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/build/_deps/googletest-build/CMakeFiles/CMakeDirectoryInformation.cmake b/build/_deps/googletest-build/CMakeFiles/CMakeDirectoryInformation.cmake new file mode 100644 index 000000000..e2771e437 --- /dev/null +++ b/build/_deps/googletest-build/CMakeFiles/CMakeDirectoryInformation.cmake @@ -0,0 +1,16 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Relative path conversion top directories. +set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/Users/graf/Documents/GitHub/qubic-core/core") +set(CMAKE_RELATIVE_PATH_TOP_BINARY "/Users/graf/Documents/GitHub/qubic-core/core/build") + +# Force unix paths in dependencies. +set(CMAKE_FORCE_UNIX_PATHS 1) + + +# The C and CXX include file regular expressions for this directory. +set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$") +set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$") +set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN}) +set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN}) diff --git a/build/_deps/googletest-build/CMakeFiles/progress.marks b/build/_deps/googletest-build/CMakeFiles/progress.marks new file mode 100644 index 000000000..45a4fb75d --- /dev/null +++ b/build/_deps/googletest-build/CMakeFiles/progress.marks @@ -0,0 +1 @@ +8 diff --git a/build/_deps/googletest-build/CTestTestfile.cmake b/build/_deps/googletest-build/CTestTestfile.cmake new file mode 100644 index 000000000..cc34be517 --- /dev/null +++ b/build/_deps/googletest-build/CTestTestfile.cmake @@ -0,0 +1,7 @@ +# CMake generated Testfile for +# Source directory: /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src +# Build directory: /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build +# +# This file includes the relevant testing commands required for +# testing this directory and lists subdirectories to be tested as well. +subdirs("googlemock") diff --git a/build/_deps/googletest-build/Makefile b/build/_deps/googletest-build/Makefile new file mode 100644 index 000000000..8dad692c3 --- /dev/null +++ b/build/_deps/googletest-build/Makefile @@ -0,0 +1,200 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target test +test: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running tests..." + /opt/homebrew/bin/ctest $(ARGS) +.PHONY : test + +# Special rule for the target test +test/fast: test +.PHONY : test/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..." + /opt/homebrew/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..." + /opt/homebrew/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache +.PHONY : rebuild_cache/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Available install components are: \"Unspecified\" \"gmock\" \"gtest\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components +.PHONY : list_install_components/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /opt/homebrew/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /opt/homebrew/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# The main all target +all: cmake_check_build_system + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build//CMakeFiles/progress.marks + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/clean +.PHONY : clean + +# The main clean target +clean/fast: clean +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... install" + @echo "... install/local" + @echo "... install/strip" + @echo "... list_install_components" + @echo "... rebuild_cache" + @echo "... test" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/build/_deps/googletest-build/cmake_install.cmake b/build/_deps/googletest-build/cmake_install.cmake new file mode 100644 index 000000000..27399f33d --- /dev/null +++ b/build/_deps/googletest-build/cmake_install.cmake @@ -0,0 +1,51 @@ +# Install script for directory: /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "Release") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set path to fallback-tool for dependency-resolution. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for each subdirectory. + include("/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/cmake_install.cmake") + +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +if(CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/install_local_manifest.txt" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/CMakeDirectoryInformation.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/CMakeDirectoryInformation.cmake new file mode 100644 index 000000000..e2771e437 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/CMakeDirectoryInformation.cmake @@ -0,0 +1,16 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Relative path conversion top directories. +set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/Users/graf/Documents/GitHub/qubic-core/core") +set(CMAKE_RELATIVE_PATH_TOP_BINARY "/Users/graf/Documents/GitHub/qubic-core/core/build") + +# Force unix paths in dependencies. +set(CMAKE_FORCE_UNIX_PATHS 1) + + +# The C and CXX include file regular expressions for this directory. +set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$") +set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$") +set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN}) +set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN}) diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/DependInfo.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/DependInfo.cmake new file mode 100644 index 000000000..04a4301b0 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/DependInfo.cmake @@ -0,0 +1,23 @@ + +# Consider dependencies only in project. +set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF) + +# The set of languages for which implicit dependencies are needed: +set(CMAKE_DEPENDS_LANGUAGES + ) + +# The set of dependency files which are needed: +set(CMAKE_DEPENDS_DEPENDENCY_FILES + "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock-all.cc" "_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o" "gcc" "_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o.d" + ) + +# Targets to which this target links which contain Fortran sources. +set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES + ) + +# Targets to which this target links which contain Fortran sources. +set(CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES + ) + +# Fortran module output directory. +set(CMAKE_Fortran_TARGET_MODULE_DIR "") diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make new file mode 100644 index 000000000..74ec3e0c0 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make @@ -0,0 +1,114 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Delete rule output on recipe failure. +.DELETE_ON_ERROR: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +# Include any dependencies generated for this target. +include _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend.make +# Include any dependencies generated by the compiler for this target. +include _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.make + +# Include the progress variables for this target. +include _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/progress.make + +# Include the compile flags for this target's objects. +include _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/flags.make + +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/codegen: +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/codegen + +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/flags.make +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o: _deps/googletest-src/googlemock/src/gmock-all.cc +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.ts + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -MD -MT _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o -MF CMakeFiles/gmock.dir/src/gmock-all.cc.o.d -o CMakeFiles/gmock.dir/src/gmock-all.cc.o -c /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock-all.cc + +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.i: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing CXX source to CMakeFiles/gmock.dir/src/gmock-all.cc.i" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -E /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock-all.cc > CMakeFiles/gmock.dir/src/gmock-all.cc.i + +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.s: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling CXX source to assembly CMakeFiles/gmock.dir/src/gmock-all.cc.s" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock-all.cc -o CMakeFiles/gmock.dir/src/gmock-all.cc.s + +# Object files for target gmock +gmock_OBJECTS = \ +"CMakeFiles/gmock.dir/src/gmock-all.cc.o" + +# External object files for target gmock +gmock_EXTERNAL_OBJECTS = + +lib/libgmock.a: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o +lib/libgmock.a: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make +lib/libgmock.a: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/link.txt + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX static library ../../../lib/libgmock.a" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && $(CMAKE_COMMAND) -P CMakeFiles/gmock.dir/cmake_clean_target.cmake + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/gmock.dir/link.txt --verbose=$(VERBOSE) + +# Rule to build all files generated by this target. +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build: lib/libgmock.a +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build + +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/clean: + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && $(CMAKE_COMMAND) -P CMakeFiles/gmock.dir/cmake_clean.cmake +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/clean + +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /Users/graf/Documents/GitHub/qubic-core/core /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock /Users/graf/Documents/GitHub/qubic-core/core/build /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/DependInfo.cmake "--color=$(COLOR)" +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend + diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean.cmake new file mode 100644 index 000000000..ebe1b2d6b --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean.cmake @@ -0,0 +1,11 @@ +file(REMOVE_RECURSE + "../../../bin/libgmock.pdb" + "../../../lib/libgmock.a" + "CMakeFiles/gmock.dir/src/gmock-all.cc.o" + "CMakeFiles/gmock.dir/src/gmock-all.cc.o.d" +) + +# Per-language clean rules from dependency scanning. +foreach(lang CXX) + include(CMakeFiles/gmock.dir/cmake_clean_${lang}.cmake OPTIONAL) +endforeach() diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean_target.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean_target.cmake new file mode 100644 index 000000000..541729e35 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/cmake_clean_target.cmake @@ -0,0 +1,3 @@ +file(REMOVE_RECURSE + "../../../lib/libgmock.a" +) diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.make new file mode 100644 index 000000000..c777df51c --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.make @@ -0,0 +1,2 @@ +# Empty compiler generated dependencies file for gmock. +# This may be replaced when dependencies are built. diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.ts b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.ts new file mode 100644 index 000000000..adc68d16e --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/compiler_depend.ts @@ -0,0 +1,2 @@ +# CMAKE generated file: DO NOT EDIT! +# Timestamp file for compiler generated dependencies management for gmock. diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend.make new file mode 100644 index 000000000..7a05e2f19 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/depend.make @@ -0,0 +1,2 @@ +# Empty dependencies file for gmock. +# This may be replaced when dependencies are built. diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/flags.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/flags.make new file mode 100644 index 000000000..79045a103 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/flags.make @@ -0,0 +1,12 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# compile CXX with /usr/bin/c++ +CXX_DEFINES = + +CXX_INCLUDES = -I/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/include -I/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock -isystem /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/include -isystem /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest + +CXX_FLAGSarm64 = -std=c++20 -arch arm64 -DGTEST_HAS_PTHREAD=1 + +CXX_FLAGS = -std=c++20 -arch arm64 -DGTEST_HAS_PTHREAD=1 + diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/link.txt b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/link.txt new file mode 100644 index 000000000..79d00531e --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/link.txt @@ -0,0 +1,2 @@ +/usr/bin/ar qc ../../../lib/libgmock.a "CMakeFiles/gmock.dir/src/gmock-all.cc.o" +/usr/bin/ranlib ../../../lib/libgmock.a diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/progress.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/progress.make new file mode 100644 index 000000000..19ce96ee7 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/progress.make @@ -0,0 +1,3 @@ +CMAKE_PROGRESS_1 = 4 +CMAKE_PROGRESS_2 = 5 + diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/DependInfo.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/DependInfo.cmake new file mode 100644 index 000000000..2ad833056 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/DependInfo.cmake @@ -0,0 +1,23 @@ + +# Consider dependencies only in project. +set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF) + +# The set of languages for which implicit dependencies are needed: +set(CMAKE_DEPENDS_LANGUAGES + ) + +# The set of dependency files which are needed: +set(CMAKE_DEPENDS_DEPENDENCY_FILES + "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock_main.cc" "_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o" "gcc" "_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o.d" + ) + +# Targets to which this target links which contain Fortran sources. +set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES + ) + +# Targets to which this target links which contain Fortran sources. +set(CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES + ) + +# Fortran module output directory. +set(CMAKE_Fortran_TARGET_MODULE_DIR "") diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make new file mode 100644 index 000000000..a04b28084 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make @@ -0,0 +1,114 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Delete rule output on recipe failure. +.DELETE_ON_ERROR: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +# Include any dependencies generated for this target. +include _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend.make +# Include any dependencies generated by the compiler for this target. +include _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.make + +# Include the progress variables for this target. +include _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/progress.make + +# Include the compile flags for this target's objects. +include _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/flags.make + +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/codegen: +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/codegen + +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/flags.make +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o: _deps/googletest-src/googlemock/src/gmock_main.cc +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.ts + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -MD -MT _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o -MF CMakeFiles/gmock_main.dir/src/gmock_main.cc.o.d -o CMakeFiles/gmock_main.dir/src/gmock_main.cc.o -c /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock_main.cc + +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.i: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing CXX source to CMakeFiles/gmock_main.dir/src/gmock_main.cc.i" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -E /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock_main.cc > CMakeFiles/gmock_main.dir/src/gmock_main.cc.i + +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.s: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling CXX source to assembly CMakeFiles/gmock_main.dir/src/gmock_main.cc.s" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/src/gmock_main.cc -o CMakeFiles/gmock_main.dir/src/gmock_main.cc.s + +# Object files for target gmock_main +gmock_main_OBJECTS = \ +"CMakeFiles/gmock_main.dir/src/gmock_main.cc.o" + +# External object files for target gmock_main +gmock_main_EXTERNAL_OBJECTS = + +lib/libgmock_main.a: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o +lib/libgmock_main.a: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make +lib/libgmock_main.a: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/link.txt + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX static library ../../../lib/libgmock_main.a" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && $(CMAKE_COMMAND) -P CMakeFiles/gmock_main.dir/cmake_clean_target.cmake + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/gmock_main.dir/link.txt --verbose=$(VERBOSE) + +# Rule to build all files generated by this target. +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build: lib/libgmock_main.a +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build + +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/clean: + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock && $(CMAKE_COMMAND) -P CMakeFiles/gmock_main.dir/cmake_clean.cmake +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/clean + +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /Users/graf/Documents/GitHub/qubic-core/core /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock /Users/graf/Documents/GitHub/qubic-core/core/build /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/DependInfo.cmake "--color=$(COLOR)" +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend + diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean.cmake new file mode 100644 index 000000000..91d0b2dfd --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean.cmake @@ -0,0 +1,11 @@ +file(REMOVE_RECURSE + "../../../bin/libgmock_main.pdb" + "../../../lib/libgmock_main.a" + "CMakeFiles/gmock_main.dir/src/gmock_main.cc.o" + "CMakeFiles/gmock_main.dir/src/gmock_main.cc.o.d" +) + +# Per-language clean rules from dependency scanning. +foreach(lang CXX) + include(CMakeFiles/gmock_main.dir/cmake_clean_${lang}.cmake OPTIONAL) +endforeach() diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean_target.cmake b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean_target.cmake new file mode 100644 index 000000000..1c127c0df --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/cmake_clean_target.cmake @@ -0,0 +1,3 @@ +file(REMOVE_RECURSE + "../../../lib/libgmock_main.a" +) diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.make new file mode 100644 index 000000000..08094148d --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.make @@ -0,0 +1,2 @@ +# Empty compiler generated dependencies file for gmock_main. +# This may be replaced when dependencies are built. diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.ts b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.ts new file mode 100644 index 000000000..85535ac81 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/compiler_depend.ts @@ -0,0 +1,2 @@ +# CMAKE generated file: DO NOT EDIT! +# Timestamp file for compiler generated dependencies management for gmock_main. diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend.make new file mode 100644 index 000000000..4a18b61b4 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/depend.make @@ -0,0 +1,2 @@ +# Empty dependencies file for gmock_main. +# This may be replaced when dependencies are built. diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/flags.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/flags.make new file mode 100644 index 000000000..9ba479131 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/flags.make @@ -0,0 +1,12 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# compile CXX with /usr/bin/c++ +CXX_DEFINES = + +CXX_INCLUDES = -isystem /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/include -isystem /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock -isystem /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/include -isystem /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest + +CXX_FLAGSarm64 = -std=c++20 -arch arm64 -DGTEST_HAS_PTHREAD=1 + +CXX_FLAGS = -std=c++20 -arch arm64 -DGTEST_HAS_PTHREAD=1 + diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/link.txt b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/link.txt new file mode 100644 index 000000000..fc26c305b --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/link.txt @@ -0,0 +1,2 @@ +/usr/bin/ar qc ../../../lib/libgmock_main.a CMakeFiles/gmock_main.dir/src/gmock_main.cc.o +/usr/bin/ranlib ../../../lib/libgmock_main.a diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/progress.make b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/progress.make new file mode 100644 index 000000000..880889661 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/progress.make @@ -0,0 +1,3 @@ +CMAKE_PROGRESS_1 = 6 +CMAKE_PROGRESS_2 = 7 + diff --git a/build/_deps/googletest-build/googlemock/CMakeFiles/progress.marks b/build/_deps/googletest-build/googlemock/CMakeFiles/progress.marks new file mode 100644 index 000000000..45a4fb75d --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CMakeFiles/progress.marks @@ -0,0 +1 @@ +8 diff --git a/build/_deps/googletest-build/googlemock/CTestTestfile.cmake b/build/_deps/googletest-build/googlemock/CTestTestfile.cmake new file mode 100644 index 000000000..42b20f5b8 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/CTestTestfile.cmake @@ -0,0 +1,7 @@ +# CMake generated Testfile for +# Source directory: /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock +# Build directory: /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock +# +# This file includes the relevant testing commands required for +# testing this directory and lists subdirectories to be tested as well. +subdirs("../googletest") diff --git a/build/_deps/googletest-build/googlemock/Makefile b/build/_deps/googletest-build/googlemock/Makefile new file mode 100644 index 000000000..922db7679 --- /dev/null +++ b/build/_deps/googletest-build/googlemock/Makefile @@ -0,0 +1,284 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Default target executed when no arguments are given to make. +default_target: all +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target test +test: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running tests..." + /opt/homebrew/bin/ctest $(ARGS) +.PHONY : test + +# Special rule for the target test +test/fast: test +.PHONY : test/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake cache editor..." + /opt/homebrew/bin/ccmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache +.PHONY : edit_cache/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..." + /opt/homebrew/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache +.PHONY : rebuild_cache/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Available install components are: \"Unspecified\" \"gmock\" \"gtest\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components +.PHONY : list_install_components/fast + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /opt/homebrew/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..." + /opt/homebrew/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..." + /opt/homebrew/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# The main all target +all: cmake_check_build_system + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock//CMakeFiles/progress.marks + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/all + $(CMAKE_COMMAND) -E cmake_progress_start /Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/clean +.PHONY : clean + +# The main clean target +clean/fast: clean +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Convenience name for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule + +# Convenience name for target. +gmock: _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/rule +.PHONY : gmock + +# fast build rule for target. +gmock/fast: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build +.PHONY : gmock/fast + +# Convenience name for target. +_deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule +.PHONY : _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule + +# Convenience name for target. +gmock_main: _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/rule +.PHONY : gmock_main + +# fast build rule for target. +gmock_main/fast: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build +.PHONY : gmock_main/fast + +src/gmock-all.o: src/gmock-all.cc.o +.PHONY : src/gmock-all.o + +# target to build an object file +src/gmock-all.cc.o: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o +.PHONY : src/gmock-all.cc.o + +src/gmock-all.i: src/gmock-all.cc.i +.PHONY : src/gmock-all.i + +# target to preprocess a source file +src/gmock-all.cc.i: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.i +.PHONY : src/gmock-all.cc.i + +src/gmock-all.s: src/gmock-all.cc.s +.PHONY : src/gmock-all.s + +# target to generate assembly for a file +src/gmock-all.cc.s: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.s +.PHONY : src/gmock-all.cc.s + +src/gmock_main.o: src/gmock_main.cc.o +.PHONY : src/gmock_main.o + +# target to build an object file +src/gmock_main.cc.o: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o +.PHONY : src/gmock_main.cc.o + +src/gmock_main.i: src/gmock_main.cc.i +.PHONY : src/gmock_main.i + +# target to preprocess a source file +src/gmock_main.cc.i: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.i +.PHONY : src/gmock_main.cc.i + +src/gmock_main.s: src/gmock_main.cc.s +.PHONY : src/gmock_main.s + +# target to generate assembly for a file +src/gmock_main.cc.s: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(MAKE) $(MAKESILENT) -f _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/build.make _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.s +.PHONY : src/gmock_main.cc.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... edit_cache" + @echo "... install" + @echo "... install/local" + @echo "... install/strip" + @echo "... list_install_components" + @echo "... rebuild_cache" + @echo "... test" + @echo "... gmock" + @echo "... gmock_main" + @echo "... src/gmock-all.o" + @echo "... src/gmock-all.i" + @echo "... src/gmock-all.s" + @echo "... src/gmock_main.o" + @echo "... src/gmock_main.i" + @echo "... src/gmock_main.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/build/_deps/googletest-build/googlemock/cmake_install.cmake b/build/_deps/googletest-build/googlemock/cmake_install.cmake new file mode 100644 index 000000000..f43aff17c --- /dev/null +++ b/build/_deps/googletest-build/googlemock/cmake_install.cmake @@ -0,0 +1,79 @@ +# Install script for directory: /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock + +# Set the install prefix +if(NOT DEFINED CMAKE_INSTALL_PREFIX) + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") + +# Set the install configuration name. +if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) + if(BUILD_TYPE) + string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" + CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") + else() + set(CMAKE_INSTALL_CONFIG_NAME "Release") + endif() + message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") +endif() + +# Set the component getting installed. +if(NOT CMAKE_INSTALL_COMPONENT) + if(COMPONENT) + message(STATUS "Install component: \"${COMPONENT}\"") + set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") + else() + set(CMAKE_INSTALL_COMPONENT) + endif() +endif() + +# Is this installation the result of a crosscompile? +if(NOT DEFINED CMAKE_CROSSCOMPILING) + set(CMAKE_CROSSCOMPILING "FALSE") +endif() + +# Set path to fallback-tool for dependency-resolution. +if(NOT DEFINED CMAKE_OBJDUMP) + set(CMAKE_OBJDUMP "/usr/bin/objdump") +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "gmock" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/include" TYPE DIRECTORY FILES "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googlemock/include/") +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "gmock" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" TYPE STATIC_LIBRARY FILES "/Users/graf/Documents/GitHub/qubic-core/core/build/lib/libgmock.a") + if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libgmock.a" AND + NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libgmock.a") + execute_process(COMMAND "/usr/bin/ranlib" "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libgmock.a") + endif() +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "gmock" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib" TYPE STATIC_LIBRARY FILES "/Users/graf/Documents/GitHub/qubic-core/core/build/lib/libgmock_main.a") + if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libgmock_main.a" AND + NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libgmock_main.a") + execute_process(COMMAND "/usr/bin/ranlib" "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/lib/libgmock_main.a") + endif() +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "gmock" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig" TYPE FILE FILES "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/generated/gmock.pc") +endif() + +if(CMAKE_INSTALL_COMPONENT STREQUAL "gmock" OR NOT CMAKE_INSTALL_COMPONENT) + file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig" TYPE FILE FILES "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/generated/gmock_main.pc") +endif() + +if(NOT CMAKE_INSTALL_LOCAL_ONLY) + # Include the install script for each subdirectory. + include("/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/cmake_install.cmake") + +endif() + +string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT + "${CMAKE_INSTALL_MANIFEST_FILES}") +if(CMAKE_INSTALL_LOCAL_ONLY) + file(WRITE "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googlemock/install_local_manifest.txt" + "${CMAKE_INSTALL_MANIFEST_CONTENT}") +endif() diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/CMakeDirectoryInformation.cmake b/build/_deps/googletest-build/googletest/CMakeFiles/CMakeDirectoryInformation.cmake new file mode 100644 index 000000000..e2771e437 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/CMakeDirectoryInformation.cmake @@ -0,0 +1,16 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Relative path conversion top directories. +set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/Users/graf/Documents/GitHub/qubic-core/core") +set(CMAKE_RELATIVE_PATH_TOP_BINARY "/Users/graf/Documents/GitHub/qubic-core/core/build") + +# Force unix paths in dependencies. +set(CMAKE_FORCE_UNIX_PATHS 1) + + +# The C and CXX include file regular expressions for this directory. +set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$") +set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$") +set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN}) +set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN}) diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-release.cmake b/build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-release.cmake new file mode 100644 index 000000000..5cfcc7ae2 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets-release.cmake @@ -0,0 +1,49 @@ +#---------------------------------------------------------------- +# Generated CMake target import file for configuration "Release". +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "GTest::gtest" for configuration "Release" +set_property(TARGET GTest::gtest APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(GTest::gtest PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libgtest.a" + ) + +list(APPEND _cmake_import_check_targets GTest::gtest ) +list(APPEND _cmake_import_check_files_for_GTest::gtest "${_IMPORT_PREFIX}/lib/libgtest.a" ) + +# Import target "GTest::gtest_main" for configuration "Release" +set_property(TARGET GTest::gtest_main APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(GTest::gtest_main PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libgtest_main.a" + ) + +list(APPEND _cmake_import_check_targets GTest::gtest_main ) +list(APPEND _cmake_import_check_files_for_GTest::gtest_main "${_IMPORT_PREFIX}/lib/libgtest_main.a" ) + +# Import target "GTest::gmock" for configuration "Release" +set_property(TARGET GTest::gmock APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(GTest::gmock PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libgmock.a" + ) + +list(APPEND _cmake_import_check_targets GTest::gmock ) +list(APPEND _cmake_import_check_files_for_GTest::gmock "${_IMPORT_PREFIX}/lib/libgmock.a" ) + +# Import target "GTest::gmock_main" for configuration "Release" +set_property(TARGET GTest::gmock_main APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) +set_target_properties(GTest::gmock_main PROPERTIES + IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libgmock_main.a" + ) + +list(APPEND _cmake_import_check_targets GTest::gmock_main ) +list(APPEND _cmake_import_check_files_for_GTest::gmock_main "${_IMPORT_PREFIX}/lib/libgmock_main.a" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets.cmake b/build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets.cmake new file mode 100644 index 000000000..c9301a77e --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/Export/0c08b8e77dd885bfe55a19a9659d9fc1/GTestTargets.cmake @@ -0,0 +1,139 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8) + message(FATAL_ERROR "CMake >= 2.8.12 required") +endif() +if(CMAKE_VERSION VERSION_LESS "2.8.12") + message(FATAL_ERROR "CMake >= 2.8.12 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.8.12...3.31) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_cmake_targets_defined "") +set(_cmake_targets_not_defined "") +set(_cmake_expected_targets "") +foreach(_cmake_expected_target IN ITEMS GTest::gtest GTest::gtest_main GTest::gmock GTest::gmock_main) + list(APPEND _cmake_expected_targets "${_cmake_expected_target}") + if(TARGET "${_cmake_expected_target}") + list(APPEND _cmake_targets_defined "${_cmake_expected_target}") + else() + list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}") + endif() +endforeach() +unset(_cmake_expected_target) +if(_cmake_targets_defined STREQUAL _cmake_expected_targets) + unset(_cmake_targets_defined) + unset(_cmake_targets_not_defined) + unset(_cmake_expected_targets) + unset(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT _cmake_targets_defined STREQUAL "") + string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}") + string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n") +endif() +unset(_cmake_targets_defined) +unset(_cmake_targets_not_defined) +unset(_cmake_expected_targets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target GTest::gtest +add_library(GTest::gtest STATIC IMPORTED) + +set_target_properties(GTest::gtest PROPERTIES + INTERFACE_COMPILE_FEATURES "cxx_std_14" + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "Threads::Threads" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" +) + +# Create imported target GTest::gtest_main +add_library(GTest::gtest_main STATIC IMPORTED) + +set_target_properties(GTest::gtest_main PROPERTIES + INTERFACE_COMPILE_FEATURES "cxx_std_14" + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "Threads::Threads;GTest::gtest" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" +) + +# Create imported target GTest::gmock +add_library(GTest::gmock STATIC IMPORTED) + +set_target_properties(GTest::gmock PROPERTIES + INTERFACE_COMPILE_FEATURES "cxx_std_14" + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "Threads::Threads;GTest::gtest" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" +) + +# Create imported target GTest::gmock_main +add_library(GTest::gmock_main STATIC IMPORTED) + +set_target_properties(GTest::gmock_main PROPERTIES + INTERFACE_COMPILE_FEATURES "cxx_std_14" + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "Threads::Threads;GTest::gmock" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include" +) + +# Load information for each installed configuration. +file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/GTestTargets-*.cmake") +foreach(_cmake_config_file IN LISTS _cmake_config_files) + include("${_cmake_config_file}") +endforeach() +unset(_cmake_config_file) +unset(_cmake_config_files) + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(_cmake_target IN LISTS _cmake_import_check_targets) + if(CMAKE_VERSION VERSION_LESS "3.28" + OR NOT DEFINED _cmake_import_check_xcframework_for_${_cmake_target} + OR NOT IS_DIRECTORY "${_cmake_import_check_xcframework_for_${_cmake_target}}") + foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}") + if(NOT EXISTS "${_cmake_file}") + message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file + \"${_cmake_file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + endif() + unset(_cmake_file) + unset("_cmake_import_check_files_for_${_cmake_target}") +endforeach() +unset(_cmake_target) +unset(_cmake_import_check_targets) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/DependInfo.cmake b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/DependInfo.cmake new file mode 100644 index 000000000..c88bd5b02 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/DependInfo.cmake @@ -0,0 +1,23 @@ + +# Consider dependencies only in project. +set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF) + +# The set of languages for which implicit dependencies are needed: +set(CMAKE_DEPENDS_LANGUAGES + ) + +# The set of dependency files which are needed: +set(CMAKE_DEPENDS_DEPENDENCY_FILES + "/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/src/gtest-all.cc" "_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o" "gcc" "_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o.d" + ) + +# Targets to which this target links which contain Fortran sources. +set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES + ) + +# Targets to which this target links which contain Fortran sources. +set(CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES + ) + +# Fortran module output directory. +set(CMAKE_Fortran_TARGET_MODULE_DIR "") diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make new file mode 100644 index 000000000..bd11e3126 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make @@ -0,0 +1,114 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# Delete rule output on recipe failure. +.DELETE_ON_ERROR: + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + +# Disable VCS-based implicit rules. +% : %,v + +# Disable VCS-based implicit rules. +% : RCS/% + +# Disable VCS-based implicit rules. +% : RCS/%,v + +# Disable VCS-based implicit rules. +% : SCCS/s.% + +# Disable VCS-based implicit rules. +% : s.% + +.SUFFIXES: .hpux_make_needs_suffix_list + +# Command-line flag to silence nested $(MAKE). +$(VERBOSE)MAKESILENT = -s + +#Suppress display of executed commands. +$(VERBOSE).SILENT: + +# A target that is always out of date. +cmake_force: +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /opt/homebrew/bin/cmake + +# The command to remove a file. +RM = /opt/homebrew/bin/cmake -E rm -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /Users/graf/Documents/GitHub/qubic-core/core + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /Users/graf/Documents/GitHub/qubic-core/core/build + +# Include any dependencies generated for this target. +include _deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend.make +# Include any dependencies generated by the compiler for this target. +include _deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.make + +# Include the progress variables for this target. +include _deps/googletest-build/googletest/CMakeFiles/gtest.dir/progress.make + +# Include the compile flags for this target's objects. +include _deps/googletest-build/googletest/CMakeFiles/gtest.dir/flags.make + +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/codegen: +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/codegen + +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/flags.make +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o: _deps/googletest-src/googletest/src/gtest-all.cc +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.ts + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -MD -MT _deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o -MF CMakeFiles/gtest.dir/src/gtest-all.cc.o.d -o CMakeFiles/gtest.dir/src/gtest-all.cc.o -c /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/src/gtest-all.cc + +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.i: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing CXX source to CMakeFiles/gtest.dir/src/gtest-all.cc.i" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -E /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/src/gtest-all.cc > CMakeFiles/gtest.dir/src/gtest-all.cc.i + +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.s: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling CXX source to assembly CMakeFiles/gtest.dir/src/gtest-all.cc.s" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest && /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/src/gtest-all.cc -o CMakeFiles/gtest.dir/src/gtest-all.cc.s + +# Object files for target gtest +gtest_OBJECTS = \ +"CMakeFiles/gtest.dir/src/gtest-all.cc.o" + +# External object files for target gtest +gtest_EXTERNAL_OBJECTS = + +lib/libgtest.a: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o +lib/libgtest.a: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build.make +lib/libgtest.a: _deps/googletest-build/googletest/CMakeFiles/gtest.dir/link.txt + @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/Users/graf/Documents/GitHub/qubic-core/core/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX static library ../../../lib/libgtest.a" + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest && $(CMAKE_COMMAND) -P CMakeFiles/gtest.dir/cmake_clean_target.cmake + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest && $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/gtest.dir/link.txt --verbose=$(VERBOSE) + +# Rule to build all files generated by this target. +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/build: lib/libgtest.a +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/build + +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/clean: + cd /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest && $(CMAKE_COMMAND) -P CMakeFiles/gtest.dir/cmake_clean.cmake +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/clean + +_deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend: + cd /Users/graf/Documents/GitHub/qubic-core/core/build && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /Users/graf/Documents/GitHub/qubic-core/core /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest /Users/graf/Documents/GitHub/qubic-core/core/build /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest /Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/DependInfo.cmake "--color=$(COLOR)" +.PHONY : _deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend + diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean.cmake b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean.cmake new file mode 100644 index 000000000..82336bb63 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean.cmake @@ -0,0 +1,11 @@ +file(REMOVE_RECURSE + "../../../bin/libgtest.pdb" + "../../../lib/libgtest.a" + "CMakeFiles/gtest.dir/src/gtest-all.cc.o" + "CMakeFiles/gtest.dir/src/gtest-all.cc.o.d" +) + +# Per-language clean rules from dependency scanning. +foreach(lang CXX) + include(CMakeFiles/gtest.dir/cmake_clean_${lang}.cmake OPTIONAL) +endforeach() diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean_target.cmake b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean_target.cmake new file mode 100644 index 000000000..e2ada84f7 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/cmake_clean_target.cmake @@ -0,0 +1,3 @@ +file(REMOVE_RECURSE + "../../../lib/libgtest.a" +) diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.make b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.make new file mode 100644 index 000000000..71b2ee690 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.make @@ -0,0 +1,2 @@ +# Empty compiler generated dependencies file for gtest. +# This may be replaced when dependencies are built. diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.ts b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.ts new file mode 100644 index 000000000..32ab1fb11 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/compiler_depend.ts @@ -0,0 +1,2 @@ +# CMAKE generated file: DO NOT EDIT! +# Timestamp file for compiler generated dependencies management for gtest. diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend.make b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend.make new file mode 100644 index 000000000..37ac348db --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/depend.make @@ -0,0 +1,2 @@ +# Empty dependencies file for gtest. +# This may be replaced when dependencies are built. diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/flags.make b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/flags.make new file mode 100644 index 000000000..a75d6e576 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/flags.make @@ -0,0 +1,12 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 4.1 + +# compile CXX with /usr/bin/c++ +CXX_DEFINES = + +CXX_INCLUDES = -I/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest/include -I/Users/graf/Documents/GitHub/qubic-core/core/build/_deps/googletest-src/googletest + +CXX_FLAGSarm64 = -std=c++20 -arch arm64 -DGTEST_HAS_PTHREAD=1 + +CXX_FLAGS = -std=c++20 -arch arm64 -DGTEST_HAS_PTHREAD=1 + diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/link.txt b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/link.txt new file mode 100644 index 000000000..f2594888d --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/link.txt @@ -0,0 +1,2 @@ +/usr/bin/ar qc ../../../lib/libgtest.a "CMakeFiles/gtest.dir/src/gtest-all.cc.o" +/usr/bin/ranlib ../../../lib/libgtest.a diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/progress.make b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/progress.make new file mode 100644 index 000000000..895faac22 --- /dev/null +++ b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/progress.make @@ -0,0 +1,3 @@ +CMAKE_PROGRESS_1 = 8 +CMAKE_PROGRESS_2 = 9 + diff --git a/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o b/build/_deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o new file mode 100644 index 0000000000000000000000000000000000000000..b4dfd27423d267b25102fa0319836548ef181355 GIT binary patch literal 1490560 zcmeFaeUx3-RVR2~U6u7pim)Xsv25$3l$W$h7D1#HurwyEk{`4b8zRScuoFivivl8} zNsLi?g^X6(a=%`UGoEf-z$}>bTKGLxSa}j_hUo?-^`^r};SFn<&^@#S!SE^2GdHwnC7`}b=j^tbQd#Kc3NIr^C=CqMM*Ck4wgynnwC@cP@~Q1v}S zs|DT_!24rn_2D=9)Tci6)TgbXiHRRR@G$$K|BOz7@*&fcnPcJqO-xLl_{`5e`RPw; zW*NV)#-w%>&3<1fu(2A_Ar)w8&-?|vr#^imACNM<=06Pe|Kn|;)|DR&{q->X^TfnY zoH*e!`uh972EO<=uFMMkE#X(B-_PC`@cY{h0pLp?UL6tM&;H5Us7A&q{hOHh$)le+ znrD{rd*VodXLO%ATBKiIGdxX9eCny6oOlx4GV#;@D2Vsl~z5a)& zya|7yKQ~9wF#hn5g_u8c`W?|4Ozubbd`~p?d{yS@11x}lFM`MU_gkRthf(;eA^V#C zUu;w^ESwxZvrw;0HREkpT8*t&&cqW}=GI2@X*_ba-Pm@e-FW;;wKBf%uISW*uihDT zs+Bcs#wyW1=11G3sY-O}A{zabxjV-@Rg|e#q7Bu`$eQ-+TlStBita|=Hb!wMldO*B zoAK5wV_CUq1IrmK!s*4)*<|(jJfB%N#d8`*^AI%~0skVOC4cSsd@n{h;J~uY#>AC{ zlRt6>eVRHSPyAVOEBjD6n{JCb?bna&ZAZ}$vR*R=-U^%P?X-QI46?tk5p{pWKXobL?*(pSLmRc&GcLeXPs8Ie8a&Al;9&shdxyX$m(UN=m;BQst+mgi%9G5uJmER=lJW)Q!A8R)b$rD4--CBW{@uE_ z{rcnNFX46KZg3V({_vSa%&owWAs^%1#}k)0FUS|@KXJW8+|U+b6~@TXiu}|fZBUPN zYffVP07vSfehV~{XV8IsPPiU7me1Spc;@rQ$}Bfe$iKuphqc5!iK}NxQ;uVATQo%) zVs7r^Sg~$_C%}u@TrAaFT(V9s%h(2G7iC%-uu|oP1T@I#0$vxofS+ytGTlV^fRB?U zw<%3_L>+rCG`UU3^zAS%UH1Pb$QsCU59&o#y-1g!fh51_#vZ%K@ z>Tqs>Z(tiloyGcK9oMRFn=w9U)hA{ISJW%WKG0J1(``{lcosaF-Uhh0M;k^!v$512T4~JBN2WKVQ8Sbc4;Z^LPpzAxZ-J}~wk z7M%P0@&V?H-qv?Jr7t7P_r>vCv%763sllFse1@I&pwJa#-0B7zBVDv>y7~O1%%luu zooY1Rc_|9CMOyfnx(43XM!t{mp1;h?_$lKfXWNu;YbqWE-f84=ocwzRGK}`9&Vl*r z0qBlO&^K&TWIXtJY$(XCWIEVyz$2U6UcQe;qkrmcLzX8Tn9?#2iZ*1B?+Yi-!9EG) zSa#v$*V>#H;AhdfVoO{YpU?Qaqz_of<9aBLeus2w0&nJfT$`}LTd=K1N5Xi~Hl_{2 z`N6W(p|n|H9|;|bb%Sj?s$*?5gG{6C2K%oB2lIXq9L}!@Mr4wf%_(}g#l+H_{&zJp z9mf4`otJlqF>QS^y6|r05%^;>q~DqN@fndz!_oWG35-D{$R*;%`CnZR{YV+gcPO7u zjCY<}SS>bh8+A=SBxqy1!hQzj_^vt}J>44#@Y*N-VsH9b)TzF=^7MGDvhOj#e?QVD z3z_~<<>~X0d;gJ;UeB>c8%MQ`_iOrDO}|gm2Qm5)0DjgnnTvt8qqX}K(^X@0GgzfJSELB67oY%76XCA?X(HJC>nSccCZz+bwD z{11MG&)V*8p0W4;JIl@jU&sLV+3=0`E57Q#CYTq}7Ultdb9^!PnBRs>vi1_pzm`ik z^_lIk%q;`(fh_6A(ZkTXzlL&b&;B@8gvq~V_m048-H+$I27SIHukjt*h5mcVc#UxK;GzpyiEi0u6mpu zuI!89&HiT3v-xx4=tcTj8PYy`#<=krabDpSKA-x3f;=J5RH9ReY*A)@?93ZaJ&M@N zO(OpTodQo4d}-J^l;M|PztgUuo`D|gT!Q}?;5a*^?)3H#jOTW#=F@H!y9;*f*!!K` z+E@)ayBTtJ3*_wTDbugit!?PSqp>Yd>OK^ z5)u{8lwlVi*$V|wGos{1XMV+Hg zepA*Fj})>ExaP7PQ-qiV+gGRPiCdTKumV$5h#axVpU;-c~!r*}c^H@GpzLQbD%< zfS0ZLc+Np)&yiN3U7(G(b^FT&-iTz57j67ikk{)WuQwnrwrZ+98C{@_)q>9gxdHuv zv4Ia#9f_W9>AZOkepd?q#Jt*g7Vn;0bLznl;qN}!u+_>J?%+H2mo`az&sK@UNdLjZ z#I^NVXIpo-sJ(L}w|5wWaI(2zTk{=ZBR-MY)?L)4y|d2j9mrYG2IX=&ggKK=SEhWP zz=>fS4aiS^Ihtx8KtB=BOs50gSe`aaf;v5)2i-r&erZo4AMeU-wqxYuT{*vreA0*Q zh!4I)`RHKOc|Y=~qh!4X>AOnlS}DD`lzwL^EppYt6S?Zr9v9+jczK)5+c_2^Anv4( zl_zbp=X!W%TP(-?@&6KZs`=x|j=&=Yd33#Y%f8%hdE=@74Lly!t`@s%FK~^mg>^Nq zbtRKw4kfxiK^p<~hQ<-M?){kx`ZNhW3fVexJM0*Y(B@z69M&M7?@0fWX`svgEfa!9dV2=n+(2e-b z`+9%KZ^y9lK_B8Pt{5eI`I&v9s<<+;WT+hqgf`xI@QH=g=^i9t6T9~T*G zc5;e-Ra@ixq-=fZ{R(DVB{A)7XJ2)5+qE1QWuNI+N8`a}pv_&d_XpJVegkc;v5Lm& zQJ3%t%Z~%?F7qY$j{Zgk^=w@F+N?WYe%Sd318^~)W5s@WnD5W^Y(a0AV#X`Nd^7mO zdHkLJ1pfN=NR;(D@i_-OAn?(|mD8{V4phQ?u=?%B7QWB)9Lu&Dj~|Zy&328=a*d*e zx@jYt!dm?=W#a(8=^-ym>+^fa!||4&cM->AJl3ZZEw@AR18i+`3Nh#_L-1kKnuE)B zd|IEaP8)rE2{DmE8(N0)gnGr#(WUolxHSf}Y5i)(N5cGiGvoJW>OuyYFK6p6w&#Pi zq%S~RIghz-B-`JhzJ$>`flpEJG{LV^cYGfb%w(41Xw;lU#hVWMOCKr2*vF-43wTw?67bFC1bYHl_6fC9u#InH^PwPfd~9rbYrxxN zS9xBs4&hLiQ=dvd=tFBh`$PY`x(a`2o8!oJ?RP`wb6u=3cIs>UHF@td&w*~E+W&%V zZ{)tSkC_Bq15G$SMyt0}H#G8fUGcjiKbv*LPbLFh+K2;jEk7L!u}9{68glGy?4&H$ z_P0bEdehJmyReQ9+VcH2jt^vl*!}Ro4gN0VTN_oZy`R#xrYvtL_#A!hVtz@xm|Ir1 z2mhJAv$e_h{FC>UyqPpHe~1%p0RH;9 z<9Wr8JYeo z{|=ioL2ngeu7?+tbef+6|rP(oKD~x_xVz z^*a%B#T+6m%sbHs$g_GvD{c3Qp+Mgz=P3L0h{h)#L3|1G#B6}(JHj5{=4@QhO@!5M z#8?*|kY7VS-l0sv&Q4COoq7&5NM9c6q>Z(i3~F}~dqJI^&znRZ^2;zJ9))-1e2X^` zuhH__GHCGq1L!g~Aa8U)-lhS0S55nPNa#zvn!ua1mS^r3h#?_N1nk+^x4w>7wd8z!cGq(E^=-tJfFL_zz?U^>7MWD zYF|4zKY`f$fc#11<6R$Kd#Y21{yXr)&ESW6HN!FOqCD#Kd|qwMRD%36jI@D#yesD? z$hW=?;s>L{uY;cD`SP9O6^}FfWcah+*|VbwX_V#1?ipji{7-sbxnIZj{_)N+;}z^V z=wWwE%*YmP1fG)rL}jB!`WJOwvP(s z1Wp}$40LZxN;uTZwzs7*+&7l#?C?}FC?EU261)S<7q)ARp74`fho{=)tAmwJT61yL zX_QADA6HG|)!^Bi`e3A!$j7@f?6iw~yesD?$hWZ=#BX~^zmi=3j`5kpm3=Y1+28CL zG|8SF4N1Q&AGFV&8{zru=qJa3`N`cn-gjed?{D&bE-kDv^1c1pUIg3E$oK_pJJ}0E zSk%SL8^gX3{jrv?7n0Y0r)vp#K3_|N$s5tNgB z?8D6XlyE$sP7c^>)y3N5p!^#4VBsBLzOdc%>FbrLv3~_VeJ1Lp@lZy~w1M)d10N*M zn?)Y-%P`V9;{15mmp_esyesD?$hR?j+w-Zzm3=Y1+28CLG|8SFy-2?-AGFV&C&Tlo z@(=UDe__0_zFpu`lYfwto+r>vx^7s1zFX+KKv z9RrWH89zYWfbqdg_29<=Mkiej-1RJgycb_>yu%pDb2x7_^!bs0&AxJ-oqHqL<~jVb zAM_myesk<%uUI=N?4^Kg7n*Gw=iJHonXyQLh1j9b_j9|Dzr^|qVbH&RNn_^a*aY9x z9vc+z_+I8umSQM6$HjN3M;8Ql_|AmG*n!81dHm~x;o>2_d=e`h#E*PFVWe0~vN(sq zWZQmUcOmA%^M*KE7;(J=I)P=Dv@`S_;0= zzWjaPe*enIl;Kv6t>ApyKJcLU`q2CPs+A~{;e~h{#(+G}@^j?TPO2)!M?dAlWpzE;4}4n9_BLrjxr4Tne>^;`QBF){Xn{9zU2DgN0su#k1FMj zat@U8IB&~&;zyOri4R-K^LTk*&-+w~>oa_a5EHlWD;<(VRK-_Bz`%&i) zU=B)I=OfV`DErAtcimK(rfKbap#KNtH;|8a!uFgIMS0Ze z`Mmng(Et4~(rM)5T_0@Z8(rQ`?U zlWSq~_xsOj7x{Qsrls(*%8#TLbV08Mc~pA`#yj`9(+1MWTeiOXOg-q;bQa}NC+nX+ z69)4PHgS@4tiSbsza*W8zcMI4uEOsbP#*HFvK=@~;yub34rv!@GbKy7D*eIzI@{J&YgofiKnokOod! zdUAljiah_G&z82|G=>ihSe8zZ<}=o*#h3EFR=PjB4D33aRpMBaz9`egD(i(gqyz9$vARq6_`3drkzJp}rXy|uAAHEr#R+!^D zx7kOV-}0<;T%L8Fzditu|2!b?(E)i649Gh)Aa8s?-rfOu62mCz>TicVU~^jNqVrmw zb#BYE&hPN-;$u3`L-|~G*nF3Ko%8an^Io2H?#r{z|IZJg%Rd>A_o)GSiO&6>9`Nq* z0eNq$F>v#R^U3B8=Un!Td6_-CdA!mYDXwXg#;luQ&j6p{D|C+iCygUP@AUQ`=wz^w zHd!cRgS7YAnMc3fV`wMPQSUvb#lHaT3{+rK#7I@Wl-<;3i?(!S` z`zVNC0f$lthupb@n95H0!TdXeyD!+z9O4#ZNXs1& z$ZO+Xi?-cAL%$PcI9|m#2G{ia?}ecM$vr3Blfl>@&xo@=_uY-~TsHP&VGqi~GX8*V z`->z_i2kr1?*ZVtzP+<^QR34A=GbtAeLVSn61XqK?Jq#PSGk|f&dXc*%5b13ag%)r zh|h4}D)DEmC3{x=&3*i~XKggLwHu8ap&yKkR5Wg2ZS=qkMiaIpbO0Q-@z>aQX63jK z&Gww|9ru1Fh=ufO(bOVn%skfRJVZPo+ke0GezbfqL>?RAo`RdP*Z&sWN3d$@40vaB zT_opO#@5N+N;%JheSKK_Jhg%6h{ZSm?ajhBO{AN8k3$o6iId^g?tVh{1egzZey!4H z7W+CvZ0CjT8n0!)5L5268F&UI8IYgsiKY&`7w__M2|GGTqpWJ03RUj)yz1u&ri%C+=i= zEIeahv^~o^oY81wFY`;cgjiF%{_z>yJBD+DVSh8?fgQ}73G7D{svrsrHOtpjrm%T3x7^tJfXb&a?}ytb!p*Umloc2Y2jU$7T$Gf z!%uKk9QkZ`O1|PM&x)@+E6(BkM;M=0;}bHj=k-n_8@F~HawV!BxBW?ZKJNgi9v@ZR z(r!FsDfN=)+LFZ;LXutaLDbv@tl|FOtKImuJ7sxg_-t5yS zm*`hAto^FGenE#6`n6aI{er&s@`iiesEaQ-8ARKo&>z&z`lIFcuGAk7Z}C9hAHcT$ z>;a6l7VhyIG+vZ7X`^3nB*-%u_M89?^UCuAjIaXT9V~@GJC^kJ^lRhpK<3%GiiMM( zfQ)?%GFHz@p9uXLC}Uwe61RfftUKFsi?bEG&c1`pEu01BILW$;jfrh}7{nx#tQt#QD^Sxp$pWcH$ zpe)N(Z;38faNdt&$vo^ayU6_3p77qUXTCHP<`v5!h8@b)wH)kh!a$6NYlmSEFUlR& za=qE0Q-EJ62by88vfO{evk_>sPd*Wz-&;Ql`^2A9*iM|GCxhL%dB)1#iTr3d;HS2i z^sQpJD4(3h+UImUG4skjVf+MtjDxIE0(a8E?~`x4w)l?ajgGwkDy)6x=OD7P4~=WF zA2{wKxF2IE+&>5ZME2x%13u?q6R=&j?`e5#wbC5xudNd=qfXE#j3uWv_#ysG3d`U< z&o28jDYR+1AKdzu=yN!evRUbT=gdy_HO|vkjyje!5ZGw*4BC+MDD@nswBgcb-zkn$ zZS;@2pa5UXb1Z(JXH^=HUuh9n%t4_8#*DuUCqIO{8p1tSW}nL##eo)>`?6k0ziMoF z9Oo^9e>TGVb=Upj7c<$=pbRM9>ubK#XeID0)5cFxbbg=UG4PD=w!QZQZ@xzyqGNDc zjp)L?dY*{B7VdevQmvfdM?Eu#IRaZv?&4!ToGoTL+#jX$XAbj+@_1*Zx@PXhXkP@M zh-IH}=PWoM$p4sw*}l=nv%2=AGy{Dydw{gEIVAcRvP66V>}jz4EhvMt``o+TNnae_ zm+D+kZ#~t)oq_Z8cMqsdLRr8###kj1T+s*h+qUhc&kI@PY_{^9jPCwW)%iAl9CzyE zD#mXP{aAzX+xhu5|2o2bFWDFlkmIMjTW4kwTX_Zj5%~)_Oc{Q<`#AE)k*{(bu)Q2F zt|4!@KwVH?KjvQEi*I(4y!G39t9u#jUr_j&o-!5g^8r&fP{{Ure4+!tembe4x*{s2D1?|v&#*d^O@ABh)Y=leP zSWemi4JZS`bFNb$=L&Wr`QKzg3SOC`>_=UZOWkP7=CJe|y1a)xYb()b&NKSMo6tVb zJ4>GrgNGCs9B?Y+OYFjxGdQ%Pcu%OEGhv$sTl%K`SvYwg_MZ34v1C`^Bit(rdiKYn zlos$I!(E4r1JV}f9n;v)fw?yF8g)jjXLmSetRu0gsQP@kiyG$#Ni&6kxh7{N%XFr^ z@pHuAh0gkZj8Dfp&X{j@!{PrwCQGn_bV8G=KHffWqXn~ zT_|&PXK*untIVT~ejQD?yJ-Mdo7=JEDe|wyKMMST z`h-c@EaMOSy-iD8{JF>s7V7aG`X=`vv5z6G^A+$aknPN)%pu=;xE`k2*~Z(_C*l7b zKK$m_!kxQLM%BTGvTy4AzUuEqPxF1IOZW~rvR_f9--qB>LPyialJi(uJx8~z!7Bn@ z#eTUsFWR>`#jz*t0zG(^>~V>AXa4p%oWUB>I1t`3&Py3&V4y@$CvX#U_#IGJ;#A-0fbLF@g3zZ)B$byOMBDHd&Z1d z7IiWm#rpgseN7)p8vPrjOROJW#``u1hiwyvl@)lqlwOT~IhBoTSw0QmzpK70e?VQ) z@t{#AL!~`8hf27Sp2lnJQ$cpdymR$kh<)CMdvS-S7>nc`fLZ*rzGa5-Pm2+^>qlmu z(|h%rh)2mdfj@*c9QSyrZ(Qhy*xs~^ruE7No`dR{&QU(JeP9of?v2}muT#HmhHI}r zZRMVle8t1~4?M=0Ja8)T2jlw9-_C8AbaDuDk@rO9v6i%pJmi<_hCU2k;GXYd9}rVj z8DRE=l_4CVkHWY3mhi9Z8)KfnZ{hxsyzkoO}fK>yP6;<8Ys)$G;C>56`t{u!wuJ@rdwJ zVNDHmH915Z)pRX+lfI|m)7WyQO1lvI`*-4Z4(A0-mbOpen=J9;xO*FSK|pU&7G*G$ z4`}Cb+)n?N+OVpl2%G+(_*hQQi=G94@g546;d|cqK>PSK=Y#6UOE_o1cW1Nq*F4X6 z+cowvhw}_$;)mgWeByaRzU>mey`eaeE?%aa-^IC{>9$55r=T4-h+gN--T>ajlX(9i zX%77*cPyKX23+a4%4*Uj?P9G_ZLcs+p+03D(>=%@#!9`sq+LJAFS0TwPbJn*8q@c| zxJj(vrN#Gk=>+W%KelVI1gCBoJNj!r_F#Hxkgd9;tZwwnb+%cMnO$cOI35*y0As-N zRK9J2F)$y9bSGbWyMR1meHXg`bxq!o2K-YV%3^+2-t&;~ zw9(wzN+?Sv$=@*EOQw5AIKbrkC)L7 z_rGxtBmW}4jUcvNVIKc-82fA<|Kfmo%(YJX`pe8?uA{IF=kn>-aqqRxW9DDlg}JQn zXw%rcIqsLUIZQmm_nDTT!*L8dYb|tbh%4It1GM)^*P#ukE`krjzGS1V$^*nE#2#Ka zc|YWCENiG`-d86(7O&gX4b}DIMLRz(`qg6ZfHvf_W(>R`w}KxX zU2Yuufj~u4WEACn0T$A!@1G6vim!9YRV?Nt1z*$0WfoCo!zgqgP zN!Q6Q;3cw1X=UH4qP}IkpdiP<kbsdby`+IudZld!P^G&{M#CFQ`lJUyEb>zmY&he^3 zE(e_;az%N>c!BXJp(Ey{&{cV*)rhb!1orTx>b=#vE~06~L-z68gw5CgZ{eHs%4^hB z(4(DZoqlxCH*AZx8Q0g?7Uv`SvyVOm=L7RvvuIEJ!O$N2hq+}{7x0>vOj6;)+ib-tf*rwn5uS-ce^eJ5%04eqJKa7_WivxW6mV%;Kl{{z%Vzo<5{y z7VANK1-&0TSxKSX}$ICEVN^F{8G<2_Z7wX)V`wwuhoF6Id43GtFS#Xcg>(&StA z+tMaW%?Go(Ea%U{l3zqmgJ;Cob7}GQTw3(COIsVn$J&u+ho{Xa$p`(3Ut!LHZe>00 zX@TeYZL9ECr3@InZb9A<-$uuo{3Lw>u57O`7TtXQUG?}~Pn`za$-nVh?qi`ZOO!3s z!N%5T8pblGiO}|XaE)*9XMsO&&@ht;Z!EzU!H*_1y3Fe!ywic(T^rfc@|6XxjRId}&LYUr8OG&ccpva7XyiJ*og%@v9Y|3@A(kVEBY}vY@8c5PGO$E85^g5 zLvG4kMZCjgxy8=C+`MKP=;Wv9P>d1d4|bNidUSl>G27=2T@>Lv1=haA+MdmIjo`;J z&@biq9P{$~#klsha@?d4Q}F9^h&Lf7DEnK9U%JHl0^{`D-x>i&?)AoZn=j7RqN!CH zdxi{?ef3qWr?y`&??HEGuHQ`Cpx2vfyTSF-bvP3e)=#ro+pZ8(BON6kemLsz9&20Q zu8u`hJXdY!pK$dRMi zTL^lO#bND)aH=@B@RF|0f}UX?do7yhz6eqZPyXmW((A#2WacxPiV zhBmBxKAe&M9%ScSb+lx$wfIJ3E`89LMGru5UCdh5Nf} zM?${|k9!mOo@JS~XRB}c(+2Z>qYdWMw84DZ;1H(468NA2+v40{Klm(XJYhSvqp)w3 zv3A2};pC6uTuoRMOE8}t&CEF+DWJ}o=Abg20j|ce?)+U^7_Gaof zs4K!bs<5|^>ye04V;zg@%hQ|-;9cUtGi1$KtQ{oh)@@L!OW|kVaU) zV;^|eGkuSdik#8v%wdm~JP$*DfbR0V9`}*n9r7t#xtD1avcl?v54i@^VLGX+oTx)i zs2-DiM_)-hI@Gj-I~B z4)w7PMji2?T)HsV+_)t>!rWXc79f3rjZn~?UCxIAXK~ zkZ&|+d)$*(ZqN4VW$Qv{gLMp#AU|_Fg8a(I3x*#XXM(oAhrh{V6wFpuf>aYyt56n6#hwhvznV z=5rw3>m|AaW`eRke<-FkS0r0rcK;cn5XXs7Kr(F=O%Fo2Nx)U>2 zm*cDFbU~B++%3ja&i;{~4W8A>_ANE?c#(ZW4P~-fPMU4rR`IaGaQ)LZKJ!+VZx!F@%7SYo5B+AWL$^q$`v%h-p>jV1T4__4-)apZIIqn}fSHhg)MBW_jTCi{MPmQ3kw`O>cv zy?hKz#sGZCGvs5H(jE{uM`#;IyqkdhhcRHk_$+%X^_jR6Husp*4z@Tq+pso0pJ(Mc zm$)AvJVBUJ9=yP3KSpeqa4Oh$C3v9!L3S>o3#>fu#9Okw8?zSr$nVeDoMU?}^o?hL zr9RsBa@ymT(Hl6WMsJciC#m)IQzJa-pmGVW|6KW$9*z&68tlD_QHyj`WTJ2ZbsDZi%qHNcn-@|`}a zz=e;5-%MXHuieGHTEK~IG2KL(&lq!@9S}S-x$NH?9ivL1Ip2Aiz`K47?#~e}?ja-Z zw7YlAh^^<+)~3K$T#ggB7-#Xio8W7F08Xpx==zkT389ayi|9Z&UGZJiAlQ z$CJOUahvYa@|<-3mc@&{Q|&t6hWb3SA%M?S@p@h5g5zn%LX0QPpE93-e6px=C52p} z-C}VG`qj|qubp!DK@kqi+5UgOkG{7EpK3AM=V8mdKzaCAvhVt@#qf;~#VoHm-A zQ*<(i_4sG3&#GCzlz~nm zeZH0Qjq?b$YbRPNt{pR;5zfi*Ti@hK+GU*k1-*;2Y`{6=2jHFQZ}b1z7SGWY?nRi4 z3Vg(V^Sl&eZiP4&bXpI2^z((Mo?$wsakcLk{@^`mIghp!zQx#{P@0}We&CUj<9}^7 z9`dU1jh_CmH7(zU2c90D_&q2mXT2~_hy(o*#-jO*ve`cUa_l1kZ?g>iEbws=F6$M} z=E0t1*}-&v`8w$Q@>S6J{ejlBY-^b6$yDEN0xo;akd)cV88!#6#9aETloNqUU7!~ys zed^XhoYR9pNSadzoq~BDQN zn;^HV=r8|hdnC7>`rsv=p}h6{2P@-GY>ifJdVDPU>0hr_)_i>gzdP{@fBbB!YlS>( z>G|b8$W+?P)t2ypz#$CSrVE6b+!`&=UWRVGEU{wv?1;>DyJ^K3LY96`T7@m2PESKjD_)C6?u$ZV>o-L+@-n>##{9{@U0z;}?I3_DwT> z`G=DD)CXJm9mRNU&&CV%{mP4X+{Lkz@dFL-e({d`D$(U?BDTB{b4=E9j)Fd%2x0f`r_982h6<{ zZM!Ava17w9Uj9RiA#n~PZzpJ9eR6Iu-pSe@Z8?^$Aperu4E#<2^SwREx#)SB{VLd) zOW;w4b2W9A$NkCgUfe(T87iIpAUI1AX(fQH=H6z&q>-yFu@gJpXRxlL30j<_7I) zdoItD>Z<7J?RsC4+8+C!)4gz3M|^ER&oCeTJnNflb;cQ1;r6w zSJ-^O>G3PP&x&b3w->S2ya-=m5%w2hlSjGkPkZRy!}^^X;6uGm+Y5Fo>@3(?SZBul zrs3`u*jx^~BmOJd zCK(?r!$$=e%Z~r}w`u%CJJ&w`<8RyX=Qz-}<`|xT3-0&w>vx2?oSe9Z@%@~@daLI5 zu*~nz;SQhA73TN0acSq8#~1b9uJil5WPCesW_)qK_AQj%H^x@=@{O?-n})i%AXA;Lr~e~yH^_Rf#dv*98O^xpOXfpy%>!jP zU$}QV(X_=h`turu&jH#QPr81KpVbASuW${ayC?{h(>%J+SEZ)MoGuvKUj=0T7_ zc3zRRQD1nTchuq8-EUruBMFZ_JEZhyn}heivQ6R&+RC?De7!;)r7Lk*YF_Xi=e*EG z=DKUMTa3Bc8kWf^_DA`qa8`u)R7M>AkQVs(^>dneel47RS{9#1kzVDpgjeW)34iX} zmN~+9D05K$yviftJ^12d;2Dmi&=h^3%;bHLRrUvKbsm2kW0nPfcdC%v{+lj^Z+dlC z<8INdh+p7rXanEft#6t6lI-cio|#907xwR_k45u56V3gt$wVah^(Js1fBnczb28s^ zn>>bj>C*Pj_QlRU0-k1Hu|Gu2 z;{4qy{qD4tZ8kPQw?2sTtpR58P|zXiL!isBo=s^CT>#8n&m0bAlEdf=>Jq=?e$Bfd z{Nu_Jmmi`|YHZTIe4E0aV3#%;2;QI{;dM%N{%t9J2I&;EqVCCXIH%ZJJNY9ls%-W$$(cyn4G(-o|oA$V+!X zRt`nKE4Y=%vKGo@ZD4(u<$73?Olo1C*rk`11Qh2mPq(eC`ZNq1}&H-T-(=F+`kls^Bg$y zW$&ddXvcbG+mCvR{l+?SM|3st!WwQz+5|oYS-OI3FUa*c-`hwVQpOK`!1$fDJKkYm zX6J@%FG<-RrtYT>XCJs;>+wbT<=e^k zU79?C?Gb!@_=~*1nKIVC2O4Y+oLT1j2>3_*gBW`q8?X(g!(JY_mxHo_HVEeHN_$7x z7s?DT8_ILvpOIU!|8ItN^)PnCN9?qtB^%qpX1v?@N99V4f82U!t#F)DPu5nVb*g@Eg=-Th9cted?mZ*?pu%Zp&P8FzfkVInmSb z-8tvZz%LksAMq;o>$m^I7cRcExBbm3_K@AR`d{GO+M4IrM*s7JYexR)j{82p_D-aa zkMRxy`0rJGw>kOM7x9VM{M*c<)GPJyJ%gjrg?1jiz9;Mvv!e;hoq!dfe~9voqZO8@I+AJnKIV`r@0?ng5DCvlj(7^n-mp z4f#G@e;j3U9Z&g08wdOkS)HGc7Tt+_o&^&d4d2luEa{`#x_#gO1&?w+;4I=u*L)XG zH;*Gw))6N=mrva#Hrz62YiTnSYy{BudhUo1`V#u#Yv=a9q-)*icP2-${`~>cz~}ii zo@IX6Tq4buD(mGi<*m?pwa__hPw0&?BAmrKzg3CvRG!>~y$(0Q??J2pWA0-GzY+5c z7<4pz&}I!Wf_ktmk~)3MaOQ|GQ~0D6__Y3RF6Kq=3vDINCuQ4B^XCE6`pEp`Zsq&C zOLJt8=I<%yTiaPXyR{trPid!dd$6?|AH#gWzRJ~lhk(azm)6-uo91KuuIy#08{d9~ zeJS<+VS>MXbGuEI8pM717i$1KK$(3(`Vvci&+-Ho;zVpq;;D!gAeN6cv236I2Uv!A!AL08H%bT7XrEVRHJ<=O@BspA@<`Ed#QP@pkv zyTI3*XRHkC)7B$QA2ZVVB!9}?UAP56et33`7;>8&wL-V-=i8=ewce>s$4U6CEIwR$nWD?P2_g*hkV zgS+~U>AJ$ta9-TAcIw{U{Ptx&uaZ9+p899Iig|21tx4RlU?Zf@{7Cjq^c1jYx5<4q zfMNIZa1UbIK#Vigm${wJB8@!XF7cCg>WB~HJ^La!Pa}=Ia#`jHt+XuRNxBw(1J0r+ zia0lhfJ^_krcI`syzEiWKp*oPS3*}jlmDx?k9VlM<*B9clkZ(cGp40C? zxisT*{=LXX_ugnqy8AIO+Dg9CcY6sIoV)^TqbH2kLOykv$HDsEkBg%Z#w??iqfIi2 zF^Qvj+&zmuA!>)jVV;!N?=YT)J^X<8ptps6oQUIG{?;yKE!M z(Hmh$zY}*E4^Opgl?(X(^87by;IG?(`#W*R?|SrQ9p=yo{PlHF=cWqsZ%53fQMthW zQyz@G6Z3voG(WN)zp!IR*5P*#%8j7hWNu%w?9e)#i-UY`xqRU>lOZS*pq z>_=|K?^2!f&Y2L~&eP;k|32IeoWHmB(sKtgd6xiZ>lbk{T;$o&+i(hHeq8liuJ_4T zJ|?-89x3xP`HMU+bOGNLc&+a2jc1*%ujk()rq68pKHS-dwFS_Z_M6#2vfrSF@x*wu z9m;aQo=G~>_BuTo_>=f>-mtH<&A6{3vjwaS;K`Vw9)A~3=HDr%A0#&7e!UOYr3>wr zV6Z(~&-3&6dTC3%q`&Axz|qCX8nlnY-O5Hkv6nDMjCPD4u^!_V%#vE2V9J; zcE6s}#kdK6-~pi><|uwM+nVxA^3i{@=e>OOWu+Y2B0S1)j}LKRK5c@+Jv9xxhh*h7 zgG->3tsB_Ae1)}xdN`ASb+Wzi5sGUE;73_IKz~+TJCOOrd9?JmA%!X^Om(ySzhZV`fP1C9bA(;zr@&6W2461H_phuQkM?$A%AalAs$8>dF}@K zFrMP!(Z=*?qlf5k*N*6Kmp0tX?OQ(Ew0=qZ+BdG5l;bJQ#>0I&oOFLH=2LOL(q4qE zc=BCm7GT2&Ps48e2Itb%+KbRNVlP6EioLi)?ZsV??<3UZfzIqR_0m%IBHxJ(fI6A2 zs5T+rMLu73O7xC=9f0tu2Mf01Gq4pKK|cunp_6(p{a4UQ-+)g1OU%F7U9c5*P$$Ax zM7e^k2si_6Ma&I>t1yk%tj}hb1v~x6VJm+0%wo6SRvZQH-Zs3wl$O0!&bAaDb7|kl z^ciPAstzWO{4@OI*^P_g8OnT{?8nA6tYg&kHABc)iD5NSpK_x;8-4|MeSSjsMm3SP z`=y|_;A=Zs)4;u4E=_nniNteeJ95l?nq%hE93!7jJZx>3_pCAYW_|H} zu)M-LneQv`&2Rybt;K@1I3u6`D)j)rKPf2AXc2&#Ja|l_xbKXN7|R|dal!&`g~sF?akTZ zT8#7qWm?GZ?S||G4hj5D{*ngG#vQ7!?wG-SEQRzYNq^!>M&DgQrtn)n?RXRHkVmf^ zS{wEr3fvtkb9MyyP7xeIIy7*MYq(y}r0de=LLkelm@D2A#vPkv(;BJ~rHAX7`dJ&L`uarBP4#7wc5s z-{<@ioq@Qy=w6q$xB+?fZwX`e_uLu2&R?-GjvuQH}! zew6r%pmhuM=Qret2lHkYTz%Pt-oS!>v*RdW8{G(y;eg$ndFm_=cf(QEO&$k(0lDBMZGI}gAE_SYno$NObk$=*WHi*{~-UNTSe zvxwQ;M4yFTkXISKw2x&zZV?BO2b6P=7xf9Gb?#H%oUt6r}@W37GWm9E%yjOtyGWOm8Z%J5yBj1TapX@tPo}Ocn8G;Ym z;h2&~2zw5A8lOO}JZ$ge-n@}O59U#x3tyo>j42p>`HuZ&I>asWabeyy$-5Qi9^n<_ zW2t-(<47E%b?`$lhUM~p--PX#KpDj3#^=w+6Mr^nY)*H#&dh>lHor(?p6xu-eH?o! zPm>Nyj*rR*gJ*Jrdg?S_%_^Vv!Du)gXJagKA$$w0208y8?l}W3LSLdOjETrp z+b8&5wIeuI=F7pq6IsghxW_a;dOOEbWon@;c#3I$>ySEPyIX7M{U$~xu!i0K5!0P85w(=Ei6PscdJ2p{oF_02QCf%xy)`X{j8xBnb_ z1?Si;wo-^?Hp1FO755^YdPm@UzDGZDee_u9x14vxI7nX}(Y!}64}95&H2q{lP?KALqZSM zZPwxEf_BozJ=(@SXk#L8gJt+${DSVgW}Yv>wDC70{yecd#2w0K2@Cy0teE|ly_wcO=ld4Q z!futauvhIq3fh2@N7?S_2DvBskWGbrl?Rkn87&}tS(b9w(}L$7{kVrU_nepPJ-_FY zI({+Q5vI*so5y{zdgVPStNoW}?#rbtDd`E*SIg-VmRZs+?=Ecvu|3xHx);yEE?5;o zKRCN$88}Ps4;WC#bW=1Oc*g3AF4R2Y#eCT(yA$O~dkRS-`hv7y=x0cQ5MK0)VY z`x*z(!*uJnd@MjS?<-gzvT?v%uzksa-fn-vGI-ngk&g=eQfhBm9vx7Y^eE7D2^_?R z1&zhuL>(DNIm6+2w*+6tWqWb2U!kqI-&Z;Z+lpf)l9}h6JK;TrlTx?A-YVE>rM{4^#Hq?y0DMk^Yx%IzJA{co zcY=TK#+w4nWEb3t{$r=UZ!%mDf*C zy+%KhGHecVhxcUC2bDWH!A}8q$9G-w(M?z@tzdnd-ysct59>3w!!vA*<(#W;nc;UR zWt^~P!S#MST=@7Dxwe@Q(h6-}Wo?3uuqZz(+o zjq#24z+bq-2Q=S_*v&QZ=l@UX*(YP(>^y%HbhPo}KF8|&4$L3YZ+NkmmzMo$K@#>+*aG=R9rZ2z$IGkM=rX^`K+ersXj{#5)(VbdC4Hg>*sx zI9kyLqyA6!2ffF>1pnB{K!+2*lgjlbJ7<#;2kJ+_vAfnRK1Q8tIz5M*$7^iuI>@fU zHkROr{s$XDeG~K@GVU_(7sl^~HonKSKIlivUd#;->=15sj`oKmmoROxi zU?v)^4jSi{LuCXPVUx+WY zCdv0(B?e(I`tCCWOwN@MV+duz6WE)9xrKJh?+C~GRgUcn{-8?)UxfIjph=MB_-@J3 zXlhL*n!l@6nObuog6&h8LL3jiUiGY%*$x^Hp`7*kYU*=MsS>+b7~1rn zwbn0`p}eq|HTo;(KWvQIN49PK5ZdYZmHT`>d&|vV8G$Xc^V!F*yo$CCR8GOB9YJ^| z@KC|lXcYY&<3ZZrOP0eoZ6ME<{%+3zm`nQI4Hpa5xng5YUXV2$rjRHXg95M z2Q>6+atZib#(?L2w2YKjKKHaHpUe3)jvL^Zd?PJTCyO~z4p5F##vzs^a*8lLPxPI& z2mgP&`$IiI-jR0EH(Lkd7}zt{Mar_J(2s6zlM|lZk$(<$IQz`K*>>kWbnnBG*T8)f z$gkqgl<{yD) zvDZ@MF26~T)?i0D{mt*;3jHCM(;D}b;d|E@pF_~M6?}h+@4^@ZF6I`W>2uAYEd9(# z{jT7Lo!>V$6z;aAoJZMg4zb=XtgE>2tf|b?-C=#t)^d5yhjj_BqTgg-UHg-E=yL&= zzrZ-q9>BPVvrOnqx;W1BKG}VxhQGo0`~MhY<@>UqW@FMP3%lGae>?W~-T|Kiv9N<2 zn|vL^_N`Y}0l)h>24RnU1!kr`^Po8Icway{7PMB@Pqvd`FZHMRWL96Ur6^K{A5&d6B|0(udEim?*<8$g_53zr? zmE1kPfM0zd+Uj9FfbYN~4?@p%tgSG=-gV^?ec@R5-E?oy?wkBD_vX3$gQc|Le*pOy z*Ng4OJ3~JjAHBkThQtr@SL{HxpYGBA*t$vMqoF^$q6I74+a3DR0u1TTuJMK5ZowaA zLVxU?@RIch<)uHsAwhr6#fnP|Ty_wbFz3=80f*mzSCED0u!mjffj#G(YsLedTcrJ^ zjk?Bz`YpgBPK+z!e5#af>0Wj2(PsRDbF8KPV|n(A`#D)JE6X{BxJXnPU%)x0g*M7n zFh^LX4S4npewVSd_tt-326q{B{u|+)07KftTfis> zH!GD-{<`#c^$KmRUb(GfZmyC)jYk8IZ@ls_cs$Hw<$Lh^k038O2p;q@c7ME&^fvy- zK8K9QdCx3i%uSBVxdFsmE>l;-t`a+eYeJX3cWwUdc;@q{UGUA!k(D9)qod|oQ3WCODSM*&cL^ym-o0wxA0zYzCRjV}Jrv%vKf+h0k3g$*e?8Yn3Ww#apT@rnCqIt! z3?IXJhHwXn<%hDmx3GSdvEFVTmo=Nv{7|#8sN;%uUw<9Hr-t>G+*32a?pf0Ae>>y0 z6n$gpoOOTr#cZ8Q%bCvw-js8*Yfz5g^rX+VQrT$T%4Mzz9_;IKn~XJ#4KK4MutYx! zxHbCy#3jcTvUsUIbk`k=Sr1zZHt;paV!HkOY<`jWdU3G5WsMSZxiA-epII)ZEgR-I zEDSfYB&9g_>WrO_8TxB)~jPpc^CA7wnexGn|dZiTfDP2f&C=5 z|3U6k4S0M%$}YkNlJc;9%03KnIj!G=6aNflh-U#8&{FDy?|WUe2Op!U{mpPeJ;4QS z2`<_`zrRboTdus-wf+>&y}LdTcDXF`k{toZLH@;CGTz^c`G$Debs6`lhx zF22r!D#9pJ}wMwr!&8zfF|oI9K06Uw7LT`sfzRFnHu!`h~D5 z^0vSO@J*zSj1S@jK2Buu@vD;Iz`^eg;QW6p#;Lqw;LZFz46@yN6T0gAIL(JB7g?k6oE-P!5f}MjwW-Ej@&^oh250P>%k%JRfA8=<*jb z*@b=RgoXSHV-{$Sf0yVt)p2HfRUYfj>f>%c!uy*N-hWE|bNw(J{tx7Xr=)$MF?iDG z7h)su{Who_T*n-Ud5`DFYqJ_d%gPd`F61cuZ^Eg3e*Bk(KKlb~E7zOGT@iOf7tIFw z$uZ;|M~I7s*hiM{#t!eLf4JiaF%q%4t^TX%X9l|g*n4u=v}qwrJBhnbkT~@wQ4TWQ zV90n!)z_?V{DwE{Nt^iAhjm}@GTCG z=mL4WePYYr)?|>W7~`|lm9({5)6oUnPeVT00hqMeM%KYUfGizZ7tL3XLhe9^CX0}@ z;916o3$UQ~2&)%YrsisxdpiOiJew!5MikZvU|}te{FJ;HO&uew7fZ0fV*(30-|ic- zIpSlq1>1@6P;XgV+>IM;j~@CH*0*8b)xw&65B*?UeQWWquJ@Z|UXd=KC7;Fa0E}IT`qt^WVRxEzkRz zn$y4)dX;s!Zf)hqzD_ctte*xoY=Xzx<(f7)57+bVmShKJ;{xf8( zU-K#OC3yohx*D61cU5z59Lvsumq(8V8Wh%lK#xK8X231rnWC<(C38*`>UZe}eh=0t zV)22zA9xycp2ohqFF{T;XF(_ULBccei~9PG_dd^TG>&1bo{v?~pTTb&weLQc>tfsh z3wKyrFw~gl(@E1M;U$d;(5W2o5_-T34ndKHvei*UNaBj7LkArJvrR_MJmGSHM zUPjwKFwx~peH+E;W$GrG`vTkr|wPVn#@g>#srTzhUe!W~=u_7ltU-Vgc? zL$>}-KGqmI>#$9`!(H0zRGx<1dpOV$?*!&2vbA{lQkVH%BH|au3cTgh`fTmfZ|A-j z$^+_f*a4j%ETt2z!?=#Dsh4qjx+4B0?G?7kzYIS5`v>$#SVwR@BDnId6WC2fJbhZ9 zX?tQ`>;AN@@;#$tkPpJo4(CM6OFreP_TSqdl&zL;x~t5C5?7?Y*&a<9A3!gP?M>T* z`Gmng@+oB=bh(@*rp^_bV?G!3a(A$;dNq7rh%3~q*st|Y@KC-^Epdg$W~_N_e&clG z&NpyI>kZ=egz8az%U05`J%eo2_s#k{XY3v4PP6_|Ikz>Q`9F`nQ5}6^>-$`qaGUD) zwHsl*^S`3+516>q?2YJ8d;b*ow7-FG>S7)6F2oH^G|H0Y{D8c%A+pQ zt!bniw+p^pQ^egH=+DRTw^8ycbzoezf=KR-Av!`@BKrI^l7A)|;FUefiXS zoj1U(6@M(O5#AE=+VK!*d#m_+SVN>=me~$i7ZpDZ>zkO1LFVzhhaBr>Tq=ui9!fsG zT}V4!Xohb9hO!UxH;0Sw1BS96!8aX(oNmUN7k{Kw_7*MsfWtxa;s*+4lV4Om{6*+V zmC=lIu&w0zasFmFB)_2fzksreZ_?1bMyc#Gn*R*S#sS~tq~@J0mHn*de->rCs!vX6 z-icD#r!@a5l&vY;gb^11%lRDKnDW2`wY*h~lBy)Eo* ztlx(D5c0o({B~pLLbf*eND1y^+TLTpp`m?yNb?>ll|8Kahf&t(d%x!0Un+Z0^ADn| z@zlJV3uSNCvNxlw@y=S!TU&y= zI^@%5Ebs%yn*2cBQQ!y7r;J~$R|~O@X5$f&6&ib9fDN}$jeh#qNB;i5T&&KI{}Ofj z9QR#Ae_;*ruFTh~!%oK@04opOb~*X-i+5!2(ci0$YuKJ^;-2lHF2OJ0gSGm8eCX%p zdyM^EE#iBDW`s{0HS_zFzO>K0PBs6Ed43;pg0U0Z0x?S~BjXGIfv^|B>o;(?L5Ji9 zKLnfo1|4qDVKV668*=h(N(ZN>?VEKLqlSL}J^1Lq#~3qh4d_m!$A2&A>HN%8(YIT7 zf%}ZTZ`Z7E`D!cP@>QHg#qW`?Mx)!e{)VlG-5+%48QLs=0-FMMV(Yg9t(vn)!{|~62z_#J~Bk|*X9{Lxu-;6>;O#mTVd)q za02{V@k~4X|4;{ZXJ(T-n-lPI`_kE+nceDa&O+H0?9Q}1gthBx)>p3W^pGdCS-}oX zCsEhYi8fXbc~Uo4TNSocqGKgAvG>*n=pf}4j^R?{xW<)WcP&;rn@eflQR&~yUJO@0 z#8LA4iMA^^Xxs9v?aQ<8i_#?6&g%-ca}pm2c3(`pFWl2+bI8t3VlFLUy$5@Wu~!K+ z#au#K_Qt~2&Clr-=1^n69E!If7O@#Ih9mezTmbu!2hJhjB6Fy#bEt+i=g@3O^Io3o znM2vxyQ`f;z|;8M^M;>88O*DlL+tmBIV3#yT|0*wOU$8qzCO*h<3Z~~w(p2M&@}nK zc3kMO19HIb?vQUqz~AF~K(i6}p69x_R%iSp`V2COvCvB>H;eZp@NwUR7_F5_kjH1p z7x@+0;(S1nEiP^4Wo)z#_lOi?w`LQ^Jhy{8`A_1TsNG@2&$0guI8%P4ke`VE&yNgK z&qUbEg0)7_gt&13EOFjh34h$P2D~rwJ1{TV-U*Z!J!LYVvRvCGZYgCk%4Gf2=Y^Ax zU=PHPVN@n051*hS^AA9p9}t&R5tQ!sE4Z+8w!V;cS;yU3JIR*ddcz z_A2a<%)W84uL2Iv?#S#L%n_Mim|$dTblWY^ZR_!k!d2LVyD6;oo!Gj!aJMGrBiC0W=rVq9sDfYeT>Hf1+yk+J_b;|= z-3|Bl^WA4Lr_a59=U!W@4EuX47r5_|?`hu_KB!Jzm5C@>%jBmkRAU8G9Ce#+tO;2V{M_4>Z8v&sEod;fs2o zr_jLrhy{2~KDWBsSLY)Z%3h6pw!S*~Y~OsM{*ykz7Ae40`7CP_I%X{A_itcrRM)C- z4!05LN&jl$htUXVf1h&fN;?N4&rIyz9GQm-cW&q_V^y>gm|tXZ)bDf z4)0o6yjkHwM+)543K#c82wdn&;)*k$0WR>6cJUtP?D8_i-P%Ncq0Oy%o1~Tf;eM3) z9?rfH9`g#{L)sDiCB*9rvBVHR#oo&L))~g_NDt{Je75L7GzB<$oX28>hz}O>(O0&| zwVYP{@fmBM@h-x&v^`&Y>=0{vXaj2_9mYGWxJ%_H;@< zyOLW@*IW6Y z(Rpa)Ba{!%=pV{e{?n5}PwZ9hux*}Mc_}-$8roR04tPxJfPb*g+Of7%;LUG~(KnQN zx<3Lf857TOW_i|?`slC3h%p9Om+RncYd)r+b=Ek-ca*1BrX%I$@s|&hymsd);w9q|?J0PTWV-;kh5k zG0ZdkK7%=kde{@)$WK_2HQWjEo;IvV0ynLg0> z7T0N_?|diUl63KRuBG`iHs$zyqHx%T!IEc%7vlCm203Eqbu@0@;C!!OyrB1}Ut-8N z%Ck7&SU8ggTgBGx%_id-BJ^@NV+a|B=fZl<_W~a|m$<~e!|%a+;z>9oI3IZ?#(m7t zTS7C+D!^nu*X^==lj)FKoS%3{+u#F)L)|bZw!+A9t}9W#Nj}blr*EtdYdWPes4Htv zf=718alY5UpA&3$x6L%W6QYj_v~hII&IdwQgO;5Gz>)nal&@0CrryormaSD6-lbST>*VyD9vL+smlI->z} zw2b9`^k)Jzm;epP_eKMa(T*#r+udL-oJ(i>9NWTKB*&kG&-V%D zI?JY<>jwhf^4to~uo>sKor*f!emI)nb|@;IPhAEc>lJwYZCI8zG;C#@Uv~Xg8W4Y> z;denl{5bZ}z04={eH*x!8RJjC@=)c9$V0*qKNIpw_yIBtGV%Cvwf{9wc%)T-QDtAg zpV7~E@)l`p{8ZqB5MoA7(&JBe%7=J&J}_`K8x zzbpBs`V9WK>OLzkearfM(-M6y&9AGazsU@tIsJXk3D`1B^LgN%8jak(6WIfOo!yH8 z`5Vry27mDhy}RUb=n}?M%&rRe0_ePBe!S(1%|qUQZ1)}~>T7c^j-9tAKQ!y%EH&|z zbbLh4%AQ8P;I6Wrw!@ie>=Qzt+6~;pWbdc*wyj;r0=Z|SSBH*(-AXu5=vhj`*Y3b# zoBuC+ZyzMraoh>Mp2iS803jOu5JeK78p{9((=Zp-a%Q<&4BV2z4|}mDWKj}G0`%BR z%2*!{!kxJ~ginNfBSe|EAcTS@w+qO`u36EBd>7|Q ztZmACh`2oJJN{jKKkmEXAbhQO;JjAcsiy0X7zytbVkhI)?SWt&ga>?KASCW zbiR5z*Y`Y5sVi%F1)u#Gw`h0SIq2+HeuI8^E}aQ=lYe->MTQ5`JNk~fCCt6{j&IBd z+~@rnULr5J{}{4IXhr#U3CY#i5AtsL|G2iWV>OT~skTjMo*r#GX|*e3(eG-;INU*q|L#A#Ks1vIp= z%W)jZM~?^c@w@2(Y{P2c6A#fLAhY=_V+U|b&r)%HWb(q~&NZJW`DN&`=;t+`Cs}B_ z$Vtp6eRM74qfNxTViP?c_$i`OA-}*a%U93OcBK6z=j%1}>`>_7V$Khx1zo@00wE@s&Ah= zZs$yZCe)kJ#*B`LZ93We`e$!vKDM2DdIElqWV)BJkSh|`jlSZ2jx59e;T??hg$VBO zg@9jAJfLUl@%uEs^BaGu_Ir`9$?Jjx^454aG2ky^j$}KQ=YQu%@pv{M|A6aS?wp($ zbm!zM@0%pPtG#bBlJNujk#u7JT(CPU*pHxNb5Nf(oIZdtA#+Aa->2yN2#-QJ&flzS zP@Xpwc0H^^$@3Vl7>2=_@^tRB}=c2|-s$!+1x9m*EAgZxE& zMSs~D=Gvgcjj(;z2FRO1 zeKZh1(wsU7V9-Y1k?i;*S@S78XKOy%N7;LUXQj4e{c%=|sQBnv~fQl2_&VQky>JaGxlB zt&|aj!MBkVmUXi#- zo=YbViK0MY^`P7>H@Ys`^^}M~1E@uQWrkH2pK-a=KC?A$* z(xHWOaaxE$RTv>JzvC<%$;)zjE=~*Q;8SZ=_|WeQ}C zz=lkT(lKXAI_9D%9kNQ&ArGT;$X%v4+i_pc)Jysja+&$EzSL*-1=GTMVBU9qed)_; zna!v>*44I+(T0xoylrE|v2_V+9HM7Fg7|}@uwmBgexImi z|5{z+5H#Iv=~{(*_AvMOJ2cv{zMc7pK~J_>=9cBo1^ixF_ziJZ|67?>4bJVuSZmJp z3Ojd?dK>qg+8O}oap`mQ%i1_Foq;w>&?WH?T?^09uil6CZJpC^T0zginCOcQgsx%d z6qz1LABsJT&k*v3_xBk-%s;K~{%qA~+yfUG_h=)_@n?5#q|j05r|yix?+ke--&C$x zy{7NV`XywaeFaCbS7Q9bwjnaE~1PUXW9`Pi`CR#>YhWtU^X3 z)^dvF1%}#(d^THgS(xi^4n6vXFd6sBHHHQFv^-4TXMH%~o*>%(QXcaIX}~>R#qypf z;RgcGT@M|yIu-8Z+>SFb>0dquy>AQS z^C{~>B`x4o?D%3y?K`hJNSR6uAK&&CZ?%kNy_;@3+B+p9eiS&O%%WVniFkX5;;b9q4Gh zi-R3gL zY2uMoaOXxfk@3X$S&ZS4yAfkCT^Zm0Tlmf5_XWgoW9*HLBVWh)ogphCo`B65^!eN8 zegJsj9M$#|3@p?c3+1 z-_TCWCv8-^3vJkD}_n|}_vd=oq*G(vsReBjzZAByl0_`=S_ zsf~kQz(+kjr$XjJ-Xky!cq$+06EgmgXYdy53hB!G%wyw#&(;@wMqc6^&)czYav+`R z+`D`Od?VPuTwRCwy~@Q!$PD^rUI9&7X#0*^k}EqmCs*FNDY-H_l3e-z;pED@D#?}K zhTQl(_?7peB<~F4U)K6T*J!L^|AN`D)O9MG(Qoa1OfZdS1&wS9=YZ5k0^QOo#u?}; zw5OavzAoC#y?TEJysm>B{yh8`+$+4`;6aY$@&~g9tB^7 zyJMR8zk2!~!FD)m^?4vw3wo^T&>7{{oS_3=A@w8#1G z&ld4MJMyXMU7)GO$tBmR@2873Mf4NPlkO7da4_&pig%{_G`!1t&vBJ0TZ!W(*n6O{ zUl*!=Kw}n_?}6uvwzRYtuvjPOVIO12I+(s$+swI@LdY zME*zT$*BB694zEVh*LItQD0N=@_$;j`8HvnMU8}L5vMsmz{h( ztpC|J(we*=@8}1<(^p%$4fhy9F7Tb-WW7n>DX%3BV-o*4pFj>Wy)}t=Z1hW37US3a zIQ-tkcYbRsJoDdL86N{fpKX{cx6Xz5>ij+UQ}XxVPs!h{9;s97l4sDwaI-o?p1^y9 z<~Ejl(|j6@SccpBMD5^@jX!Jaji6<6X5AF=kon@wusqW*L;1V=;C16{OEfQ{?O6;s z^gPNIuEUs}MY}e8R$fRa^SGc3S~t^JFZ*A7MUcf}H$b-A+-7+ghZDu#wDUCr^8rxj z<~SBv2OJ37e$=_C!`7w`@^1SO1CF%XZyh8pM6QZnK6`Myrth3j*bc$zT{%wogC2y- z@AjYN+Zpl+Ea+_J%MiZ-_?!8SM?7u`=6Tw<+@lD(zr=gQJD95$AgjDRN&b~`N<)FC zcC^Vt_7~;jixt(Yl@8P`n1}Ly2{M85zRcduaL4#FTm^sfincpz?_Ht2Ot*E9z%TjQ zO4&M`v;k!X@s&T1ntV{%8Tdu`6!rSp0pkEJDN3~#w1sx>#0?dx${7rl87tnUE^9!G}Kb-jmcA8&6b#ktWv0mmokMjQ* z|9!pz%L{&G-k-5N-Zx-*_CY7yr(=91(1Yi|Kpu-6U_Ig+aPOuQ5%xD|-zzLB*I>DV zZ$Q#?j?rh9{dZC}w~K;o&)-v5&f3ga7*DUhGSwV!E3>mu#?tRV`Yig4cBl9T&~LBT zH$YiOng!iGkMlGdvF;sQCob@&%>$WD0lpU=9S(g$m=T}WgFinSC-=K?QZgT8a!$Fozh^#MmHl3K5%}P1@olMX7V6hI1TxF! zHGu`WLt4}B^s>|ZVWA)LbJqMCwa>%-&V*Oi&-63!TUmd~ockfSC%Dj$jGJFTA9bzu|mMEzFaxq-5?STFcsEipmI2gL;OzHju!1+`Zg169+w zCDK>u58fn=dXy3PzEV4wm-Zoed_Fbbiqe7Kz!UR?3;NC{vVOMA9pj)W>GItK?}E0M zyhGiIG+4H+DmN@IX_2i3mhcDZ5b^11@@O4>u}Brn3(>uz`95OGgD~If9eSK+G3Rm( z!2B-77#@lHSN(&!RpdMTM$+%YbhefF4lzGcpN4LEBY!yW8ot9|9|-**gD4L(-yxnQ z9doznnP-$o@y`1eDR)ROvCT2Y&Bwuat`UsfmB`)1nNM*XzQy;TOl2sWYs0*8AAA#f zjwAF8v5o3m=57W5`n96oHzsmFqVwNvBA@H}9M>YOJaIGL7916SYm>!9@tx-#F@I5h z4U`!rys^9=FSdi;Ft{ahOXMZyl1N^Hw?tlIZdt9o4$c?Up4e;m$Iea9 z%rh@_0Dfaz0&N{V3UrnE6|fo4`=su}lRbE_S2#8a54PAm&r9K%8o#6Kal{3>l$*B{mOUPzxFP1m5s+_jo)Bo z{)XEC5Bi+!C4^iRoqKCYvpVR%pdXm?(Rs}6v=z@*AHD4TDEw~uIWIG&G*x@Te}fGj z^c(29oVQ!2wv4fy%xRcYZSVWiA>Hc+xV=LdhZxtAPtGAoPMo_=V>eGdG6wmV`SEeD zfaGId;VG2&X&lQ+?{UbL(sC@D*gi$Tt3kiz9(R!g#HS8?I7W!K#{K`C+WKs32j2w- zzOxMT8w~p0h?k@rY_#=%L%OwHXhGeAc(4xWGK5QC*3vS5tf>F)F6q;E=Y8sJBOlA+ z_x_O&-*kUb$ru(Hh526eN9yyK54jd`_E7K%FKbIcu5o^NL-z~TzZYBbz1R}iWsuWv zL~Kb>A2*$p@&7N{y$Rr9zMEeo(%*ERVL9rj%h`SzT*!e4uF3_i zp)9hvG?Pu17Wss1PQlpC<{6z+R@y(6$x)Q``=^Mr_cc(C5|<3V$vmI7tjx>zy)4%! zk^T>T6!pVK0Np`@h<1(G7LvZ{l=z4W`RMy&UXfX9Uqo=*{WgQ?m*#KB{3YiLEZ|H> zo|g-`E^En=o?6zo(6`P*UywNnb35bZxS!v2KCWwEZhmb+=QPg$l7}`jaJ6*;;*Ysh z=0(cu8t&kx&+#?%O(dt4Z>`N7PolSuj69d~uvAIVx)1JFj_MRWXKylpJlh|<%=Znpe|VYiBP)Ebt?+&Q3g53=;rs9k-`B11 zy&AqVZqlEV<^3})(3bmteV?&Ee4ZX?&hj1P<2bXkhPXz8STW(V-d;STPoyt#Zc}eG znU7+tv@E~3E`5mmb417Uc=blZ8IQ05H}H(~R`=ML^fmTRS^n0gqhnYzlzJ9%#vkzv zyiwaQdujU^&oR6Yb?}@G@zJ!m%sp6m=iW~LeK|Y_;re$8qsZ$xcTn1oGY9vON1;={ zsD03v7kabgh2Fdad8vOtss7!*ywGVSFLYYV3ppTbyM1|8k6(lyUq&0kv2!r;ecNtL z1>>82Ecswd@OjDUTc&@WZEu@{jG?VW9s1;Pl}VfnHb5_jy*kyaZaBBQ0@>uoyTFAE zO-BndWHiX|a3)GT9xc9^_dyUYV)LGVpps183D~=blPkwTKfsNAMLgfapCR{8@oson z-WtjcL9Vck=>erN&f7wt32)~4)jx8t=D8O>lExi#*vlq9cJjUD6Rh@?^UHvp9MI>2BQ8&yKM$?%@+x>vNHF8zD1QE^_}TVf6ruJ}~CR{4a8a^0D_+ zGTwU%zG{;L=wCat=k4sXo1n)qCIGV1*9m3^&EfY0e(bxXFh0pA2FI6U{qcR*p%1ZF zD%vkfpN8Dya%wB~VLqDq065mr9$6D;UD`Hws>1R480PI~Zk%r4B=}6XkIX?X&rzlk zzb0fi){!~J?tVOzFOSH3M}34vxQ_yER_1#3MYKa1tR1|c?P!kj+{+t79gT+~-Jz*_ zJDb?siM*Li;r>pRE$;73pVIWFpclU!WT)-nOixU(Ob+7-&HF?leNxjW3+cx~dgmDR z={%;4_>(@ZHrnCS@$UPTbA_*;_%}6Ak*E2M`B^?Z@AxAh6JT|2lzz+O35u}-24Ct@ z-2NkATb~>+^a<9ZL!VT!@A6@hA-|G7T%f~IrO(kq`Vmb(Qb<3n>4#~nXrDZ&X%80i z9@6wfh4i|n*9++fLVD)_`lRdn1o%szOb%U)((%4xpJ<*n_lef|+w4~t3*Xl#3Sas} zasE~I3C2Y)3FqSR8F4m{IU^??{66iInQPw*o9-{rZJ*L}Um<<3rtdAJ@6z;Lh4i2| za88QHR3;OP&bjVCehhHyBhg~M=nC~)vf?XzQu*ssl zu*H~8-{?I&Y<`1oN8|xp$9|~XaQQS784Bzd?@k^bS zx*pGH2|D^p`)6&>LmtB@YtqmiO&{TW!FA@Ao|#Miv4y!>&YEju{g?JHWD;#_;F9;D z?~}TYaiu-i>15yBr?d2Vwj%{Rq4oNS`TKODv?cw@p{t}NHY*+%9Wl?vYXA8g#v8nsqKq!Zx}okxsr{S5N2XirCp2ejkMNHO2~bx^R!ke(Jf< zc^hFLTMxX?H8i0SaN@b+fS-*g*2Ohhj_+yJt?aH`#%kC;1m>e3O5_`05FXQ-`fOt- z+K1rA7Uw2E)~p<1SXp@8t{dW^Id+gv82r(u z#hRJKeo9*!g*`$JHuExuuE>Mt`;)PWx(OHKLi~VQ&w%mZ)^bbgItP5%p5^#A@++2< zKVHxS-!NPoxjZvDK))&5dtC3ppbWLNGX9SGuo3S=;~KW?lh-+bH1P5FW%djEudE*1 zOYirjr5NjGBiVQ1J&b*}0cYkQ{$J1MLBDg|&14I|r)sMed0{hfSWP~zH|{9(4eJ*k zbaKRC2|r=%vTjS0XY-|L8ApThXVsVC^HWA}tjRq=GuRiU^fkU%j;=s%0bFkldm z>yeKCjL#h&nay81e-?PwViyRL_8sL|YHQXy@1c(+opr2uP8|AEb-q#ayTRjl7Wx9uv<6Ohk0R1kYMoLosp z@GJ1H#pG)nG9DJX&E#{RU#s_eL9oMeKeWLsme1#NN7wgwy7tT2qRjEQ7x9f`Y~96& z`f-6VR+L3e=mYE{#*4DQOy6BeKAdlCnPVH_ua!L)Hukt)P{Ub_zpQsAF&!~0a{?Q7 z9c?;3Ur>2w?>j2tKBd1Wypz+@znv!EgK>SDC{Y(IRaOK4pl(7~~JNJb;_Y71F2&T%dDp?g4+E>tJ+a8sd89zKAr+ zH{NUeJNUPF-O$I$)Csu@>yy8>yl&v)UaRWQZ^Znx8e3wuHizf|>p_2&)6f%z)-ey} ze9pCO=w*4lw397;->Q^(Rh-qA$XR{o!LwK|4ZK!8CgvB zna-ou$Ko^9H0If%KKQ{0Tiz- zN&T@ojzueDV-f2^?`K}7+x>CWCrrMiHWt+eU>g8mboX0Ot|88j9FFB*d_ z;3<6sykE94==zO!nR&gjXzv@Z6VHgx8-=^hG441gC75q2n0L;$x6T#Y63)yBGN=W9 zr;bLrCO`O{YiG8Gl48A+wB$W)+&|2`LR)=aguElKF1JQwdwtE1taHcs?%}Wo=J5fX zbLr^NRPPY=rlHl(3&=~npd4cEzg^d5Z_~A%{M~R7{LyxnIa4UpR9L5xj_+AN4x(eP z4IK%(0pX>p!<3C*p(FA#pKmvO{PS3A;XHEkpQ}76)|cXYua0`0jV*9Md&oD|o#86= zAaAiA?lJK6v2!0Lis@RG^P$HR^JK7Z8IMuHuh0(G>+`3F(XPX2*Sct(i#9_aoC`W8 z0W)d`%X4me4gaf|n~s;*JN^1f;jVW*XJYl^?#-RwmLKnNz4VKv)zA;euf-m2uV8=b z*%mtrG|lv3@S;7J*^V~W(cd8!L-lgun>WcuB##$)sM!DH)799C^uw&lH}XAs9Oj1C zA&)627{s`z%;Cr0O}40I%K4~c&g`Z~*g z$M^mCxe9(#t~4#(=S~~zh|r_WU%-B|bM@#m;yMTY zXPAd>CY~${djK)B+b+`{;F%CiC!hLx$#|r=zi6ayPj28P>I3Y{T1De*{r<3e?$1Mf z(nXHJ(4WEPVxO`t(ngFi-t}a zz#<(iZk+zQ(7yL#pKLf+Hqy6%56AYveH?9$kD=MCjj(Qu`@01PwzoOTdwZ~UqIHM5 z2CsWCkBkH2ulPo59VPsq#mMiR$b8>R;6dR(z@c3j@eJ@G{|%`1WQ%{NaohYB#%=3c7`NNr!no~x3*+{#w=iyd-om(zuK_nF zPi@^<_g!E*Xd!3pahnXG-Q;B?np5T|#)1#!CjEr`?m)__yV+|B(Cw$F=Wucc#e33`@| zH=cQobr`3QVk|+oc&F(W;A7D(sAK3Dq;7E*F>f*sH#rtb_Y*AsE^Jv?+#+^>$EFREB`Veiy{)LyY-zkfG z;Qg6w18p&BKh|Z;rsF%}NiS1(W?UL!5su{JJ9tXq(w?KfUJd&-@IC88qphu-OId|; z_4J2v7V(F0=hb!LEaJV|zwmcQb?pT|qu%1#Yutl5jlSiY2-l^UNA~g0!9 zERQ@lLj3SwKlQJ`lNH>hf;)B^8*zRf*2PYtUv2^IHp8y(#<7gOQ|Q-Q`p0}(dbEE7 zaFi}LGj9zrdMD_gs$BGaCjG@aHsi;>{+jO3`P#&M^HK$Im+zQsb@R12?gNYPNKlS6~+Jg;@0N1ZPZU2L^)Mx63chJQM#bpp;>fS;=A<|^GY8eJ%W9he0Nrn`|at8`SjAdDeUK+ z$65Y44%o}Wep>*Xu20uz!Y2+-VSEbxFh|RH$Ns!c*bh`pqaUr9X1va^WBks2LB)Kf zWj+6bpNY%2R$JEdq4Hn4%Cg2=#F>1`dQthRa84VJpSt1s_g7oC?fORh|Ap0-_5G*) z_Bkyp^6Vb)OJiLhzoerjY4<2!Ab%I%*?ysgJ`)bpqz{*ow08Rf7{80(!amp|*w<+C_hJ2Z1nCcRoqpuRhV>!FL~ujC4$2TF z=H;M|>{8i%NXs4y>-%3YKQ!tQ+~GH7Jz06gZ;%EZ#$jlz7~dHqwi;e0owK$n{r-MQ z*;L!jcuB(V0e{#7n|q+cdFb2LE@{)P+9r*C@w&&0cILOW2lISJH`P7p>ok3ebxD25 zUz|hfi{&@*pX*%7^$$s_PVoQvo$lUp&dbCPbM_S1E&lqc-)~*09{XcUXSqPL&=*S$ z>YwkxTn`Zk+zFd{ljxo|Io%WAMfXIS;C2)E7(RWYyWLyF*m7IP zlYD$f8wDAh#>9%eMnvS<*kNKaD$$f91Oiu1Fna>!;UrkP4u=PT||3-2$G1<=dZ{xe2 zGs*WW$;oH1$9!Dq6WbHS?MX>jTGzMCNgPC@Ki^}^l>Z>odCw@?qvxLVm!eS{VOda`8UG81084b|d!Xvu{#8-+^(NJ&pGxjxVZ@rD^$ah5&Iu zoCEa6a{OttGY`x7JMbt&J30a5t;?<|-G)ljZ70Tg*;w z;w+rzrDM|1yWuB;pKdSQ#Wa)cCI^e&bbVN-1c*H9pbjAq;duZz%zu$;&#L3G4DR`0e^KIccYuTPA*A5?NZ19OS>QYpZ~IT z|G)jq+Nm-%M(cW6OKs3pu0H&x87W_4Eg!r#*~V2i4Oszl)xB0OKUeA9$?H z_j(3x{iPjx{}*(^XzaMLNVz9I=x56F#{R9^MXtSB|B}ul=r^8MJFU7{YJ7t_J$-mK zN2`r9iGFwM=JaL6Y5yMLd%O6DjxKbCj^3;H+g;SH<()27 zSGGHJkv^u5Pir6z>3wAzC_58m0^86UMLy*1__Q|8S3!EQY}r^!5d)30Xk|?z<302b z&qL`T-O_kJ2%niQ|A5c`bVkz~;hi{3y0*o9XXZ;YKQ?J4`9NFwZwGpi{sC?^{A)k5 zPbD3E!THHU%7QP0nJ>ur_$}pVO`?)E~u=HOlSpv(O=VH#O(rRvqgN`-2Sa z?WY~Q1$J=I6&VYiJ;Ode3mugAVNN%Koy*8x!~CiHFTx(k+f?6p$J``fH*Xrc=+{EKZ~G-F&wBYyVC_;nbyqxR zzy=WfXwUjS(lb88c{*s5z%W}Eb9R&$HXG~W{w?#fq|O)N^D>{|6ztW!EtW3#Q8i+J z2X#29gZ=@-M{N$;7`_g(Gq^VB=Oyx-%}Im&R;iA$Ci+NJsBxf=R^ zsp0fo&?MWZ4ShAjS^Jmz3hK=E*u0nYT4mnxHRL6YsrOMQXB&@e{qH1(I828O?>}w!j zgSJ`!NuPB4)6!k&HD`W$7@G|HYu z{uzAF>eFXCLyPdU`rwOePR6jB?f9KIm#qcze(q_MJ&XL^ZvT1>;Yd4?C#w%KkZU`> zJ!NZuDb{d&9>D=}vxBk=ql4G_Z0$(s3E5g)-%GK^(+7K|U$>?_?qCgQ0r|W5ZuCs` znQ)k9>w8ikcs;Ghu+nb7&Yw0)(!l2(lwCmnF1}~==`-O-eaORqsgGl7G=`P7hx*!Q zwy^n`D2IBJLwOw-mmR=aK-sQO*Jr|%x{)WV8}ccw#&xALmF4TkxRidx*yLVM!jX53 z`3~SLplsKt>oZ|W-N?iL;yC5I&6~w11ad@606anH)P zOm8;Ao$t2yr3cFWNyOujBoV0bZ(;TuEwy+{57-*Je>~6=hnm&fR@v9A2AL`JhW=#zXS&i)u2mK| zo-kyZX}a&=i6K7 zu$DP@zP?q~#9Q@8=X(DF>m{%U*dM)rfptclMRez#ww}rJPad7)H#_&9&pU1kYivAw z6zf@=TY!uIlLze#>;~ZKc>+a!LHbC26YyQZ_Y(XRVu#e>yQssLaVYpQ(nEN!mgweI z{y@-oc-C=ihG$JXzS8Gn{pr1-Oba~NL>c45^j^p_r19N+A}M?#z&Dfqg1hEvfzL|k z0>f>;;`;K?!6`Je|_MFtM>BTn5H!W{%GTY}*WL({< zI@zsry9)0{vz>+X+Y9fePu*NhpA5K$xjU~X=z95kG)5KIK+n7n4Chy+j=&d)p_TP$ ztepm41OG<61|D1$uk9-E+Aiwn!&A5)B-qP)@jesJO@g2GC+tSPcVGiDHqmSprrC3` zUosz+>SofP-F)uq)v*rY-f8dAJ`o;V#+N1aSspFh8^u}(+JbK}uNv;^|6*)MuYtVC zZ@g&F!i$)nd+njANDst&#*6AF5F9kU*k<{LI;0KSZnNJQE7ok}Jn=(?ccbI(Li*0a zdy%(j4`%J@hPsP^wD&O`%WzNqKI zk$0ceXOp$*GV+n*lV$I7We4%M=^9+~;W~9XiT(?|$cz`nuLygTci(1oLOwnh`>&6- znooT4^^txCIV#RlB{0Y}`;fJ34 zc;P(%-{(8*Q4dGxkF?%VjPZ6{2fto6tW+b@d9ds=fi_uz`7Rd*e~GI_1R*(YiL8Qg0liF z=Bb4DZLEB01^)1DwYK~}E0yuM0av!agEB$`%$@0E>}!nZd^n8jR}c4}y@eQzk5)E+ z^4HR;qY36%h)V>)wx}I0KiiPuFMSnBk0hIPI3`7={<-U)GUgp6xZ( zNjZ7Pye)J=*{)C5XTr?t5P#)>Ixt5FOw0$pw$lSJH%J|$sKcl0v&Dtme3MT0+vxNW zrd0mzRN!^i{)dtALQkH zZ4>Uf!Wvo@&(|JCjM^=TQF}Y~jt)=Rnw01$)mUd}PR8$n#=%C0JUID-YUc*Id)wT+ z@JfD8W0f@6KKRdQ!!+{^S=qk&RVHWkJ3A*G z4f-wN^cv8K-8*CYI!KfHoy;xPpIRO*Tdbe3`D=mJLHECH?pGyFKIerH50SqIJtlv* zHWH4t^DaIkZbQ~l+WqH{8eS}8?b^XIzHfsKa5L9~kuRgGU!Tf&4l+YvVch$?OgrBQ zJjXFXdNq|#ub%z@;uC)gdzgo153_w|8Osx%gFn7#BeVdg_cUzpTTu6A=xg2B&)$0) zGU%2VpY*iic^dqK{KSWKFt6+#I|Vq6{;?~tHV50s_S1Hrnn0a7Z>OgM%(Ez8x|WP| z)|b_F0(F6hQGZ5T!zHo0zNR|J*SH@Q@iX;XupiZpgA-aO;LNym;{)T3po2gM{C<_s z*4|0rFxsz!N9}dCRrAx>6Whj~*ha2*XZvsgXU3&B^_lRab{|EZ)qdSNs`HE5?k{3L z=cm_C^}Y$+S9NI8C_Sp}2An~9c1G(X(3?j4_uNKxepc)JEb4p#`t}!5XWQwPM?#$o zfYbHq`b_vyJ0C=y^?uzp?e^=OINMnGaRK?e_-=g3ynH5{2sUi0w9yYIYLA;UdZP~J zA*~DLyFOi?2{)<>b4=Rq*9W7z?$vRHIft>a8UCD?FwPcGzU$NVnQ)nxF`5Fe0=y6Cw8=o(o4OzJ-DKfC%45}jUhLd6 zaV6ak{}jHl4}~`RytJg_8ZvKj3HxV{AqHPz_Kt=1ia*rz+Ks0>$VWI{kCAt^M|mdn zvv&3j;0mtLLkw=JdQ2ZY)i)NXKfn)6f34wL#-FLSf%b>c1$a?U=%G#jmivEDcUI@E zN}pQ;%+fuwhAZ^$K&q7+JPl&6XOG=tXahmP_G|woC^!uud`RvAeJ=b?}_O|#NIKInlD)BKjpl5db zb$PRSQ=J#Okf~`S=qYEB|8sy(yL?#VQl$>0a~uiW)8%@52KQS*?sUHexN2vdg>F#_ zS9K17dkS!8`t5-X?l%2j8k_Zz&vnBOJ_4;zNeq z)wNWtRXhQ>ll^creE&f4{R80pgwhxJOY1>8>)9p!sdg*k|K2C-2>JUzD7>5QH&#qn zy8fMFy3z&y7w}CE*VfK|pqQ?B?LfNp(J{2SJ)q4;bqV+x>ceM&lz|A8Zq@MU#f)SMGHQdN*`@q_e(Sfv0UvmGCoOfz6V^KCG}0 z19o+IY8o&y_^?+xNaK5YNb?<<=(GJT{#M#jM_Xq4b+=+HuHau+{E@#~Usp~qq)YwC z!+-HzD30DQ26)+A;C*9e{|}1QSURw7d}(7P=n)$a&i5vBpIQ2Z>KBvHDW9MoIdbC8 z?5=N|gMj>k-#(?!KApQC)^c)=OLFor^d6$#f%lHL^uCKSnRa(!Y=|Go@v!la$Sd#} z>;C9UWxyNECp1?+D8^-3A0Ar?miC9h(mr_zWhhH~pd-_T4@LuzW%St<=+oIX0UJHg zh-^HbyeiA!6@T9{vGF8OsF56=PuO#r8}>J?-8$CPHhDTv>s}PnBUefYTAlys%IqVz!ty4IY1NF5U@Xy|x zh;7w_UkK;uVC^8RGc~clEPHOeL-!PZ2&4pZmDw5WDcnz<&dZ1mvmbLr zoqGyzm9+!NF^C(?M^C97c&fOk@EuCGTjx%)%}3%rg){HVZ3=!9SWb2WpL{Iu)v=8A zjVN7Znd~dFwHwGgwuQ1S$_syiq(gq$n6tACxW3@?GN0iT{Qr4dES+a;$sAaS+2S4< z4~zDX)Qhp7tuMi@mNeMbQC|27m@fMatND5eZOM+l8xNPsHSRqj9b2a!8GCVn94@xW z%7r;D=p9#2AHjOTL$lz)uwDTEBJ6-);G3m3_quKde}YFF1^z@_RUdzXuLvWoZLNVn z?+J8~J+I_X@IxPef;T#KoJ|0oign-@`16?Z=dtDa^91>2|4?-PZ{twTpZrFcwthx_ zi~4OfJZJoQM`%lt=XmA@X-ode_|x%@uTSzRPJMl;>ES$At{sNBOh0#eSdm;e9G96p z&F{oLu+103Tqn3xDL*ShBYD^7HS?jCi}~;&@ZnEl-D=&kd>GlX?YCXDwPMkKzz@8e zp0Qrfs2xlHe+PQD^yv|$`;oX$*$$ig1%UA#OIJFvcd5# z^&^k`SHr*ArZ4_*SlbdjFn^@o4O70|X(N6o9eSf1rXoBr-*ixJ0eRVe;_(Xdcsx?P zOFtox{CC5@)QkBpZI8tDPR8$2Z*?TD7xQBWdR>czZVj2mOzPO-h2P7nFa_NS1^y_wwR>D4ip_;COm ztuu-JtAN!-zE|+Q4=%S()MC82&g9S3GnoQAAW9dV(w)5*;*6F*i_g}aiMPoJ zd4^0OKJwfM&wo+CVMW+F_63HHe|c6J@F#18XJeQ!C>IPr$_zgLuWN;;V^iSi_>^ZI zqrbaWc-_G30z=20JnPu|jkUsSJ71KJCxNG9>XQX{Ywi0eo>9kzz}K-M&pJN-v)5af z^0&ZL9uIBF$Bps&5!5Ae3c5^hvO2{WOdIdiYZ!|uW5wy>fc5-Ne~t^vF^}lhY8&_Y z0X71D74V@)XYi>bL!TD-%jiv}JNIUWrk?R~i1r`Sq-<4X7j&9F+_F3Y$EEh$;`n+u zel1pVrLmSOOra^-L>%~>op;&J*O|BuXX{BgM^?_Y;~oO8Z4BXjMwX4%HmY3PK>XZk zob}coo|^PUo{9EJoQbvvXQIvG zOtcZ4iMG?uL_3%Snk_?bwxb8y61KI=p0A#M7<%0YU|(Ra!Wq7_#b{^u_9gR{M`)rv z6YF^EulYB?5a*=HS>#vaLGypCNC@r*#pthxER(Jkx9@r+=$~e8?M} zOE!u$r1zEOnPzoYwjItd>$o)bZxiW#Ws$}{xAU%=sDtmUN782kT-(oJ`v_8PlkGvU zHTiThv_G8*V_O$JPbKpoLBHc}^T1u&)}&Hj`oeD&-ipV7fgo!2rxq*$+PyCJhxpQ?Ker+zLUL6 z?e&ptrOie>B012wKDPCsAB(LA{XF5FnaH0~_l4dq&#)Jy9_au0e}1Q%cbeT`&&vD- zefRa)dJ|f=)CC>)4YBq10;g&`zRPUAvh}ha^q2^zy}A82)2?281>gJhTGf4QeT;RH zhPHMmbfVr}kL{imx>S+2MVp0m=|L}waN31_0;~m;?>5#?y@Kz3I3Z2?5#Qy%tN$p+ zcfkp|eG2`{<509+Sbt^&Fhv+jq4*hQ)a9EkHRmxEw)yDeJ!@yXI^Jr z^h;Q~ZS0Z;Hh$%~8piT}^?K{l@hCLbF)7bFF6CLrrmSZ={dlc3w0&6u(~VObW0J1p zEUZagdw<#9DS?TxWb`yUC$uY!nLV$UhRXk;U2eYAaS+-S(pR)4JkKkyvwUt#R1stM zHu&mcJ8`aT=-K%^`?(S3Kb|?kGThVLgYVqVy|&oRJFjnY>Z$mV~>~*JcGS4=mWH$ z(wST*;aZx1=bjAC;Zi4L5$Y;mSGGJJ7HGuvbe_5KJ6uo4nw_jC*0;>v%D!>$!S9`a zH(#&Rr!aYzA=gi=zxd6bub%#g@RP%b#XC{7Kg0L11`z2^?fje(>P#k2&jYXX)h&Ss zs*lX^ZYP^7dj}93fb&Lv`R&F7SYJRMq06ray7Yb>JmA(I(|sDxvG3sg?wgm74QSOL z(LFg^_@IsZ+bFY% zeA`cJTTaJqNuSpAr@^B}r&F4Cs*v|7O@E5xDCqd<2~9gu$oqt*KjGj`YT9HW?=ek3 z=JFoZw4-re;YY38Y?R_Y%$o~+6V|xy(Dmfo=ZsDQs~g(Umq+W6JlbFIbKER+2hYlw z1fL(mm=xZtBqx7f?=J20H$ALvdDyk@h^8IsZy)P8U%wgS=w=;9@1LWeAsa`ZMZ5*Z z(Yc%bIC_5=M-K+Ly$8uJH$j#j39=40E8+G&H8FqRjf2L~`*j@MEaPZr**K~z?)4b= z)x>cDhfJ;$AO12N?+I|Tca7QVAYO}S^Z0#CjGyD4r6RnnENn2s?(BoyZ{z$HcUTgG<&ya%Om=ojfK71*1d(c?$UJF9oe{9VNR0sJyAF7&E_S{NxluyIs}OhCud}`V}o!K;>Wua?pHs zf(vCOajA!MLrQU(3A|{&KhdQTcNFxa+vdI^b8`OvuM6)+vwu}c4|-Hy&e}}8tlhu6 zQW?cf%AgJ8$SnNw(k@EGQRv>p3y0(}(E1B&MX;5nJwly)A>0?Kxg zUW%vYCw$^5_1%m5ssrkKD$H+P#F5F_Zs45-0=+w$HY-KQ+JYxfMJxSC;zKOfDQyedWNL zaGp!m;Sax@;6EAsa;5lde)AOw{O6$}7 z=ChFcFmKX-02t5j^nPcn&&{FU&nLDg`v+y|*DI}0eH7-Okowk>Px|YVGhf=y&wx2s z=!d*rq!WJMc+>pm7ZLcFpCwLlvN~n9vCs$ebq8q~z4STw+yZTxZwcdx|7PQ6XUEhx z#dEnEJALC#`BLf~4deMqjC7m!;5}Zpj!sz8zn_9G)2&>L*2zBwUO^i1N}mh! zL;BoApMGYsY69mI)E|cW=fkS{w;MXoG^jrWTTW>93D(8)n8R5MpR1SXGMeAwU!*?R z+r@Pfp^w$~S31x973LY(cA>5o}LbyL>MMeH0#-)$FG}U%frtuMJ(n=JK$IC)@$m#2y~OqmBEu zZ9ca9wUI`e^V|U5|JAI9y&A3Rhw0bZ*aB=T%Y1UbwrlgRv#sv^SBw|IYw2izTom_0 z%mB8?T6`~^KakG4gr~uGX}i)V&l%t2=|mjm{vOb80r|W5UJ4uOgiVYwoeb=pR5MoNs-rCKInhBTqZqP zchFh$^nTeG+9vpEJE^k@JuzNHCg|9g_&|=GN<8+6JLG_zpZ&kfI#j_Yw^7@_5wgY>VZQXB$WLa3)5lnuK!^(T>F`+Olcnx$OLlMt-+e zSxn?gcl`>D*e1|}^k@CNkE@1rTU#$|9h3JL{&WocgMY-HQIG7oOl=S4ddvr(Nlt>M zC-3)Z|8Lk=c{|%z-KJ|6Tj%!SopNbf<1`7k`2zMK)^i;A-N)8h+*a6!n8UJk)*&!~ ztF?o01!uGev7J-1)$NyS!=YW2|D0nW2eLbmqkDr@xeq&ORZZ*mDPcxWGG^6;>3d+UUPCQF!}Ma-}u`Iu9pz^K3}sa9q!x*+Kf90gPNZ--Wb5{ZXWi>JQEOZF4-M@Y!{N z|C(*o1^K%ZaEH)_ABF#*aZ1~{OWUb3iaLOAPdXaLa1-!^9x4+%kcoc(H|r>Ev!?$% z+T6k&l&waXd)ST<_6=~_gZ}AZKX^y)>uxnZh_RG>yN5eHnNFHw@80~2dL}ZzC&Lr- z{I388|A#h(GoT1J8Npc7et$;SO8aHz4vROS4~MWT7BB5&EpW+w4o$4mpo$ z82w&3@W+B5+8xoU(SMHBvUL~o+MJi5Udl_hEozs;)nx80>Sr83@!kWxcn|xuj>8_> zy?r>jvJri-sC}?!q$JNskP&PT%Sioz7wZ2}#FXe+UM2NgzhjQM!hHj*KhZU3(!FA5 zXrZrCqa)f@q@&@t8~CyHtO}=*HR4q0SEFg5 z=Rw@nsr0Pp=YQ39PK+Zy;l^xy>9@}IWL#*oeS-Gmjk|Xy<5`|UJp=jtb>=1RgYZx8 za_5(o)KyykKec@IyuXw2gMH_{37@>jr7usPha$PKe7=G5gLm4}W@g_|2JgUJ4jwz% ztZ$$D_bNlkQ`UD(=lE;YkIj)+2aUsNj*i5OUq}9N-%K|X`XJx#;aDdr>KigP4~G4!5l?Ab z2xoUCpM&GG>|-A20Do(j(2VojM4%gSETfgc#~ix{b+El7)U!7In_tWL()BrUts&nc z`eg(1?fJzGzn)aQ{k#?(CAYPybFfcrT&FX6U)L^9T)A&Z^(3UD-!nauJRmqBPpyOa zedyEM*&pmr?)keF<5ppDAC%<%D)_9j0zBlYRj_vt@E8|M7^U@M{4x*m;4g*Veztn- za!2cwG<}ZjUhoC`$Lr6gcP(H{NFQQMP(N7!&6#&48O8pk-jV0^`apiJ6l5XJ00i&C zw#f8r_*PLayUU$+Ch|dMk8Eh2+CJ7gb!@Dq_OZxRw3&SG%cM$&4rtJvQQsG0*aRQ+ zhtPuKM(Up!uRzZ8o1|Ml@UOh1-8|a}=T%)<8U-C-Q!b27T&be$q{V*2u3%n_#d);n zqdnO1*t0sH)xB|IJ~{^p@#Db;*fM8%Sl(i`%lg8`lIe-djiEkW>db~*m#Q;1{`%Rg zRjy}myL=XWK8rO5`i1DnnFU_0#@5R-h}R}P_&feKjl+abeHQEZxHk;nA3?c0P;Lv( zUO5Uldj86GrNefi!;P&=+sDHGi#%41?QFHN=kgBNznhbjXH=#&HCBy%XMKVGb$*=f zV^5IAcK|l-(dg*hos|O*vD`nlJR^v&wpdq#y$ibVF7`WqBj5+vH#|GWpOZC#Iy>z! z=80>n&xHMd%i2PSsgrVb`YiKvL#3>wDKF_W%YIeL=5!Fhi;v-R<*fZbjQV~2c3&BX zr@ntN1E7E7thb{CW{a5VAJ;TpF$Gy_vS;~-B{yy;Fx~bO7IJ1W1 z{b1#C?cl-79AnJe#2IlmHhVhOcSpRiZR{*!i0rIumce;%SI&300tcSS68a>_j*$mM zcHmp9`{)?*ifjSSBD;rl?4y0>2(R@r^1b}%7-&NO;YT~_lLI~{dtd+T?bzdx;kH}p zW_T2F{1|X#J78^L}axq|u7^DEoX zax~#ya{5JB5BhDM@9YomBU#*syQC@@XS{m`I&bgu!2g9U8UCct;@$*0s;>FJ@W@yX z?^1rRl)14rvt^9?C8Uq>UCQiA=FxV!mxuT=wj6Ud>KHj8{xEzO7`t$$`8xw|8I3Fk z277O^^Sc!H@%sw&BpoV0`=dfTD=%=7&U%eLXHmbIjtY557$q)aU`AC@OxSnNe{mOB8z8%)0MCOfhtu@Gx z-l)*`#GU4YL;5u2BFplgm1Z^EQBu1;Ir+H7w`&@7+H*Y5jJgH$QwI!Zg{hCP3i(x$ zZ`nM`_OlH0Q;yK5EPBb)&`UJ^Wuyz8fJaUC^V~l766BWl588_MqWn)HAOCUoqVT}d zsnFl-quwd-AaotU>(cm_xZ3}aK zTqnLqb>dy{1`O88`t0Y&%^s|;aO^bAw+ueLPyNW`MbGQPcjJNYj86q0f`L44UbKXACV?Xp3%zfRx6O`%C|1;HHO8NRd0lwjCW7hH}@5yCTHuesNGB!@p z7U2!bL|bpNZ`SYVGnsd!9Xig#^N{p2>-T%6Qrux$N{{!2x>;9mdu6=0{oweM9(G|Y zd%8&-JS$W2rGEfv*|?YQSi_RJ1a|-cXUq%GMK5g|3uPbEdG8+~Hvcf@zHo+{t<4l| ztbw|5!8Q~d1oLjQj`elec+$_Dd+)-J{kuNQ$^0JPX;WEwg*&`jk7gd??wPq4Wp!E9zZ87~AK(_rTT)FlOL`fZRvEvNZ(3QR^d2+tYeYwnRI(Q;pCbItv{ZSkv<7G_GH<(c|&|S)7R+3 zxyCU}8nZ1zgSydR4YH%8&*;CZOef#&>#r*9&W%_{8;bgi`>2b&Gz(jFWH_NdEb@+b zaPDZj=QKRiju`>XM&8A|dF%+&lQXPm%jH@pnVOzSu$OwP=+>2Z8_I$=auglXh2j_XJ`7_#+rK1hm zGjxFLS!J!@rgB!ERnE$@%GsiBN*K$@TYjT`!7}oUd2j*xxu5?A$sE>8y?PLBBRS;y z+SX{Yy1$cnyn*A1bc@RZG$_z>tjzkQ6c^29~vcYPNB zp6X&umwRKNkJJifi{+lC?gO1V*o<4|{%PSotRLjxEsmLWv@U%(^DFZlTZ8?Cz=+#= zyx3OQON5cN^@i~7+ln)G#(S`@3~s8=(pIN;b?{7`%(s?y2g&>?z7Y?~S$W4;$A2bE%^ySlG8>aRmgI%p z^m!#s;q~FJ>C|1Bk3U-{yn5QLuYfO&F8SIa*H<^-{HtJViEkpDIx?cjRG-Ufc94$b)F+Hmr_O}$Sl<87|B4Jlv4K8gAn{UzbXFLArXMlm8)py5xu(^M*jJucx?j_nS>40$zra9!Y;Pa# zn$9&K=DT|O0mL#6>G@b;UAAogTJ_pK=yCP*5cnw(+g#|*}S@W3-8 z(kB)VU;GC@%bkBMTjxPJwn^*|vr93Lg*4cZDyOCWnm4p@`S_@8z5rbMoG4#7cM#^F zd4XegS$n9>JBn_>x+Tt;ZVXSgCWkJ{TIGAtzvJ)&ygRv4y&G$#cY!YOoy%OIdI@ld26~{vCQi(EZl-eaRgC8yx1lV)vtFiCW`uEx z{mHikxHbnjQO5Gst`GHm6!j#dK|ZiPIWGux4ZRz26L(|%L49)49>wQX%sFSfm5UoT zqkfMYzBe0hr_7K&RXV1!vBVe;+hZhr(hlQ<^|F2T4gQ`$Kg1?Hsq+`xJ@T1@^H_h` zOkd0h`h{`ES^tnOZ2M$oY7zdP%4g2Zv#p&5#{PcbGo$kXc!%fj5`XC{9pCinVmed-B_jI5in19;aEH*cD4A*dnT&n9~UhXrdM0`BZzl7FkpOq6`0_~#? z&To)6CmD+aSS4`^xv$g9N6z7@p? zkdv*XLx*(}KH6_>WBtw1FwYlllcEib_7`jq7k5>tE0fKrll85(4x

u(SH`8vqR z3eLb4S&x3?+Vu9z)Q3CJ8x}ApQkGDsUBK8S4C)%ehmZZnpfzHrpkp5!!`+zUruQ&j z1#$@Kk4gLr_XPMluqOA~apf1nWShwAS5N;K_%`_U;eX8h(5*UjdhlWJ-7^-7=Yu|W znX-!VlCgAzBeD7o(n&js`i|!@g}xXDqJJ3@})p zc3K!0xqmqw4fKe{2kId|47?w`Fh)2YWnB(!|D<%}Ix4{eE690`?oS zj)Zp%Vr4+OdVki~bPALjjfvtLg}8PV;nkEl1knC%;m?Mbe4 zG6wODOy^XO*+5B%ZAVD8VUG2x_guum_UANlgc<;7j7cSK@J-p%rWV|I+hi6VApo^f%Qmu@6) z3jc!V77rlaYB&k|`YK6z*?V1CJhSW~%J%M^U`*9K&xb);*1obc$kWN{RHqvBGnQu_ zwzaow;>yws$^6n6fdh1c-VyNpkwBO9uzP+mjJb3Y_j=TWE^+);)#Z^!SRw-tf&UL- ze3Pf@D9>l~LB;u6n8y5+??MO2KG5Wf#XoQlYW94O)(d&unF-^6$4y!<(z1H@qu%|f z>v7b(596mkfpfS*8?o*d=#AJm@&nf8=I>K~P-maZ=VH)&Ok0TK7}?M}27kn{gX4>= z2kqJYTa|Iv!@a6@Mi1nV_3eD`DDoc-`R_!%waSJKyJsuo%75cMjUORQtnEFt2z|3< zX(-40ZiPPN6X>9Xu?H{~AFhm#kk|N*I3UKwPS`$A)*tCXbbvR=MkO@Cb7xH5i$*DibzZ* zU}ds7QMtod%_#jQjXj;rd8et*VhgkV`c55=XPDSsKSG-VydBzSJ>_BMq3kC8I<)E9 z;hE{YuL<(!rA6qk81rAUwAw+)Ql!Z{jGuo313$*<4S=5C&zwHbO(Uz?GZwHbPR#kW~u zqzYI3gs#nfxVt*749}+^{8sgYmua`quj%{J>%!o5iQh8a2IVqdGP}yovXS=P=#qrVJ9G<;e4{>1B$y4w+X@k9&;@f#AaAd4ZRed1jd;8A#81w%u zGG zA=kDiwi^3(reVk6jD;&*(DoJ7A#FfE(ynJan&!J?e^xP<;TzMzZ)kJC-O4ci+h{l2 z7U}S=J=S*SU6a4&jeyaJ`8?}`O(lKc=>!^yA00G04?bdh8N<)mw?)v-_%PU>$!llr zd=TUjU!U4GLNC(J%Cb+v=eG6)y$U=L#Fgz8mAbRoym6 zd2tRhL3C#7r%6^qT-HYd2&|tFdhky5AgW@CEhh>_+>zPk?fp-}p?}s2_4N_eYt` z@cu{hXOOR1pU4W174ik%$z$eE@IJ_*Jb)Zol`pX9t6&`heVZ4Q*!~e2zW`ZRWAgHji;Q|0?=^WK%wO;>-@(S0|!9)D0(3*ng0T+1VoChk&D- zXP4=Vz$eJBesJTVT2gzB^+kL`TmhT1g1T?t+*>@G1GZi zka2x@FZ;cKTXt4~o^QXTb}Qv`t#VL(6_${x?ok- zsoAgC-#j-%-3)$xwx8pGbJRKXBYiaDbK#s;-IC2|JojT3^H(;f;hWIC`bhbl1{yNf zJDSseAk1kNBaHHHP7Ag??_G-1Z`Zl*!#MLb%xT;Q$hwR6CC^r4dU_Am3V^Hh?S4($ z51HZWlk&ikd1Ow*oEqXHw$9o8Ssw!|^7gqZ_*>_B-iv1QJamVMCj3UYyf3Xd&r9qD zcqQEx&+}cSNxBPi2Do?};lm5<*&E}Yj%xZS*GP1phyR0hGZrO+q5el%r$$?ZCeW`X zFLdlEuhSJW`l+5k9l_^idGizW(GTrpJ$sNR&?v}G=E7V-oqQVK2oL%k<_4T&THk^`;u*mIeP|EHoV+)}JLY(?CzYPqR}a{P zi~2jF1Zsd>azFgBFfBH1` zPb=Gd?)8*hr{(%WIPW0XX}q70aE4SzrJgD4i+biuv5uXnBW>gEaMQtnzvmI!k;H?% zGd&r2f^({*p-o)Rvo$%S&odpgU>{4n2%EGZe^Iv;U*|5^)tb(B=WWdON9w;U=hro} zww~HHMxDQc8hn|lav}aqJ<5{PDdMm~y z@i6_>cy>Yc{o_Mne&snk>~HR0ze{jaIvhymg)XD{yVWc7MH@(iU8E7};T~0tujlCN zYMpv)>`AjXX}4pp?WymBc0Bpo>ij;G7u%k5zMgMN_#Q9fA$fHEwX)2|wi6F|##r|I zU9$Dac0R7W-fcQ4%TuSZxzu!spjW^r0Xe}uA|KSQi^g9)KU;cru=XcEkg`8hDxh5~Z`y z1bJxh(bf}QMxXv%;XPHDKf`puLp{QSXBA)66<|YWYAI~gDY_B&bg_P+wR^U@X`>Oo z!DQYY7Bg(PgO)5$TE3_@D$8Si`T5-yoW6i>_UnBdXUp2To}e9M>}J0P<7xSas`mw6 zd}hA(v5(-~lw+4)#knO9KsN9GlRITjmvc-M4$r4149Gt5wW0obmSMkdr~n51xw}7h z=jB#i>ON`xXf!0A`fW46Jqvg>v;(r8{4RT}jdn&ima*81=xiECPqWF_WLs9rgX^)^ zFW;MK^^m@XJMr~&d!UR5XB-deGiC770q`JXFx%qgX~cub^TTK(_$cro*595eyr&BD zai%+&p7Egbg|3MQQ8(oY;e*b?*TnULt_K?Kc$dCemhTRf@EvSQ_8tAQo_-PEZFGEx zd=cLTf9_`bcCVT5T)%j}dv48qcL;j|HCEO0osQ8p@m(YK1ylBs=Es%BhjV6)XnE<*=+ZDPIZ01duVH@X9MbJ*)j`>eM&zXOoZ z)TO}BKSJ8`9lFVt6#wK)`lCnICEu=nWN~cv(w~k|$Jn9cr-MA4w@go@pXMy`Addbe zmNnljbt2Owc|mzn!GP7D{ykjRhcAr7mP9hDW@1O;7uY>&_ced6Z{rWfbrf%UBgw-xfT?uaK= z$iJ4nYlZ!~^Vi`=K>zm8r=%$oA zTR(YI)4tPfLVeV?xn{8dyB}jToFVWx)&AF0?+Uc*8+&DS0mk1NdX%;A^`wC|gXW7; z`o0cX)DMrkXUW-C;N99V_3q@`DejzMx#Mcv-b#J!NRSKjy5=7s2_9 zWtcAhRi}?w`bOlTF2lVotebHqj0Jfo{pJ#53sb*wc1vA#$~*5A1H&TP|Ik~gru zTqhjZ-Zv2c@AcQ~=g;r;*Z0QPceOk$%s=!S&@YuB&V{lwE!uvC{MTY{n9bh@dpWyO zbkq<}!TRVgqW?$iGR#xNQ(_kpmqR-fYY<;Ddx_t;f0qADTgg^qzG%KEX>VVic5KH{ z?;}36C5pJXxe>Uy_2YV&Px(F5#mmx`qY>(yK7_ly9}IJ>&<6EqHY?yRxb=I^vk@&y z+OX;~8yfvOOdpL(@P=*-A4q;*j?sqy5pJXdc#`~*(Z=CRTbb?gKE8;yCHO7t+hARw zThtFF<@?&#pk5JT!!dr&G4`5%JFW}#;E&;$T7Vy?i#1A?WgfPNJd(YWw;401epsx- zMX?9hQ$K3_{@+j6Kl2`mX#iid9+rnMcfj8B_rd37dyFzLS@x(O*&DwEKDA`jq8aF7#(VuT$0}!>&*3^pDlBPjYBV_Dh3Sq~BTiMkMAP zF#(KMAWWej*4}4ll8e~;2LJI^S;LTY_}iv|m+hZUN7XNh_=Vp8&))mT*m<3Ie(#;D ziSDJOI+h*(ltj|A0f$m|g+SR_MB-|+7p-JvyGiYAvTIA07y^w{w^#`7BIs(pA#>5L z90gd@*dnM6#4}_T9x7Wbu$x6WCZzVFN(lKN%OAA`B?`<@@qd48YgJm;K+pg)M48f@}IeS9{4#lt*zVtJx`naSg4 zU+IVa3!eV~-n@?WCmcezm4t5WStaMLu7fO&0v;8gw>vw-I^6K$;NUkuM|t#<^7Gd` zpJ!Qpr)#CaTjuWC9Fw+17o!(ARk@7+)h{$Qd`ow>$&lv<>_+D6Ii1>uk7^wZAL~xS zdfqEqcz%!fL+qLL3F<8NR6p{H;WO6H#xR~i$0TLQB`2n^Jb6<89VbaHYf{$b9lVj_ zzH9wIYS9^!=`pv-h2)lX^D6_zb@e-o@3qm|+c4e*PepyB!F<5K{T}MK?uO(y_ZDV2 zP2euusd=OYPo27Rhq8e$6B^LJXw(^Zn@r=-W2Eqlux32a z0!K;*({o+<>qS5Cq`}(8lP&%Dc*)UWUGHA;%09`<;XC-hbmg%VU${Jc=HAP-za~6@ z&z}2ldd|i@YH)6AiITyg+BvLt{5~tu>4Q7Xt26u*V{{*~UdIw|Yd^1Y@xs~-cF*`% z|F>%gJazIK<3R7IOwFq;_?wwdZ(&_zuO}RL-beNp_Prb41eeg|rs=?OuCvYlh!>{x zK;k?3dJ=wyolhg*=@Y(I8{sp5FXr=^%@%&SdyICmp{#GQht~GI}Jac$63(F>7U7D3vbTsBXrb7FUS0q+)pXs>ExGD zi|iM5x78gg+v&RqHt>r7;yi<8bW8p)YqnpBzODCP{}&H4 z=ce3alGDS(?|z-~r(=1~dnLBJrE-s|%;3wzZ>$Rra$#1E`TT_(kgzuqoLqgbiS?%E zytq6vcf6qbjz40>=hglN(a+#FJSI6Tqh!45$8uHG5918uRsU)O+7E@VeZrUaA6_)N z-|^kA+yATkz5)Gfx!0J{MHRXoJ;1Z*U#X1%1MtbNnoiLCTQFU~_jedo9vrhS4sFbL z5#Ira+i&p7^8wlOUeulBlvw-1K|Q~xU5($~$2yF~+U^M^gF$6t-3-4iJl1Jre;evga^JZ=KUMsRZKW-<1;U&D%a8UPvA{z{JAJc!HO@lOI%Zub^6z%@ z6UQku%J5pig^hF>PV1EOk*tjwaeloxOveSk;)ehm^1FO%jD^ItFzAg9X74Tbg5QqU z66VzAm~9cSVq2VN*AIR3V}d!JH{ZmQ>OB_ydoEv)P50QIpSixFI@(95_>yQRKLI+S zS61Wx3t-$Y9boYY{pLISEBH_UHb(Tg;Abw5K1avdIP@L4;x5P124W-ao7&<&Ty!Jj zr(NbCqM^|?^}SNudMx02{0Zz-X=@pW%_TJ!^D#7zk_PEotI2A_S0-0&{MA;jg_p~* zI4;cJIhgus)A2ALkIv;w>~yg0{>|~~^4^9|RjsHgZHhu2<)ibp0f zz2Wa@uk0h$Nu|A+We#fiiLh*kA1`-P^-Y`2nT0MbWt^9xbvBMsv*rSR(s_~bq_4Ng zx4?#M-4b$lnMc@n@h*4|I;Lvoi9Sb(|IrgxM}CF-FVuqu=k?8EBg(TzC-9!K?his! z&-=FAp(Ib+-{XI!YbAbnKpXwx7wbbZRLd0b36@=*S&_!`YmE2d>g4%{g1_Va&no-S2Yz**+h}EcoA^vExKG!U9j9)0 z`Jd`SjmP50F1MM_m5dQw!XxP0z%$$Xl=RarYoSld)c71v>shZqHK#?7mUG$y56;`b z`gYI9(fQDwzH=QD#%=rU=bQP?Fpj6jMKKn1(c2d15iIvA&ZB&;?-BP2=WNILu5^*< zrCyXFp2eS)#u9jfdCm`6oTuQ+PTCgbT;AH~za8I$#{v9so$-}6->{1hw}WGL_}_i= zb9k26bUH8R9rG=4u_+z8Hz$uWEL@oXCwpr$T-ujWZXXQ zD(BtRr7wTK2d|r+Ol(>qOD1y>zBYNX@6t&%Ohx^fc{z@?Zl`5z#5FqSp&Co)JT$gl z%ACi-c0D&O#%MU$#^zw(>v0uzTk(_HAlaWE>3J?ZTKXF;yphhoQXel{w-GLHXr6oU zEt=;>{;YYVWqk0y&uM#--EHTy1&8J}|%@T)zKYU&=HHQ~3 zxjr>swK-?7_1GiEU9M-luTaPQ)J7Wb@p(~eJY&>i8)b*Gd15RB-0%7I=QtMQp(hk~ zW}T)Z?(A|rl|wVh{RQKh7#BDf*OELI!CW(qhx6>Qf=s`TbDDYIZN38Ec7l2E_|lQV zmv8;7e3oa~^Od#J(O2+cl9*sexvZ^G;GYbpmUsER2SYn=!q&C44hBB><9?=UsB;13 zOD#|CKFhl^6P;V6-08#f)lY5eT(e$`wNOU!=e5E6L$0)aLpL1KnM_x2wYv#csjGTN z?}&TUM}3nmwcF$xO1@Sv%)OevGg!uRuP(Q|Uw*Uy>eK&}vxt8D?COR7tH5F97gSz# zR?>Hq-mA-cKh^dzz1O#?_wrYk^{srrtoPo}0qtjA9^V|CW=~qrJ^zx9BW}TTZ6s?ER?Ul$ofE%55a? z*7eTYDDi%ycud>&^Z9`Z7`#tlu77K+{C-MhkduLQbfrIj_xKHt`ii**{qog=Rb}Oo zOD|8k&0`^N)5bqhc|GfQU7q(F;BR5>q~D`rWygBfyRt0%Asoid3o4^>+4&WE=if^l zzSX?Cp7fphI)c$*O7AR8#|vKSGX3WF^7u0rZ!Tlewa+$c@IhWw-G=Cp>U-XoZTP$s z8hX4O`la>CIu^5)?SUWo+l=dV_zLg`{cQJ}zJ@AC-ojSDs_Wn@5X+~3A43MmDCm9n zEvDww<7s>5_orp{qmTa^_OSSxMLni@jYsiw_>Xl4a?V&oJLmCs#t7eQ49q<%%`5Po zcqh0r*#E88&sdA2oyGYoiyu?I#OoeCh3j;!;GLkS)NC^M)sM-xp`jo=EQh z=r?)6%!fAmb1&K&h~{7C^?XWeTb$$byYfRkZ{OCsSVNfTJebI5$cL?Mz<=T+c(tCB z=$mk8_Z<125Yxp6sw?K3JX)>oD#v$%tb{u5{3z`!TzgG>$ZTFA-IlI@*;#-e(Y*jV6LfHw{%eof-~fEo| znz5bJW%sswdqHO={3uX+Zx)a@Nu>!$T^LFI(witcqoHoHJGPYYg2OTVLPjD_T z#GJixkxx|gaZ~1GMVm9hJ=(op1h-Rj%<;7S4CUI_(DufYTHF47<12gax%Zj%HBMr6vJ-WleUf)hy#sUMY}WOdFJZn2 zIS{FD!n5#3d9xqLTw+6f4RTpF$bXw$s@~zSmstCWhx=A_R-@;CmgPJ*EAYX&PbnYy zQ-h&wlJG-Wr?J;td7*ms+ZXPB5SUiqd?Gvl`i#n(?-lb7spDrMhP<7oJ>#J`_T_l) zn&rO7HY)euVj0H|{ZcoLR~mh#18aH~Je=}gnq!diY@4p4ugnh}x69jLAbed}`dW2$ z>HAsv7C4Y6_uI4nTX@j*Q<^{Xx_DbYxa}!ed}8Uy#rGZm%hv#s0fjOrSnNtyax7cLZ2 z8VVN9KCSLv`r1zT$YSlnpYQz&G;Y?)7|%>Jim+z7ZqoQ2ZYP-UNLFm`4sD}@0==6) zr}(O4%-I-II!gYLXYMPrbtUjD`SttbTlJL6biwX+(e-G`%is%sNqNd}mW+Kf`mZJr z%GJ?5x<^ue>}+F}anDj_6nHHgU*cnj^JR5cy7u;?JK#&k%zfS~-wL*ey7si&irrfq`+8=mL`DEb6>X10#4&^_jd-` z^T(OE)Zd7@2|pjbTtBPL?03}J!0v5>>-0I$t~tN+hU{m-gYX{k@&~MzqbA()jNbpW za$tso@7O-BS9~9rU{-G0CGy7Z6s*^ttaP98(h+^PGsSHkSKpB#;(t8T&S0o^;(Nx3 zPRVo65+AhS8Nt)+{h8!4i@XjLi$4|Ndu2{%;R-g+;vZ<;5<6xwjJPLL>mLT2=`_`Q z*~f*BgNA~;9lk{V3;58H&}^qQl3GV5V{q9uogQJ8jW&D%3$%D$usKgI$_76sn=jKAtV~Bk*Z&7kZ+t%~Y2K8E;^f9K@LF;9}NK z3vGJaGB5y?< z$=jON1J)R)XsO>}oi>A8d_bIVL+iBX7c4eqwrO)gcl-$UEv?P!e5W#qawg zhIL2v?78$=>$+7u_upb%KT$oq6rZ7){JFKw{@~{pdy8kePv@h;Vg7#A-s3~$@1M5! z?4QivZ`ynOowoPjTxa~R$qvUoozpmV`cLVu0+%VTOqCT5{d}c8p57f#cjA8!r`N3~ zI&nHqVqwugH2W$aqddE?>Ye-4`Z%7Uf7@}zp_=tMY@t2NFR}GT_b2r~iv#V^@aEGn z!6|m1dr#d*@_R(lRhg|%=k$HmQGCkowZOMjTnKx~8inlC+OqybTl~*zUTX--)??cb>z3m-!`OEH((>)rS56FHf&u7E;v;C{;B=*hk$?5--VCPJS>ld6BPS%iQ^d=CNbd~?8o#&CIsFk1`?jtz z^m@Uj8Z3JD@8E;q!FOun2>#yN_VV?9x$(4(q6~kz-A>jOMHgtuIRzG*ls%W9VfbB- za}0-FcA0%MHQfG8`&;ZWtd8HgD7rHq`xfb+;*+=#d0@}qQvGYnEq_V8A)RnErQg@$ z+1%0*af~&;;3pZr#AEc)i+!XtP5bjcXeZ=0C3o7YZ{BMi!R~6r=Q=DuioY`t-2I@( z$^AcVY3{1nYrJPvWmX?};=$(?^Omow_=x;v#gRDAl65rhuk(I+AIBo!ueqCtJAQcI z77y*##qD&?Xk75ZCO(vS+-1^X*~Djc9O`%4hQT$0S6a@P7ae@9m;0n>``e;z8lNJz zV!Zlrb@J4&23*5qgPz_?2JN?eTIQx+k9h+zRqa=(*GGuQWoO=u#51bD-F(L5s^Bn< ztp@)q@#`mzESOG|9C$8(<%9?}&n4nK&`tS+;3ec(RhOQLJ}h&WEFNgjPpYjiD6b&I zAmJZ)5M5~NtmI_J_J+LIZ^lLY=wbe+yy86678@$ZYi$VsJ`bB3cf_I3729wLP4#DI z-4e^<4l`)waTjkpvllJ=Vm3zgOuvY4fDOKo95u1tDC<54^fhqnU*Cw+Zb&!TovND4 z+W&Zl3A!ge-vr*WK7_-1eWi!q@HkL%KGjb2S6iGZTk}Aq3-CeP3-K&FbQtsnH-3xt<_jkNOn9);VW%X~knWJj>1=K`9}J6fGwRo|vV?DsvF|BUmZ?h-7PxX779zj-{cv-Zy1+Q59nVjEqc|$aJJGpsG z{0}cdi)i;uuaBhPM5ECOW3^QlpvhKajnFmyM_S zExl34N|JE^v*{a+*Y%y9(C6yPG{0(8Yg3hKI~c2*%y~g&NcRPoXHozgX>DTuKk^H&Rb4j^ewnR#{gf- zV?38IdApj2#wz1X89%tw zoGU-CQ)kj~rgf%o5?r*(=|SCr?e|wkoV3YkYWEyR*r(-%bl#odGU+$vVesF{_oKj% zB|d9BPMUcKKH4eS7@r%XD|~LXGFFb3d=t**IQ5U?914AcejV!UcgtK&RJ>v(=v|~ol>@8xe-xjGu_Z{@~hMFkW-Bwvo){_QMV~KV^uhX{yvY| z+GZYQFld}M4vmNT6yxR0SlWQbHm}l{%@*BHEGo3myo%q@in1=>Ssr%u;Zwe#os|Xd zNtXXC-p8+_eiuKcd=dI-*KI@2A+>$k34e_Oe5OMrqcxB0gj0>l<|dBU$n%CT=nsF0 zPcAHo?i!<=F$oSd7I0>B71iBb@Ubab7uKO6wG0mqFztRV^_qjqimouCZz?>-KI0cwPRmUc``7&{Q!tHFUI=+WKF7aypZ3%IEBW)%#il>R8$JhKo)3F&(K+P) zl5=4BC;I)U?@^OHa?bcr=ZwcQXD_}I&$(sJ-@7HmeJ#IO&nxl1d-~!Jy+WSLzGt3! zQ1dh8eJPi!Cmfv)^U4*q?P~yx3)}3m28VY*UX0*>)qLA_-bDACZQrl5`>P}Hh3B&7 z_-yn9`rqRT=#%Bs!T!*%|6YG2#>OAfp5@y@&H!b7u5bFPA|;fbHi@J!?BTwQN`PvU~*Ud=xzOW88dl z*IA3zC(n*U$}aaq%)D$5IQj1X!`!Tijac1Cmn))w+Mm}++vlC+(tL*~_eU!7EJmIa zkkDVg68i&ANq3%B{OETD`#*d0t_Q)ld{SHMu;1d7X9eS`;{Gl>;K|3deyF;#DSV#k zKdw0W@Z!IT^yQiNyiGdmr<@a)c2`3|E~v4)?`a*hq)2;u(3SL*R_11^D1lK6F*}s z!hSBol=itOJ|L&VV&LLG^Wl>7N)9tteFrZ3r)~B_Jza%;;de)v4X46|;iwA_`aTOD z2C+@(;&Efgk@1LR>uvfGhPOQ=f2_xdJ>^={$snzuN6(_3)hGU@7jkfDXN= zJDIOR2QOo^)H{EkvI#kV8{d!{)CGg)lWXWh+59!eo9V-(t?G`%kDV}%xieSC)k(*R z+j4X?@jS~@`zgU`cLgWDaa-M(>&_hA>OXYui1C}onfd4A#+F)sq+oJ?aPbb=4fR{c zn0D&tXS8qVlX3n6j)A+Y-vA9nIF9G|u z`2UFn|3%%X%hrSoqoJPHq%XcWb#BNAUm#ig`;vSMjlipu9JYRWThMc~Hp6B$owpFwTD-_(` zc&e0#P99J6DED|W$LuY&#KcoQsB)ICud?vi@m`SEGm~%XEU0+L<|XCW|JTo)L8| z4@kasuJ9K%e_H!9=NEk&T%CJ%^M6s!s@_{)w}rlHT^99Urr*+WYc7C2#dfB$<#844 z{EeI?c711p73&Yg+JUz{-PN+^KJ_1EJBT-?=Vt8cY>SSzY@RAw z5x@5wEcwjgd>Gj&KQF`YYRvrGLXVnFmwjWd9PCA6gU3niERMzF%%*>B{Yq;@iic8H z{;KSLk}nw7>^#1EAm%n`&rY3($#3c|kMF+xadoUJV^W6{wd|Wv1 zoxJwG-Vwfk0+!W_w+>$m~|2iM($us@) z|Hq5I{99O8ZE%{!ydo@=J)mKSBd^N>5F1E#3&EB^Ef<1zY|ONyHn^*PPB8G^VR}*TxF=#Nzo5`XlyAKgV?4b^LL8rQ z6QA_80(6~^%X!aW6I^LdAa%l>$Gs$Pv{{=-<0KycG+pX(l0LF@>zS*Qp2gSD3noK? z$HpD$UEh5gpPlCoCc#?n&AG8Vk2RKs(7*Ax+8@jZI+0gtcoB`Vb$H38@%kaf9%30A z=WcQuM=icf2j3Wu#5X3FPAh{c(Ni6DjXz_Z?d(}654FKLp7yngaW=ThV^6K?gyigq zzrgiCc{~1_`Srfrbk={o>+zk+uk-k?^QYT}DZik_J|W9n;%W4&;vZ{^+H=J30|%>< zJ^g_v#%bRV>ifa?eMR5($9LyN?z5*{FWzf!e5SgoJp;eSkJ`)1JQG^-ZcF-@y}4D{ zW`x1oiaNf`6LX!A`HpS8ki2`(pTXTi%U$Qij*3{c>p0m^lMl0@;tTP8b_X;(&mGYG z-@60)kIEb`#*VO$Lg?T7+f2!qHW7?72&h*lG_@t@w6WUENyk`IwO48f$mwKQ(n)E`*X(1`-wca zE}q-G5a@|rXAWVuUUt}XBaX*40dT}#y)=JRv0&wl%V$+R%SqL5_5`t>66Pi09`*Iu zhuy~=e~0$MelW&q_CJ2h?zI$M_-^wg^#lJ~zrr2#GG9%D4#ojT$i zera&m*hXt7`uAJ<&z`;a^d6Q;yUsA$3OD$B*o$*<9mv<&J%(;`8}WwI#c&Lrg?nhb zo$sF7C-^Jl-vTc46V)cXfXvVz^|SUP?X0Zm$M^U8ewms5`2t_?zWei$@6Dg*cj?dD z_vp{tclq7!&qIHECqLeO`6;}QbA4pGHu6M9)1n;hK*y|1G7i%Vf)|))zHL5}=#o^AdQ2p<9R)7aycldDegeJ( z=b2#-*P}gjJBW_NE)Dhlz|r3B?wxOe6Th9d@lo(4cf2dU(5>b(&ftHy>fn)lX4y~t zVRY{SV$Zzu-vzvGn-z0dGyAN%W}m6A9Dsq&*UxZWzzPhhpPAW%qOAEP*o|JuNj%q* zFLN26Qf2aXnwT=a339@@EUZtkR~&fRA7;K~j4iFGZ#ysiSjevN$1P zt*V$KsqY4(#{ImnnT)HGE3pmR-xcG^wjW)wSK`JO1T*n?=clfD9dkh7#x_#+M9krb zHqiW)miOxtpra-}ZFz;9C91h{n=fzi7~wO- z^TWCD;1PPl7luR8&gKy@4rcE)Cgw5Ft6qx`Eqpx>T@%Kd>|HPLi!ET@y1Wp0YYI0> z`BZlSpVKw)c=ucv`OU`O(x$IRI^X&DsF%?y)sGsVkMWuNq4mcv(+0F#9)DDOQ9iG~kE-6^RlUD!?W`XP zcAXdllnYah=qhw@sN+(`COao*6@FN^0sUkUclU*7zzcq+oLv+1s^|9qGa9v1JC zkL-HFV8Le^sl8Dc19qOXQ_NOC*CqR)Svlv5#O0@%WxC)z$xXwu!qfP<#{;5(Q^M!es*2w-;m3&AzaxxZNdq0 zSNw2bK?hYQ&i(9i|C{<#oYiRg=$UJyxvBek!dXqCz2IW*LL2yh%cH0Z>iUh&S37l_ z*!9-C8s5pY-wK|je=MJ{)4!73xbL~6O?|7%Ca}BDxQ^cgouzj?H^y`k&(e1*59qv# z(f+fiJ{f3Oe>)*R^#R4_fBdiABc1os`TIkxKJY#Is_`d`r{AVAVcV}~4a$Aq>#gfP z$V$syTvNCx=&&o8Tl}pwXRwypgMl8Uk7e^}(HvhhTboK?^7$BiN_(M=hMv2!BT2h8 z&#B!uzAF1MzA5V8OkBBtO25rGQ{5Eib{1b&8DwP8JHFO6$GNF^8#VLFdM;Vu!g%T_EIZzXK1;@u@!WN`QR8C=S(u5wQ5J4$o8s@3XR6-POQOw7bsylP z>^JeZ+zx6Vp?nYdS7!HvpLJe7Mr!+~&pk=}tbUqD*!TjaAK>=i_y<2leh6nOpv(Cq z?zAxu`lO06CVO`wymJ`-s>-e^w|3A|eZlPUDtrO<0Y4Sba>DQNZ(EAln9Ydo3vVy_ zx9$46Gs0H)hffsSU>;}vUA@)raiBePnUBHc{X6Qr@oAc~a%??U?ctZ(986;Y=XxJ} zo=M^ZL|O-*P_!9Ey^HUvI+-j&ca^7qixqLESsE)!+I4$`-)Hy)7PV==;T^T%dA3;} zEp5zf)5d~NV|5+Nn7vQn1Q+yAu1Fg$Hlf`D=WQy^DLm-e+Jdj3D>et5*3W8>-x>UZ zA@!47j+(Ky$4AgtK97P1_+zxaG*^Xl{vus^iks?;Z~L|$g zIg4V_@EG%k72y@004C;JQ+P|tJMYxf8@m+eaGc(jLx%|v6Z2q!e{r`;me<+NP`3Cp`9^vYW^6al% z80oylM>LN+-Fwya(zkjRn=n22fP4|fC49b&uG%`?JE?roE5t~wj}Pe11I>Zf->1G7 z)Yl=6Rdc2balXcRxUYj(;t2FT>YaT3zUs=>{Z6M}(A@p!HD|hIYR=^H=JO@w4Y^6> z+x2Ust5P4Op%=J0EqeW~=mvg?L*N@4e+fq4r(5~iY~v^g>TfynEj;3~1`KvCv*rQV z1HSz^F?MjPx+|&^_%NY$Vv|iK)&Kgfkxxwi1D&=VQ^L#8*?AVdf^K3C>i0Bex+AIQ z`oQsten$TQFMN$`HzkhaJ_g<{OPvc;|%sfAtFViOW z-1QH7k~q#8(Zb>%^}M$^-;6rfT5P`Yq;OrwAWnz5_}g|)*SOKYhWCZgf1q=sdy{f* z({?K3_QUu)(js3o>+XA}w)sx`RS%n+z&UDcZtZ_1JnA~m*Cja1LwQza$9q1960Gd+ z@YoLhAh*8eP)FEaW5Br<40KkqLbwn z>-@zzoxgZmHi5VryhvSim%~Fp^ub-F8!`5VyqaDVZ5g-6KLQ7K-*Z2I zBc9RjevRoM@hfneuPOSUQhA=K>-BYx@Jaf!*sy-X>-=V4;-+j{hJ#7qS-W#C{>BC~ zhBF$2%Z|>4c>&%E_-taM&ezvQeaY?Z@_qXGelGRNouQixQQj6Bd3*Wa3%(+28HnTe)mc6kd^Y%@C%D$f&Ds6-PGj?P z_n`I01FCyyNpsLpk9YLDqG#gN(26nOcO8)ac3oe;dsy%EM>!v}@s;_yp}sv!w^|v! z=fCNX`^8K012=zJ`6;n~?y++iC%U(_+P)yya1Ttt>3b}1-HKd?8x>&b4G`QdbbNWd#0?Y;z`bj)uFo{1L zUfb&%Wi90o-;ZhI)pvtMJZtmp*tgRd*##D(d!#Y-tS|HT0u78Nq66 z@w%*|ru!e$*qhq{&$GfGrGDmvjvN06nyBq;9+3Gp9X7`5IZvvOto=Sd?(}^I@Qd#Q zHyqHn_yFCS`7=p-KtC`q}qvZweOpfw_(Q4Hjz= zZ0LpcKXn-u%nN}}HX_aJj9c-#^UQ|yE8{c27MRD<|Kcs)O~c&i@oS@Ij+M@pm$ibw5+-$pU60Z*#k+Lzu<8wvlo-|cG`Gx1+ShxANxn=`>D z;$yQV{1@GkarnmL6X4-IJR|(0J9tjduWOT6WMfvD`jL`}gqG>JC6j^o#c$60=bZN= zFPdHz4}pL99vcUpY<-2_YQy&FsJzM=|4#FN5(Akub9eZHviPM&fAInLI^j>cJvZ5m zz7Ddfxq!vx;1iP-cwFD@xA|U_w$KFsLz@&*ErnblUS>v;KoyHKxTiA(GrbD@}Rev`ITHsvMgVf@h(j}U)`SM(dca^G9@dja3VzNL4}j2e9fU*SvCIxBty z7GNIA2Vd+drY(L8^Xvw9&}?Pl`eU!OkDm8=3cBCt)a~)tmY6{@7GO3x*-`uLi}s~1Gih*mJ^Z&V9|#9iFeUx%I`)_7GIU3pi;vB8 z+F=Jw&eeaD*VO1Ccsi9?{0xh$N^g+!O}ifFr7XN)aTw!k?U9z>+|wM2Z{Q4B&HS&9 zF(MP_8;g&E8>?T$H%E;;z(c-2JCmb=rwosRSvFH~{YY!#p!ht}QL>j)@^VUWJO062 z;1lNJF~>`?nE9mR>Q#Jzw2t{l$h&ZlY}Y)M*#FvpkpH%~HgOZzCK4QDlYg|=6B@tb zZCewFa*B-B_9)d)lCL@+P%?>KV{Q+v-su>1x}G6>{Z;f5f2Nn@{}5XMpS?Wq`hM&IF!rLj!mE?;X0mQ24jv`yOuR=d*% zc;y44=Yl>ZehL=|C+9!mFh ztMnFrXZ@SiOImOL%R%*@{f<3815=F1<8YExmMb@QHh>0|)Q<=TYXM z#p1mvl zb-eH!eoFa=b$+AOC*iq|T_%RRti1#H{;Q)8E5APA@Jlsr1C7=ni1i)_@UHehq&ep= z)p*$i=P|x${l@Q%ZF50*QoHMisz8^8=nEbSa}mn(=kz+GcA2Bl{^os^=o?|UKf-XI z>faygI=slhVn50YeMuL1yQVXw57-A0-U(Lq=X|Xk8N&&&H0K@k4|rl74jlqKJikwW zz-8l-Ua@gWKN|e<0nNviFGqRaQMQV8t*msCm36s~drhpK^y8xN1+P*cnOv1FpgeuA zNM4pt&)Iy0cN_gB@n7}osY8Kg#ALZEXW44^a+(w*Ia0`ES~?aS1Q18(CT5im@=?P5pho$@k4*!7t!Fe;F() z=W{0J!t5VD%wELa@HYp&AgB1#>3eJe>k|GRn|jJ~lvGx{ht0I_udBWEo+rKm^QQ1k zI|jdCt9|tXZtFuGbVs=TPvBhr(i0CpuYJhI56CIL#6lHfS`USNdbakZ_ugi9w&+55U-EOOY|TpZ zqMY*?dH^`uXlZdtjq~uZA_l*9IND*8P2ncW&Ff~<_1*YfcW+2%HTiiXled>7voBTW z{>nWKU$o0>21fyNE8lgLAqF=~TT|sGxoo)4296}lAt$gWno4i+jy!GV@8w)Q)r-K* z{GRggQwCSjpXWfpgPuo2{jB|#I=hKum9TZupMGn4we(3Y13J|FW9h`Xt4rV4{Q*(` zC%K+u@*0)X_}u%o@A8(DPyemo`QG6B|Kl5*fA~jlJo>f&_YK>_r#06V$v%0G$fV^u z$e+_)Z#V2E>gEqx-UNMOZ&s>H z;DwyZ?7jLoI?AWX-fx8W#v}MS)!+ro(-e%XqgtCUhcX6_zHiE(%Gx*@zMIeS$sm`@ z%JIy~XdI0BR6qERTk`Fe<%_MD5C6F3jBv+sb)ve2?`b&~@^OJVl>Mm6ve#6;wek-( ze?~S}F!Fu#XYkLe7nn~NjM&8={DZHNlYnfmO4s63Q5Rp9F`XIrUL^#Og_(TfLWKJ?U`jJsi(b*z|hxNmnO=Lwfv- z@=*%Ox^edC(l!?!FN)%%L2 zk9_{Yn2m3xo8Sp}_l$63FiQ_H*CO}VcqHmT=6&v$A$#x#YtvVs`%mRR{(|=3-V$P- z;*YD(%}efe_So?G?+xDgXKy_H$RED3`1qS|;17lMs&Mb>;9zy>({YWd_6MXFSf7eH z6fcOcg8bo2$S&hwF<<99bR<5q(I31BtR_d%zEMWw`Y!&*e+)0H+~EDyrPF>kp8KO( zcgp%x8QNN3(0kR{I<&NQ_}RNZyVchnSMdA7(wg^2to43xyH{P3ef&l6f*qyJK~MKE z{d#qT-{cJq<{u0989mp$d(3>%ec}DDDHieTOGjRRPJ8VS?z@e<0)gT6Ut2oz*x$eB zv#wDk*z>9CZe)*5zxcBgtH#nzKu<9Bd$+%!K`4g<{ zFX0)S1o{yRPhuDe?L3Z*J*1sdhle(yqrM*ud`&EnXYxScC8u4<2h=5(DJ%PM!29N3 z`ES4X^p$`4#`CxRA8)MwSC`+gvhw%919@{A*TtjBSu6NkFZaV9edB4yD}H7!*65iK zcY=SmgsbleS6jl>Vc`lqotTUDrMpbdrHiP`{&)Sq$atLhGki^&ywehH%bP3te*xKQ z;*>sjLY^GY_yO<>eBO#5@i#e8mRHYM#SbpS9v5`kIPqi0KeHh|hJN_jHs_Px+Yry& zJF~qF{lHJK2ZBexH{=U$+!x9Qn~JT#UaL;jWoaFLyWED{#h9N$TcXj%{h=*v1pT<3 z47Q zZ1C?%4!OTVV5^)b~65 zKm0e}8~*A`Zww#)lQ*{hr#IgqM#8>{Ezt(us(bq2z3RdPTIfTEccks2Lx$~sS5YI=0UfPh% zkb`A;QG)3;!TtmM4dkLPd6Z0IfADX!yq9r|9iVTeXIHE0ucJ4K3jp)&@i#hn@RQ&9 zI(hFG^?Tpo_-7vL-S)-92fzDu(fvirQqRUX_Av$;_`GaWFv?ztw}lse&+CshzELdi zuz*`+0J^E&AWPyOy<6<*S>q&@!S6cekkA|3RK4`+@7C?cZyTD=SzXQNY%D5wQNPo% zh^JmG>lqDuitDPr#SpHrlx2MZJCN2pGnP!ndbER0IN^Sz@oDt}`A?LgU1X8`G3V2`|L2AC3wkG< zvNj!f!+E0FpVF2KwVSz^$D%C$0w0--2qx$*oEs0MYrCTRj`O#UGE;Niq8$CX3}tgv zjUO2@eogpqM>yUM^=5#<#vScfHtqsHE}Q5N_#rDh$>Rph^Uj=Fys)JGxF@>3}E0aa-Fh@G)-W2-#&! zL5`}6&m`-hd>_v141L*~UeE7oOk)J85kHak)93b5Re-brAv{^8ZgFW63xJ8i08qj7Cl3!aViaJito+pJpe zx0I>Kld6t?sxGqwJkW1W_Ra4j4>7caRy)*BW#D@1f^X^>A4wNya?~c>*US8~Wq6TB zo+mE967wT`AB(roJ`7@TTMOvF;l-7ROK1(-(Bn__2Fl=%Qya*5AxmWNzd2)NUxG(%Qwd8f51aM@cIWyzC?Nk zTWNoopAWQ7EM>-S;t&=&SsvTk^%Z_SGi_o^=r4r(u(%F_QVYbn&vbYP(? zTlH4IWer+z;nx~GF|NtDU2g%CeUp5`BR0>AI?DU-JuSeVu1S~ccWxtFeaBhgFUq`Y z?7p ze=hoefrgD=0Zb*I`}!O*x|4rdsqLA4OJHd6EoYr$F_yvm&;9YbV&Apz(T2nE&GARz ze|*K%UEnA6;7Ws9>z;Poh;le-eI8=fc-oHyLa*kMcQ;`8R3h#-WB^Igxgx53MP2beM}0Z$o{_ZLJsmJu*sMz;z!o#$4t9k*~O&540Oxc-c<-(jsFf zyOA!AyMb`$cAB|`;n;n+h);`gL|vcBdO=IqVeZ2TKcC6?B!Ma2r{nFTr+~4u4{+}^ zj=Zx|`^XmbT~}OoBl7Q3ah6-4VdU+%@+>#O;`Vh*<2WqbGap*l`E8AEOx6L!Gvxol zgX@aN!*ArE^55vExul=Pz#1L%l2_`ycJfujJkQIrj<%tB5A~U2*%|`t0E1|^`7M}y zE=&C1^4fKd%b=Exur|z^0qxk@2{~{&|C;spr+m&Dzt=D@-}3x+l{23~Yx0_>h2Q&G z2dwOzMQ_{I6IJJ7KW9wirB92s$M$(P`bpl{pUd;PNJHk~%L`#`f_awpsdqlcSCYBg zdTbjWq+H;`WF4d6b@OrJdRfMUZRJh`-pa})vap?f-c}aG&$aw({8{@29Il(PGNTrs z=6dVJwp<5H`7tdz*yhCWn{;rhXY1dI{zXTiN4M%ZQCOeO^yy?;cI-T>sKZ@GkOPk? zcwgoRX+0U|5qF#+6UI&UhL~E8{_qT7xjLvA9+ziD^(F1kCGQ5hO9;3Q>%ZFl;=vjo za%U79B7UX1;rHE_|2Ogbhp}}@d!N{$4)G1nu}pD7d*H?3kNupDvP2vzt`A=Pjo3DJ zv&K)1{jhcke0;NblIkmtmYt2szIO0!zN!4v)i?J)2wt1>GPjq{P3c=-)2{7?^lmN# zw<`DY)2?In~Nlx@UiR-7PJH+e0 zM?x@IjcJ}L3)%t)wl_iF=#O{&EgzeEbtQRc z@iV<6=YVpxK8t05`JiG3WB#(r?^iwK zWPgvfuOvoSjNN0tw7D4lDUY#~|B)l!!JFo*MIOVqmQQOjL*s)eOVAH^fCV_ipx-I)YhB>kbIbglkCXQ){q4QrCGOHz7QDNE?%y>wYv0-tPg)!BThd0^zt@o* zzob01u+DZ!`$Kg0ySM9gu)#hb#$RT=f_!s-XE{6(hDE_g|L~J^`lZ9s-=LnC=R409 z_kTFpA#xsg@3?}d{_OPfZ@dS7^EcIB3LpIG@bf$HXp&D`@Tl=!?8{+3<33VfFh%&R zKRx$)0Z!9L#W!dJZ1CMR$rOE~E1AFGll@BcRTz)YM;%7W4e)tXkF_=*vwz=?SKDOA zcp>T(+Ca{?Ys2}yRn8Keb;*>C8+>W}_FLmg-_6o@Dr@*@__zn6-(vWdZ{+Zax9z*( zSUzwi-!1E7oWz-gQ}8Pq%5DYQ>iFele*RmM2k@f?!z7ufX}(>b zLxFxg*vx5-ib6 z>G=ux??y}Ie*1T$WsEzdJ_>q)Jpz@^-3c(o{>0ZV6B+-v@UgGOxF3+h=6IR!`=tSvC7r+HP;u=D|;{gGu{#Zt*CwH*JZWO?{H3l z=E8PPz&&UE{ZP7t@64wU{A6W&))#xdBdN}Dds@yF8l4*O;Qgm`qU};Afsg%F?+D*o z@gX@(@iF<|BR(2AZ15qxTEBun!-wR>-b+4qgO6~}U2|S#*Vvsl8pF=svKx5<{K8{xzHMonTn9|^V&fz84Lgc&fNxm0oARsXHN=B` zePaDsh{FIs`p)7ps!y!j{d4I)({;pN>l~_VYz;our=5S`aG=+KtA=ILF_%-h8yaJu zT&!EqT%9zwIn@O|2I57G)t`Iv$#`~M>G1_%VUA??Do#Qfa6`;$eId}>>WD8n>xed- zA2(EfQ)TS@#f7S|Ey4qHMD=~e?LqKQn)`lsv>9XJSMtN0t}WjczTtjiAG^y&C*OoU z1o%&D;+eIEPKkX@cm{fIDgWV&`oSM&uDBwY$UVcCKJ||S&*T649Z2{toPP_B0)J`V z;PH5s^ZAbBFMYOs{KC1}QSAv?kL?go zS!idxBRM0E3?7gZn}fo`8V~(t@}AW9zMVeZJU{JgS{?OeIX=$}p0^lvsM~w@ zLk(tqBPQ+lp`s&*p(>szeZibhzjY1jbIoxYpu_`T6_E#q#OE z4< z@kU&)F`WfpYrKoH(~Ad{OFnA$0M+Z+z@#!M&!x67J#W$EP;%pE3Zq+{7PP@wf=ni{ zo>8vLnb*~DBs_%tLtol#+DYRU)4p6M%&|{kY3qx9T4{gKwzW^SgHEC!WD(whzxI=t z@3<_+XEq`oV;`Px&ai>s>5D(RV?^x}hvC`yS$%9AOYE2MV!Qzl_3&F8+)}6XZ}d** z$OgTX>Id*Do-5+$gJfOT-Eq1>eBXLhMcxp zVR639;Eix}@kgr9Tn7S|(O)tDXQE!RchF_Ve7%`rDR44#y~(s6GP6A>`ZQjNe6l0% zin_qtDVr+Wnzx1C7B2B6(B1wU{e}M4T72UhwDdQ{lcS(_40o?p+FoB>t$8l_#yty` z%RJw$mz)ROM;ItC?3H-eqSMB8c(B!t-U6;v4&kjd-aU{^Z^pCn=m#0E`KseLvOLtc z!}^mgtLv08aA4X_o`IeGr7qmn_?mR{mHCuvC$!OwyVOf5Pi%>=u+6|vjB$v(>s;Q9 z&kDU+$3mpD%-@W2DEtOrS3T#kO7JMWgC~3m#ZE54bG$3#H>RCM@xV^{btk#l z31`?i^vI6+!v&AubbZ%P<`%L)ex8>3VZyQVdhHLNmOrdq8uN$075!n2#eG-&8e&S| z*!|&8yFaXQss9TOcuvp9GM_k!6Z%}jd}6hm`UUh6Jj3xJPqevIhkIS&WiKaTnX`gly(o2FeeLKI|d>P@>=Yv_lE#tIUjdXlxeCK-Vq;XvN#PL*s;Ysx!%ERZ^W%z3~h>vxVQ0{YLHs?x9UOzof5x-RvH_ zBiq-bHjgQ;B^c3XwVwx^Bk?6NDxRT@fcxSMr52cGmO+iVRfqPNhl)o813H7aL%sgc zGXM0rC%ou)0T|r`J2Xn?j%nPq>^q%g9?aQC;M4cLzw7SuOYoMC zyNxfm9eYA=mkrb1;%V0LOt!GOrCf;CX>7Zcsm#V@wnyvCWAUft$M1DV&f__B7Ng*f zUDidg7w87RBi&-ddS^LPidAha1U?VE%-p);T(D$}sSLx9re8ExY`~{uT*GD1V$7U< z!5(C-7tjxSEzF4%Sx?&E6%LbnDO_g%!H@9XxvnJqr*sEb7R%dC)>LO^niXZ;K4f%l zf#35WES9G+Fc$DeEDC%dR$OljDz76rh>*Ql>9<~OaWlpu@KI)<$>ftZL`9ghj zT9|)X9oadfgM2mW-Sjp2;d^LJ{{4Gv@~ynnqBA<<7(3{Wy?A4kPs!c8aZUbt_Q<`5 z;*Hh6`$ykD5%=|_HmA+c2zqqdpFvmFcCLljXR6ntlXo+3o$`z8IsNY8?UYT8t<`7W zZcSeLd%M`;PX2@PfYxfI~p4^~O#2qx*a7@l!G6gwI1fM7$#Xr<|7`x*XMX%?AG-o`c62 zEAzd@9x+$RrQT<26_fUOy3fP**5kvR#$&g{!S-@iLp;l4qZUux%UNxYi$vc9T-Ek> zoX0DX$-&-w{2!Gzawi|K#%H4E%XlF^YwFW%XCu6f$+y3mzH+MDxAU1@ z9)0~Ui_f*-Xvf&j_0-AMM1t~-*z4iD!mR;s}v*&JaJwC>?i6#1Bb4z&A}E1@=Q6}p-&^?|hB|oh`t2xFs zp%Z(=;@!Fz>wW%lzFzZ#=A7gmQ0_5o!O-r(ym&f0D|P?n+k`jv06vOOpnKWKJt0qT z+V8<4yTWzsMyLijDmVr?rf&3ltqwa4!)K3(t@wC#?qB>E!q z4d)bJu(J?!W|hY5IYAp^=^^3wJm*dw4SI<2u2z-ZS&kf`v+fXYI6rwlH~YOjx0-xF zV(ai3xVL;IeTQeb9~7DpvjKPZPJQqW`DCmfJB3%ea=TL*G@er3ZDU#m&KQf2F^dV! zT5gi-bf251u=rRkuPE|IsnZp&v%7UN*-zufZScglGPq+qHZJjZ9S1DxJKeInllHOL zd`9z3@JhSkysP0w@)i!?@ygfdq!$8jCizn8&*wxTzlply1+_5{T<8?zjo2Q)HFuqw zSCPkztk4!V#Lhf}H&mZ?7UTYImm!lCeFKm16nz34JPH4iR}3A%Kku8lM5A+f-xhD} zNxy_~CjNH|UR?&se+35Li`}~R+k$g=2V94~5}c;`RCry3^|DS_;c;~O!MvA@G-00wDMUxj;PZ;4(j89o(&Ef+N7}{ z*6;91mKUm5Iade&$+=sja#oRsDF0YCp*&(!m=`*+UVq;57h>Yx>MZO#yB z2A?u+&cEWGg&S@|=euWZjFLH;_)9+9CH7j>vfctG_(fh)85Eq^nI${)aWd;8!Z4+0 zlD@$M_(Wzmp^f^h&&vWw=#(g*(5B&)gy+XodSs1rK%*X6i+V)wme$~Z>5;WskIVq0 z*($++PH@@>dy~MBjLGed>38Zynge?q{xmMbvD>mP8r8mg!i%2gTec0qrE*}t6*@!k zc%78*XQ_WfbCDMO%$$eSq6<9sh)lg_)|17jsh#W^@SFe}xY|$68IuoD6k0Ee&=q=z&#;r7+LjJ2Pt`s{|5JC-fRvMtV%v1I3sU1u9z_~`PZgUt}%U~^5b56e$hbBJ5SQdgVGJSy0G%p_oeKH9+4K&_Ge5Rle zygWslHh!42&wWfT1rK((}rYJ;<=yREFMtk1#FYxRD??Q)NII1kKa z%k52@p7S#1^Yrk86W_E2{+Z~L;G#8;d6dM7`g?J)uDBTcFtfOr==~!d7b|=fLzt>?HE^?`ZtPMq&pz|pDCigGjS=G2x(*?z}kmr+ksi;u_<8iZt20NBw)rmkT*m0xRxW|Z=f$P}?6|n0*k?;W;FbA< z$M=A7a0BOu+8E`J0B@U3v+uHH*W*9dby~&&zPjS81KTtI7Q|OaVLk%?)Uc&`P%`$e zj=6T=5uQl>v~2D)&UrfZn`Y9rEq!`_foGC=NP){6iqqJ!#yHPS6=&e&~Mx?8O{RJSx^@=2h%x^in~9k zSo#r%pLOIj!fOf#YYI=L=QFb=DH>mWu9=^;ojG8-B(6()d|otp5g!%Vpih%q!75#7 z?|CO%YQN72e|~0r{a$O6E2oJq>)ce89q1hP)zRH&tGRfeZ(J{hj;vi`mzUL!^(Edp zPup>Bs4-ey_eZ3RP6A{iBQ`M_J>1DGRzuE(Si1 zY5i!uA(Nk?Tq$d+li!Dume=^0-v^qA9t&~KFS=~khv}AwTWocTPtnQqSUvV=I2CT| zwLp>WK8gN$qX>8aUqEzjS0>_aE>(Yb&J*_^Dw`#yP59MIJXFs*Y?v#NEHTJNR44 z9pa<*`8R!83_|knITc}CfesszQ*h$>AIrLLclpgHAI$O`t_ME}Jixtt3|$fQ9_tGi z1kd{T<)2;eAO94##OZ<_IjldOjr{E4m2ZBI|AXFFKE^%3Q*a3-+8^|^RxkYc8g_PX{0;VD@T>iLd>X&m`luJI z|1@?K@sr`K0|w;urs1Tcj~(e*)U)xn>8+w%tNcyy4fdV6I-b*Ag3Wn#r~7wb{@w9i zc5WRun{&Tcqz4&0_ZP3~USDXqd7pg!hk8f+d9@#Md9Z6M@*z(3pSXN_{CK##F4|=D zzGe|B&Bt z{iO2Qm@hwe=Gr9Y*r&J=Ur?vZPMqsM`A6BAJN*y`Jdf__2Ym`G2BXHnJeg;kHw%8{ zBK0CtFL|DbI#pj4;+TOUGXXYs=h5G z{ZP*AdBo>(+K=qE>eu}YB=dD&`qtK$!J%^Wb*K8_?9vn#`~kj^-^Y0WXU_Q+P8cI_ z%Ad&KrA+X7lKj3gJUKjbbu@P}++B~K8gNd0<}~Li3h%VJEIviI3f48@>`wkx-@NO= zW%1oX8Lq!O z4t}!HHsTom9O%9o`=7wH`sU*gJ}-aD=m$T;0}G-p=bb)Ot0 z4{==))`1fHR`fU1T6IgEGG66=K3hi_IM}XS=`&|#+hCRtX80AI zk%`TRmX6eNBAREuU0Z$OHm%<|<_>?F{N2}RL%hlvp=JvrAI-u$+4u^aIbPw@S<3CY z%xLGec;_W#a>w}M5B|aTdoeDO;k_7_$?rgVuH~GkdW}wW&DQ8dSM(%0s?KRj=xMoc zk~Q+zkgJrJou(%H1y2A=nirDJTV~>m1ct74XckWYFb^KFxnGSe}t1=$58-n8kbxjr%xlZN@uA@0aocV#{!tFLVrY+hCV@Ygb zA}i2kMPpl)zQ>+12SksZdL+ajLfvGpoz%sC5)&Z~;697XnDwi1WHQ!KcWSPS9g8x` z|NXbmrsp&Hiu9WD+sD^A-zn;K>dV+Ka5pqh{oydzXUrC}Qk-g2d6$+$f3!M+4DkC! z@d7>w`vF!bcb|RA&;HdnaEp&ZS^SE@QIoyTP|n^>o;dsH2SaSB_G#pI^KDsvv(aW) zeDBBa@}aGL`Qz3WKDqoh=A$t-Wxk#9nfiUYA7VM)&-#7AhHu`dI^qu-m&UQEvDIY` z#WIINnJ!$IFBZ?z_|1HbSZsteu zRT-=5oM)!;E8Nzyl)PvE2KnQy^ZiNg;}PS6kI{?R%0Z90PS_LU-(vO7F1CUY79=w1>poi+RAc?)Uyu`)qh?8;{0N zk4Ny@c+{`K$+s#QkDk-lhTY!#*w!6Z<)yK{wA*Ciau*;FfyW zIX_D)*tx`KPi@sW=xnpGq9xFJSKCG;n+7_dh`Y%r;JPe#NhY zM)1x+_YAJaJpjf(&{nX+Z`Fz5v&%2B9CiA4ehqvc=iReyV&FXg2-v$4*wc+znoByDfkk_JK z*{|b6a$)v(^X(J1H2huB{bdf zEYw6FxNIZS#5$I5jd=&|qv$=h;}-D>_JQBVH`~a*>m~HN(M)ylTZTRTS9`;GoeMD3 z-yQmUzs9f<_y`?Y^+LSdejg3LgRE?C7jBA9NbtfKT(3HhyYIR3fcA8Z_MbiV$$%@v zhsKraPviOB=$N8!0IH@;~u-g2XKgHX6vylJZJCZTW0oj)DlB#83%3bSg(cO zceVd1!ATu!5Kqzfl&)xjv6Cm-##q4WeCxcqaZGnjN#2%KH{dVmP4<-^5dY3aUNydr z{JgOc?oC^dx{+_#+qzEV^~|p;;9#BK`S7&#!kpFO^)*It&`d+<|}_*Kdq8Xxi#aHHQQ zw}~I0;KTKp%W>Ur{Sf?Lz3OX?@W1DSt!NylE;M#(^N`@O_-}Y;IErzp4aKF9 z$5Sz;=k1_-DQ9>!T@aovwv@nF=Fp*=vb96S)NGyclhvBTXSP!@He`c15dZC6oVgTn?Fs-TO1Q|QTLIIr^F|)SzIihTe0arj?P@ka8P!)W?w72rDd(GrSH`L1BRY>l>X=RA6Rz7OU8q0qtl1nq5qT@r}`C+Q~OlM zGNk*QPF=QVTY8M9^=VbEkE!qLoyP;4_{L)Kn=$su*m?uuIPw*`D6=2i;3>sX#)BPo zkV|0biFdY*C3Vy{dnEYNerKi)v877+j>wVt30bOXl=SCvhflb?5Nrnh?liueZ7b2= z=I?Q?Qj1Mx9F@k=raN4g&8L?fgLl#Os|Tw%?*s>TNdJ2ta?5vQ51C8rnO~)Phu@EE z4qu4h!&z*4|Fq{d(Z2L(&0|IT@R-p-WwwQ3z5@o;D`63V8y5Esmywpe;3;WPUmg&sYO3K zH%M!zHIF2H*YDE)1V3;AcLsA&hJMf^RXPWZb35SmIb@K{?R`=7H2uA8WsCWy%PlY* zxFxKc8f~NxS-UgXG;d=qkhz7+h2zQRUu`igUq3Wk4bE;0u}S7aw2dC4edK`{7XQ<^ zG2hCVpvMsnF44wFOPY4%7)s5k6b>s;{hLkRwnY-cD&U|?>63? zO#8X6(}=I(r`_ArIe|U>Ke%+|HvRtMi7#B<_so5l5C0^-oOtVKGcPh(I@mmCc%Xl^ zM;V)QKU`g69Kz`b^}qf`EMrS}8mh0u>Z_6-tji_vrgr3p;*4YUE&g*KAHPU?|0;AB zFJ7$<9KC1#mnGwx3!GEV?BM_Vi|EXN>3QKk#gAPk@hl_(A&LyceGAy~$2 z&u{WB*$c%SiQEkO_4nKG9Dc#c1&gXek1bqp2gWn{HgJqt`ZKivuoZ#eD19iZR5*C zer;VRiuA1QMnc0<9#dJ|PBzewv&Q&bhTP{S&WP_c{4vd2-^%;_UcP}ft#PgkI_-AE zExMw{Z&A0^mUqwq{RD2o&2I11ZtZ6V_;y1}^k$n+l8wI@+n(HBr`)#KKC>yZWB9o2 zWx@Wp`NoZJDt|Y^z)FLOwSgw(^W@v+7QcEoR#@) z*2Z?@9yPYT)>kck`aHnLBK=qAL}z`)_DqLJuh(tH_l(cx;V%a`>+eaQsV~30@HWPT z5*XYLx?kyY$0_=ZbGyKIw{KH(>`}n=6mE((y7li{bS|D~@k4Vi9%ESu{yaJYT$*l_ z9RDbti?=Nwir>;XZWd3M9s*y)Ynl7ge?`5%YByzd+G)3&`|+vfrNeA~WV%gptf6ee z;=JPBqqrVEYH}HTkEg{3$_di^t3Ct=+gTLi!G z_jaGJN1p@cXDxg1()F=!+cj+`X>-T9!#J^d@-?}$<()piuGEF-N_-mj2f2=v-Z0rF zzKK6p#?{(spd!;jc~QR<-#XQ3q!9n6+F2kNZvKSfDj9 zS#I~ipl^eJl;mBG8ebG$>bR@*XiGofb(ZyWi!JD`jou9VO^t^mj2S*6eD*!{ZO60l zHhDLLGgtfC@89pg2%fM@U21li8;smSowXCkHFRDg@8B9fs1oJ=5p{I z@e`e|@Qe67;AEx$1?`s)_R0Jp{T{q5-$?ar514+_C+i@AU$j48a~96Df>w4vP5d3& zds485oIBd{a#x>g?sW9Ifnwvee*t_0;c8R1&io1eu5-skW8ZJ&@pQMP_z^YT6Z>iH z#JPvoBVOe03cGtpxU95xzcC^w^7yB0Jydoz=$Ir|uf;bg&lSK&0C#?_4!oH91YNRg zvZk`3&uPB4_1w5Gt{&T@k>yl&jAoKu>Ok9bF;ClL$c}59hlw}n)9V3H1DuKXn%0VFJ3D78 zk*lEB_FuNM4zNLj-{2cH_r)wH`^T`Ap7&hxv%kM&yxhcd+GNmqvvaPV;MQf0I)Rr3 zr^d-|_xbG(np=zeW5&}m6mXHOyK9_`8QOB*w&d}W<-a)J$M0rGgzHouO8GR{gdgyS z4JiBK8GW19^L9O7GqYhySsUkqY=U4#hC)1Eac1co>sRn+btluh%5lbc!KK-EW(_L4s>mV(- zH+)B)P31m2J4bf$hR#qG?^eU0?@cyE4|oH9e9P~;n&A8YnDTCWCpoK#HFTr%v7oEI zE&l~LY=66KJ#%VcV=a529LhhqHTXm3@ARrm&$xdS%gG*H`i`Gtpf=T39ebu-TQ65m zH1>@1Fc6;*5}r4#Mjm5nlm>&w>#CNZN3%pZ1gF1Yp&i;lF0UCS|^mge`ikU zD2a=Rw(bwRjjD4C#^Fvy))&wJdhk!dyWgAW`WjgA-SAU$-!1X~THx)JqlEu5e_eRC zH7xbVeX*u1g>%l4<#)+%Wg^W2i8}V!IYls z(I%Vk4b4;*JAkf*Cx^^?w6%3(OM)m*LE0^$T|9 z63<=d%7bIKJL!BH9ZFl%bLnP3#mw_MXsPxBT!LNawK-0h+xdL1>PP)G4&!ac7si#| z`9!^e=B(lT3(oim`ryx)-w9{tlOGcPV_&z!Q=Ahg{23g=ndwoD-=2lPY;T2d%=_G* zyhr<~gm=js-!_j0UxKkYKjIIZA8EYeN%!RfP4QI|xg6&{47!dsck(}G)-|)VKWhB8 z+Q(46)>wH@a^-Ures|U;U74%vO0RuYxgSUF)tp%8 z3hf*Asw4i+p6Ao&>Q3bs9-(jPj?7Nu7XnAydvCAqW20#E2WnGrx78)zw(Y&Qd06L* z4PVw7of_YEmSa(WgFf4_cS8?I=5 zLO3qrVeTM)WsUfVe4{%DRsTrpGlD~8bW6U@7HiiDofuzDZ{ei=-AjI_d>|T8MlhJI z<9C@GNgUVWSmIm!I&As!uLi%)ZHmvAE5&sV^g>K$aVY+ke&LSgYtn13dpM7nbIq^? z#2I++`IfFrs(4P5KT8(sHEdu-j#9f2;Li1u$tL`!F)jN%JU-ia^*fW-RJJ91(7|)E zkXc~av3%Ss(ptWdQI9(SpOugH&wZrUdsP!XP)UE3`T-bi&0cgx=hfenzM&aD2l{?_ zAzUje)rE@C?w?i2WIJi^`j zLxGopcWOL+{Fb;r(+~5kdOjeVM}M?KOe^hoV3*=kL=~KGn}mp4jX-w z_{oW%roKODdNr(U9tdO-1gUVGV#MT_~BvA z$)b!H9M(=F^YoL>F`dRU`)#R=2@d9nyE@|`%7E*ScT4W_9zJtEMJJ#q;Fl?0j5OR% zZ|voi7W%Ky1K`g6j!e!YZnx9NF01G}kL9GkV{80bdI4HyeYL>kG~Y6N9L}5|Uhx&q zHGeB}!FBJu&%vb|GCy#~^Nosem~IjLQ{#=e&G>dkd^1Y4vEBh6-Ne}a5BXC~~)=MR^BdOR_c?-pBac20i6U|#$ze+wRF-uITC zKUVPeTPmNsyxZ+vBcBD1y5)a5He1W`>AkzSHy0$=2j%ZC1m6Yz2A9U~@@G7TAAFkT zuD2{EI|;VZ{O1loerCQY^e*uLx8K;>)P@I`8ehH3PoFhz35;&fr+hf^TWcJZ7^-X? z_#%etzlr?}-s=0bM@_tU?_TWU{+~G4viOYV_t<=5`nJv>S`8JG+NYdH&V%2(7rU4{ z7j_UFf}{4w*x;~OSafO_7mK)u0GT}bs#to-?4YkH1y2o0^^)-Q{SMR>$lD&TIc}( zSj^S>O8k~o-zq+ML-|ez-@$yR0%s<}t#k6X$R~fl{MdL0a=H$-WBx{;9Ee-BPo-z) zBNC%E-RN>2<16)Ep7`8T&Juhh9b)kpd{ujvY@OG;(^|uG|Cx0N(~pF^or#TSt z*oJ&*^T`>@LTGQ#Cl7eIDLy&pqg`(<_9*z