mirror of
https://github.com/logos-blockchain/logos-execution-zone-module.git
synced 2026-06-13 23:39:34 +00:00
add tests using the module test framework
add tests using the module test framework test fix update flake
This commit is contained in:
parent
5d42559db8
commit
310b6df8ec
40
.github/workflows/ci.yml
vendored
Normal file
40
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master, main]
|
||||
push:
|
||||
branches: [master, main]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
|
||||
- name: Setup Cachix
|
||||
uses: cachix/cachix-action@v15
|
||||
with:
|
||||
name: logos-co
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
|
||||
- name: Build module
|
||||
run: nix build -L
|
||||
|
||||
- name: Run tests
|
||||
run: nix build .#unit-tests -L
|
||||
5492
flake.lock
generated
5492
flake.lock
generated
File diff suppressed because it is too large
Load Diff
@ -18,5 +18,9 @@
|
||||
packages.default = "wallet";
|
||||
};
|
||||
};
|
||||
tests = {
|
||||
dir = ./tests;
|
||||
mockCLibs = [ "wallet_ffi" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
55
tests/CMakeLists.txt
Normal file
55
tests/CMakeLists.txt
Normal file
@ -0,0 +1,55 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(ExecutionZoneModuleTests LANGUAGES CXX)
|
||||
|
||||
include(LogosTest)
|
||||
|
||||
# Unit tests (mocked wallet_ffi)
|
||||
|
||||
logos_test(
|
||||
NAME execution_zone_module_tests
|
||||
MODULE_SOURCES
|
||||
../src/logos_execution_zone_wallet_module.cpp
|
||||
TEST_SOURCES
|
||||
main.cpp
|
||||
test_execution_zone.cpp
|
||||
MOCK_C_SOURCES
|
||||
mocks/mock_wallet_ffi.cpp
|
||||
EXTRA_INCLUDES
|
||||
stubs
|
||||
)
|
||||
|
||||
# The module targets C++20 (uses __uint128_t); LogosTest.cmake defaults to C++17.
|
||||
set_target_properties(execution_zone_module_tests PROPERTIES CXX_STANDARD 20)
|
||||
|
||||
# Integration tests (real wallet_ffi library)
|
||||
|
||||
find_library(WALLET_FFI_PATH
|
||||
NAMES wallet_ffi libwallet_ffi wallet libwallet
|
||||
PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../lib
|
||||
NO_DEFAULT_PATH)
|
||||
|
||||
if(WALLET_FFI_PATH)
|
||||
message(STATUS "[ExecutionZoneTests] wallet_ffi found: ${WALLET_FFI_PATH} - building integration tests")
|
||||
|
||||
logos_test(
|
||||
NAME execution_zone_module_integration_tests
|
||||
MODULE_SOURCES
|
||||
../src/logos_execution_zone_wallet_module.cpp
|
||||
TEST_SOURCES
|
||||
main.cpp
|
||||
test_execution_zone_integration.cpp
|
||||
EXTRA_INCLUDES
|
||||
../lib
|
||||
EXTRA_LINK_LIBS
|
||||
${WALLET_FFI_PATH}
|
||||
)
|
||||
|
||||
set_target_properties(execution_zone_module_integration_tests PROPERTIES CXX_STANDARD 20)
|
||||
|
||||
get_filename_component(WALLET_FFI_DIR "${WALLET_FFI_PATH}" DIRECTORY)
|
||||
set_target_properties(execution_zone_module_integration_tests PROPERTIES
|
||||
BUILD_RPATH "${WALLET_FFI_DIR}"
|
||||
)
|
||||
else()
|
||||
message(STATUS "[ExecutionZoneTests] wallet_ffi not found in ../lib - skipping integration tests")
|
||||
endif()
|
||||
3
tests/main.cpp
Normal file
3
tests/main.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
#include <logos_test.h>
|
||||
|
||||
LOGOS_TEST_MAIN()
|
||||
333
tests/mocks/mock_wallet_ffi.cpp
Normal file
333
tests/mocks/mock_wallet_ffi.cpp
Normal file
@ -0,0 +1,333 @@
|
||||
// Mock implementation of the wallet_ffi C functions.
|
||||
// Replaces the real lssa wallet library at link time during unit tests.
|
||||
// Return codes and out-parameter contents are controlled via LogosCMockStore.
|
||||
//
|
||||
// Conventions:
|
||||
// - Functions returning WalletFfiError use LOGOS_CMOCK_RETURN(int, "<fn>"); an
|
||||
// unset mock defaults to 0 (SUCCESS), so the happy path needs no setup.
|
||||
// - Out-parameters are filled with deterministic bytes only on success so tests
|
||||
// can assert on the resulting hex/JSON.
|
||||
|
||||
#include <logos_clib_mock.h>
|
||||
|
||||
extern "C" {
|
||||
#include <wallet_ffi.h>
|
||||
}
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
// A single non-null sentinel handle handed back by create_new / open.
|
||||
char g_fakeWallet = 0;
|
||||
|
||||
// Backing storage for list_accounts (returned by pointer to the caller).
|
||||
FfiAccountListEntry g_accountEntries[8];
|
||||
|
||||
// Fill a transfer result based on the mocked error code for `key`.
|
||||
WalletFfiError fillTransferResult(const char* key, FfiTransferResult* out_result) {
|
||||
const int err = LogosCMockStore::instance().getReturn<int>(key);
|
||||
if (out_result) {
|
||||
out_result->success = (err == 0);
|
||||
if (err == 0) {
|
||||
const char* tx = LogosCMockStore::instance().getReturnString("transfer_tx_hash");
|
||||
// getReturnString yields "" (non-null) when unset; fall back to a default.
|
||||
out_result->tx_hash = strdup((tx && *tx) ? tx : "0xmocktxhash");
|
||||
} else {
|
||||
out_result->tx_hash = nullptr;
|
||||
}
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
// === Lifecycle ===
|
||||
|
||||
WalletHandle* wallet_ffi_create_new(const char*, const char*, const char*) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_create_new");
|
||||
const int ok = LOGOS_CMOCK_RETURN(int, "wallet_ffi_create_new");
|
||||
return ok ? reinterpret_cast<WalletHandle*>(&g_fakeWallet) : nullptr;
|
||||
}
|
||||
|
||||
WalletHandle* wallet_ffi_open(const char*, const char*) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_open");
|
||||
const int ok = LOGOS_CMOCK_RETURN(int, "wallet_ffi_open");
|
||||
return ok ? reinterpret_cast<WalletHandle*>(&g_fakeWallet) : nullptr;
|
||||
}
|
||||
|
||||
int wallet_ffi_save(WalletHandle*) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_save");
|
||||
return LOGOS_CMOCK_RETURN(int, "wallet_ffi_save");
|
||||
}
|
||||
|
||||
void wallet_ffi_destroy(WalletHandle*) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_destroy");
|
||||
}
|
||||
|
||||
// === Account management ===
|
||||
|
||||
WalletFfiError wallet_ffi_create_account_public(WalletHandle*, FfiBytes32* out_id) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_create_account_public");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_create_account_public");
|
||||
if (err == 0 && out_id) {
|
||||
memset(out_id->data, 0xAB, sizeof(out_id->data));
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_create_account_private(WalletHandle*, FfiBytes32* out_id) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_create_account_private");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_create_account_private");
|
||||
if (err == 0 && out_id) {
|
||||
memset(out_id->data, 0xCD, sizeof(out_id->data));
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_list_accounts(WalletHandle*, FfiAccountList* out_list) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_list_accounts");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_list_accounts");
|
||||
if (!out_list) {
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
if (err == 0) {
|
||||
int count = LOGOS_CMOCK_RETURN(int, "list_accounts_count");
|
||||
if (count < 0) count = 0;
|
||||
if (count > 8) count = 8;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
memset(g_accountEntries[i].account_id.data, 0x10 + i, sizeof(g_accountEntries[i].account_id.data));
|
||||
g_accountEntries[i].is_public = (i % 2 == 0);
|
||||
}
|
||||
out_list->entries = g_accountEntries;
|
||||
out_list->count = static_cast<uintptr_t>(count);
|
||||
} else {
|
||||
out_list->entries = nullptr;
|
||||
out_list->count = 0;
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
void wallet_ffi_free_account_list(FfiAccountList* list) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_free_account_list");
|
||||
if (list) {
|
||||
list->entries = nullptr;
|
||||
list->count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// === Account queries ===
|
||||
|
||||
WalletFfiError wallet_ffi_get_balance(WalletHandle*, const FfiBytes32*, bool, uint8_t (*out_balance)[16]) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_balance");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_get_balance");
|
||||
if (err == 0 && out_balance) {
|
||||
const uint64_t value = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "get_balance_value"));
|
||||
memset(*out_balance, 0, 16);
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
(*out_balance)[i] = static_cast<uint8_t>((value >> (i * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
static WalletFfiError fillAccount(const char* key, FfiAccount* out_account) {
|
||||
const int err = LogosCMockStore::instance().getReturn<int>(key);
|
||||
if (err == 0 && out_account) {
|
||||
memset(out_account->program_owner.data, 0xAA, sizeof(out_account->program_owner.data));
|
||||
memset(out_account->balance.data, 0, sizeof(out_account->balance.data));
|
||||
out_account->balance.data[0] = 0x07;
|
||||
memset(out_account->nonce.data, 0, sizeof(out_account->nonce.data));
|
||||
out_account->nonce.data[0] = 0x01;
|
||||
out_account->data = nullptr;
|
||||
out_account->data_len = 0;
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_get_account_public(WalletHandle*, const FfiBytes32*, FfiAccount* out_account) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_account_public");
|
||||
return fillAccount("wallet_ffi_get_account_public", out_account);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_get_account_private(WalletHandle*, const FfiBytes32*, FfiAccount* out_account) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_account_private");
|
||||
return fillAccount("wallet_ffi_get_account_private", out_account);
|
||||
}
|
||||
|
||||
void wallet_ffi_free_account_data(FfiAccount* account) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_free_account_data");
|
||||
if (account && account->data) {
|
||||
free(account->data);
|
||||
account->data = nullptr;
|
||||
account->data_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_get_public_account_key(WalletHandle*, const FfiBytes32*, FfiPublicAccountKey* out_key) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_public_account_key");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_get_public_account_key");
|
||||
if (err == 0 && out_key) {
|
||||
memset(out_key->public_key.data, 0xBE, sizeof(out_key->public_key.data));
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_get_private_account_keys(WalletHandle*, const FfiBytes32*, FfiPrivateAccountKeys* out_keys) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_private_account_keys");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_get_private_account_keys");
|
||||
if (err == 0 && out_keys) {
|
||||
memset(out_keys->nullifier_public_key.data, 0xEF, sizeof(out_keys->nullifier_public_key.data));
|
||||
out_keys->viewing_public_key = nullptr;
|
||||
out_keys->viewing_public_key_len = 0;
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
void wallet_ffi_free_private_account_keys(FfiPrivateAccountKeys* keys) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_free_private_account_keys");
|
||||
if (keys && keys->viewing_public_key) {
|
||||
free(keys->viewing_public_key);
|
||||
keys->viewing_public_key = nullptr;
|
||||
keys->viewing_public_key_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// === Account encoding ===
|
||||
|
||||
char* wallet_ffi_account_id_to_base58(const FfiBytes32*) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_account_id_to_base58");
|
||||
const char* str = LOGOS_CMOCK_RETURN_STRING("wallet_ffi_account_id_to_base58");
|
||||
return strdup((str && *str) ? str : "MockBase58Address");
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_account_id_from_base58(const char*, FfiBytes32* out_id) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_account_id_from_base58");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_account_id_from_base58");
|
||||
if (err == 0 && out_id) {
|
||||
memset(out_id->data, 0x5A, sizeof(out_id->data));
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
void wallet_ffi_free_string(char* s) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_free_string");
|
||||
if (s) {
|
||||
free(s);
|
||||
}
|
||||
}
|
||||
|
||||
// === Blockchain synchronisation ===
|
||||
|
||||
int wallet_ffi_sync_to_block(WalletHandle*, uint64_t) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_sync_to_block");
|
||||
return LOGOS_CMOCK_RETURN(int, "wallet_ffi_sync_to_block");
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_get_last_synced_block(WalletHandle*, uint64_t* out_block_id) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_last_synced_block");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_get_last_synced_block");
|
||||
if (err == 0 && out_block_id) {
|
||||
*out_block_id = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "last_synced_block_value"));
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_get_current_block_height(WalletHandle*, uint64_t* out_block_height) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_current_block_height");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_get_current_block_height");
|
||||
if (err == 0 && out_block_height) {
|
||||
*out_block_height = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "current_block_height_value"));
|
||||
}
|
||||
return static_cast<WalletFfiError>(err);
|
||||
}
|
||||
|
||||
// === Pinata claiming ===
|
||||
|
||||
WalletFfiError wallet_ffi_claim_pinata(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_claim_pinata");
|
||||
return fillTransferResult("wallet_ffi_claim_pinata", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_claim_pinata_private_owned_already_initialized(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16],
|
||||
uintptr_t, const uint8_t (*)[32], uintptr_t, FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_claim_pinata_private_owned_already_initialized");
|
||||
return fillTransferResult("wallet_ffi_claim_pinata_private_owned_already_initialized", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_claim_pinata_private_owned_not_initialized(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_claim_pinata_private_owned_not_initialized");
|
||||
return fillTransferResult("wallet_ffi_claim_pinata_private_owned_not_initialized", out_result);
|
||||
}
|
||||
|
||||
// === Transfers / registration ===
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_public(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_public");
|
||||
return fillTransferResult("wallet_ffi_transfer_public", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_shielded(
|
||||
WalletHandle*, const FfiBytes32*, const FfiPrivateAccountKeys*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_shielded");
|
||||
return fillTransferResult("wallet_ffi_transfer_shielded", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_deshielded(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_deshielded");
|
||||
return fillTransferResult("wallet_ffi_transfer_deshielded", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_private(
|
||||
WalletHandle*, const FfiBytes32*, const FfiPrivateAccountKeys*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_private");
|
||||
return fillTransferResult("wallet_ffi_transfer_private", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_shielded_owned(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_shielded_owned");
|
||||
return fillTransferResult("wallet_ffi_transfer_shielded_owned", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_private_owned(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_private_owned");
|
||||
return fillTransferResult("wallet_ffi_transfer_private_owned", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_register_public_account(WalletHandle*, const FfiBytes32*, FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_register_public_account");
|
||||
return fillTransferResult("wallet_ffi_register_public_account", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_register_private_account(WalletHandle*, const FfiBytes32*, FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_register_private_account");
|
||||
return fillTransferResult("wallet_ffi_register_private_account", out_result);
|
||||
}
|
||||
|
||||
void wallet_ffi_free_transfer_result(FfiTransferResult* result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_free_transfer_result");
|
||||
if (result && result->tx_hash) {
|
||||
free(result->tx_hash);
|
||||
result->tx_hash = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// === Configuration ===
|
||||
|
||||
char* wallet_ffi_get_sequencer_addr(WalletHandle*) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_sequencer_addr");
|
||||
const char* addr = LOGOS_CMOCK_RETURN_STRING("wallet_ffi_get_sequencer_addr");
|
||||
return strdup((addr && *addr) ? addr : "127.0.0.1:3000");
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
174
tests/stubs/wallet_ffi.h
Normal file
174
tests/stubs/wallet_ffi.h
Normal file
@ -0,0 +1,174 @@
|
||||
// Stub header for wallet_ffi — provides the same declarations as the real
|
||||
// lssa-generated header so that logos_execution_zone_wallet_module sources
|
||||
// compile in unit tests (the real library is NOT linked; mocks supply symbols).
|
||||
//
|
||||
// Only the subset of the FFI surface used by the module is declared here.
|
||||
|
||||
#ifndef WALLET_FFI_H
|
||||
#define WALLET_FFI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Error codes returned by most wallet_ffi_* functions (0 == SUCCESS).
|
||||
typedef enum WalletFfiError {
|
||||
SUCCESS = 0,
|
||||
INTERNAL_ERROR = 1,
|
||||
INVALID_INPUT = 2,
|
||||
NOT_FOUND = 3,
|
||||
} WalletFfiError;
|
||||
|
||||
// Opaque wallet handle.
|
||||
typedef struct WalletHandle WalletHandle;
|
||||
|
||||
// 32-byte value (account id, key, hash).
|
||||
typedef struct FfiBytes32 {
|
||||
uint8_t data[32];
|
||||
} FfiBytes32;
|
||||
|
||||
// 16-byte value (balance / nonce, little-endian u128).
|
||||
typedef struct FfiBytes16 {
|
||||
uint8_t data[16];
|
||||
} FfiBytes16;
|
||||
|
||||
// Full account record.
|
||||
typedef struct FfiAccount {
|
||||
FfiBytes32 program_owner;
|
||||
FfiBytes16 balance;
|
||||
FfiBytes16 nonce;
|
||||
uint8_t* data;
|
||||
uintptr_t data_len;
|
||||
} FfiAccount;
|
||||
|
||||
// One entry in a listing of accounts.
|
||||
typedef struct FfiAccountListEntry {
|
||||
FfiBytes32 account_id;
|
||||
bool is_public;
|
||||
} FfiAccountListEntry;
|
||||
|
||||
// A list of accounts (heap-owned by the library).
|
||||
typedef struct FfiAccountList {
|
||||
FfiAccountListEntry* entries;
|
||||
uintptr_t count;
|
||||
} FfiAccountList;
|
||||
|
||||
// Public account key.
|
||||
typedef struct FfiPublicAccountKey {
|
||||
FfiBytes32 public_key;
|
||||
} FfiPublicAccountKey;
|
||||
|
||||
// Private account keys (viewing key is heap-owned).
|
||||
typedef struct FfiPrivateAccountKeys {
|
||||
FfiBytes32 nullifier_public_key;
|
||||
uint8_t* viewing_public_key;
|
||||
uintptr_t viewing_public_key_len;
|
||||
} FfiPrivateAccountKeys;
|
||||
|
||||
// Result of a transfer / claim / register operation.
|
||||
typedef struct FfiTransferResult {
|
||||
bool success;
|
||||
char* tx_hash;
|
||||
} FfiTransferResult;
|
||||
|
||||
// === Lifecycle ===
|
||||
|
||||
WalletHandle* wallet_ffi_create_new(const char* config_path, const char* storage_path, const char* password);
|
||||
WalletHandle* wallet_ffi_open(const char* config_path, const char* storage_path);
|
||||
int wallet_ffi_save(WalletHandle* handle);
|
||||
void wallet_ffi_destroy(WalletHandle* handle);
|
||||
|
||||
// === Account management ===
|
||||
|
||||
WalletFfiError wallet_ffi_create_account_public(WalletHandle* handle, FfiBytes32* out_id);
|
||||
WalletFfiError wallet_ffi_create_account_private(WalletHandle* handle, FfiBytes32* out_id);
|
||||
WalletFfiError wallet_ffi_list_accounts(WalletHandle* handle, FfiAccountList* out_list);
|
||||
void wallet_ffi_free_account_list(FfiAccountList* list);
|
||||
|
||||
// === Account queries ===
|
||||
|
||||
WalletFfiError wallet_ffi_get_balance(WalletHandle* handle, const FfiBytes32* account_id, bool is_public, uint8_t (*out_balance)[16]);
|
||||
WalletFfiError wallet_ffi_get_account_public(WalletHandle* handle, const FfiBytes32* account_id, FfiAccount* out_account);
|
||||
WalletFfiError wallet_ffi_get_account_private(WalletHandle* handle, const FfiBytes32* account_id, FfiAccount* out_account);
|
||||
void wallet_ffi_free_account_data(FfiAccount* account);
|
||||
WalletFfiError wallet_ffi_get_public_account_key(WalletHandle* handle, const FfiBytes32* account_id, FfiPublicAccountKey* out_key);
|
||||
WalletFfiError wallet_ffi_get_private_account_keys(WalletHandle* handle, const FfiBytes32* account_id, FfiPrivateAccountKeys* out_keys);
|
||||
void wallet_ffi_free_private_account_keys(FfiPrivateAccountKeys* keys);
|
||||
|
||||
// === Account encoding ===
|
||||
|
||||
char* wallet_ffi_account_id_to_base58(const FfiBytes32* account_id);
|
||||
WalletFfiError wallet_ffi_account_id_from_base58(const char* base58, FfiBytes32* out_id);
|
||||
void wallet_ffi_free_string(char* s);
|
||||
|
||||
// === Blockchain synchronisation ===
|
||||
|
||||
int wallet_ffi_sync_to_block(WalletHandle* handle, uint64_t block_id);
|
||||
WalletFfiError wallet_ffi_get_last_synced_block(WalletHandle* handle, uint64_t* out_block_id);
|
||||
WalletFfiError wallet_ffi_get_current_block_height(WalletHandle* handle, uint64_t* out_block_height);
|
||||
|
||||
// === Pinata claiming ===
|
||||
|
||||
WalletFfiError wallet_ffi_claim_pinata(
|
||||
WalletHandle* handle,
|
||||
const FfiBytes32* pinata_account_id,
|
||||
const FfiBytes32* winner_account_id,
|
||||
const uint8_t (*solution)[16],
|
||||
FfiTransferResult* out_result);
|
||||
|
||||
WalletFfiError wallet_ffi_claim_pinata_private_owned_already_initialized(
|
||||
WalletHandle* handle,
|
||||
const FfiBytes32* pinata_account_id,
|
||||
const FfiBytes32* winner_account_id,
|
||||
const uint8_t (*solution)[16],
|
||||
uintptr_t winner_proof_index,
|
||||
const uint8_t (*winner_proof_siblings)[32],
|
||||
uintptr_t winner_proof_siblings_len,
|
||||
FfiTransferResult* out_result);
|
||||
|
||||
WalletFfiError wallet_ffi_claim_pinata_private_owned_not_initialized(
|
||||
WalletHandle* handle,
|
||||
const FfiBytes32* pinata_account_id,
|
||||
const FfiBytes32* winner_account_id,
|
||||
const uint8_t (*solution)[16],
|
||||
FfiTransferResult* out_result);
|
||||
|
||||
// === Transfers / registration ===
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_public(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiBytes32* to,
|
||||
const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_shielded(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiPrivateAccountKeys* to_keys,
|
||||
const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_deshielded(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiBytes32* to,
|
||||
const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_private(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiPrivateAccountKeys* to_keys,
|
||||
const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_shielded_owned(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiBytes32* to,
|
||||
const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_private_owned(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiBytes32* to,
|
||||
const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
|
||||
WalletFfiError wallet_ffi_register_public_account(WalletHandle* handle, const FfiBytes32* account_id, FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_register_private_account(WalletHandle* handle, const FfiBytes32* account_id, FfiTransferResult* out_result);
|
||||
|
||||
void wallet_ffi_free_transfer_result(FfiTransferResult* result);
|
||||
|
||||
// === Configuration ===
|
||||
|
||||
char* wallet_ffi_get_sequencer_addr(WalletHandle* handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // WALLET_FFI_H
|
||||
429
tests/test_execution_zone.cpp
Normal file
429
tests/test_execution_zone.cpp
Normal file
@ -0,0 +1,429 @@
|
||||
// Unit tests for LogosExecutionZoneWalletModule.
|
||||
// All wallet_ffi C functions are mocked at link time via mock_wallet_ffi.cpp.
|
||||
|
||||
#include <logos_test.h>
|
||||
#include "logos_execution_zone_wallet_module.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
// 64-char hex string = 32 bytes (valid account id).
|
||||
static const QString VALID_ID = QString(64, 'a');
|
||||
static const QString VALID_ID_2 = QString(64, 'b');
|
||||
// 32-char hex string = 16 bytes (valid amount / solution).
|
||||
static const QString VALID_U128 = QString(32, '1');
|
||||
|
||||
static QJsonObject parseObject(const QString& json) {
|
||||
return QJsonDocument::fromJson(json.toUtf8()).object();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Plugin metadata
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(name_and_version) {
|
||||
LogosExecutionZoneWalletModule module;
|
||||
LOGOS_ASSERT_EQ(module.name(), QStringLiteral("logos_execution_zone"));
|
||||
LOGOS_ASSERT_EQ(module.version(), QStringLiteral("1.0.0"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Account management
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(create_account_public_returns_hex_on_success) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString id = module.create_account_public();
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_create_account_public"));
|
||||
// Mock fills the id with 0xAB bytes -> 64 hex chars ("ab" x 32).
|
||||
LOGOS_ASSERT_EQ(id, QString("ab").repeated(32));
|
||||
}
|
||||
|
||||
LOGOS_TEST(create_account_public_returns_empty_on_error) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_create_account_public").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.create_account_public().isEmpty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(create_account_private_returns_hex_on_success) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString id = module.create_account_private();
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_create_account_private"));
|
||||
LOGOS_ASSERT_EQ(id, QString("cd").repeated(32));
|
||||
}
|
||||
|
||||
LOGOS_TEST(list_accounts_maps_entries) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("list_accounts_count").returns(3);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonArray accounts = module.list_accounts();
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_list_accounts"));
|
||||
LOGOS_ASSERT_EQ(accounts.size(), 3);
|
||||
|
||||
// list_accounts appends JSON objects (ffiAccountListEntryToJson returns a
|
||||
// QJsonObject); entry 0 is public, entry 1 is private.
|
||||
const QJsonObject e0 = accounts[0].toObject();
|
||||
LOGOS_ASSERT_TRUE(e0["is_public"].toBool());
|
||||
LOGOS_ASSERT_EQ(e0["account_id"].toString(), QString("10").repeated(32));
|
||||
const QJsonObject e1 = accounts[1].toObject();
|
||||
LOGOS_ASSERT_FALSE(e1["is_public"].toBool());
|
||||
}
|
||||
|
||||
LOGOS_TEST(list_accounts_empty_on_error) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_list_accounts").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.list_accounts().size(), 0);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Account queries
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(get_balance_invalid_hex_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.get_balance(QStringLiteral("not-hex"), true).isEmpty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_get_balance"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_balance_returns_decimal_string) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("get_balance_value").returns(123456789);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString balance = module.get_balance(VALID_ID, true);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_get_balance"));
|
||||
LOGOS_ASSERT_EQ(balance, QStringLiteral("123456789"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_balance_zero) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("get_balance_value").returns(0);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_balance(VALID_ID, false), QStringLiteral("0"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_balance_string_overload_parses_bool) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("get_balance_value").returns(42);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_balance(VALID_ID, QStringLiteral("true")), QStringLiteral("42"));
|
||||
LOGOS_ASSERT_EQ(module.get_balance(VALID_ID, QStringLiteral("1")), QStringLiteral("42"));
|
||||
LOGOS_ASSERT_EQ(module.get_balance(VALID_ID, QStringLiteral("yes")), QStringLiteral("42"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_account_public_returns_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString json = module.get_account_public(VALID_ID);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_get_account_public"));
|
||||
const QJsonObject obj = parseObject(json);
|
||||
// program_owner mocked to 0xAA bytes.
|
||||
LOGOS_ASSERT_EQ(obj["program_owner"].toString(), QString("aa").repeated(32));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_account_public_invalid_hex_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.get_account_public(QStringLiteral("zz")).isEmpty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_get_account_public"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_public_account_key_returns_hex) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_public_account_key(VALID_ID), QString("be").repeated(32));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_private_account_keys_returns_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.get_private_account_keys(VALID_ID));
|
||||
LOGOS_ASSERT_EQ(obj["nullifier_public_key"].toString(), QString("ef").repeated(32));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Account encoding
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(account_id_to_base58_invalid_hex_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.account_id_to_base58(QStringLiteral("xyz")).isEmpty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(account_id_to_base58_returns_string) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_account_id_to_base58").returns("SomeBase58Value");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.account_id_to_base58(VALID_ID), QStringLiteral("SomeBase58Value"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(account_id_from_base58_returns_hex) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.account_id_from_base58(QStringLiteral("anything")), QString("5a").repeated(32));
|
||||
}
|
||||
|
||||
LOGOS_TEST(account_id_from_base58_error_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_account_id_from_base58").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.account_id_from_base58(QStringLiteral("anything")).isEmpty());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Blockchain synchronisation
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(sync_to_block_string_invalid_returns_negative_one) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.sync_to_block(QStringLiteral("notnum")), -1);
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_sync_to_block"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(sync_to_block_string_valid_forwards) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_sync_to_block").returns(7);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.sync_to_block(QStringLiteral("100")), 7);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_sync_to_block"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_last_synced_block_returns_value) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("last_synced_block_value").returns(55);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_last_synced_block(), 55);
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_last_synced_block_error_returns_zero) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_get_last_synced_block").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_last_synced_block(), 0);
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_current_block_height_returns_value) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("current_block_height_value").returns(999);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_current_block_height(), 999);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Transfers / registration
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(transfer_public_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.transfer_public(VALID_ID, VALID_ID_2, VALID_U128));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_transfer_public"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].toBool());
|
||||
LOGOS_ASSERT_EQ(obj["tx_hash"].toString(), QStringLiteral("0xmocktxhash"));
|
||||
LOGOS_ASSERT_TRUE(obj["error"].toString().isEmpty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(transfer_public_invalid_hex_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.transfer_public(QStringLiteral("bad"), VALID_ID_2, VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].toBool());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].toString().isEmpty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_transfer_public"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(transfer_public_invalid_amount_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.transfer_public(VALID_ID, VALID_ID_2, QStringLiteral("ff")));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].toBool());
|
||||
LOGOS_ASSERT_CONTAINS(obj["error"].toString().toStdString(), std::string("amount"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(transfer_public_ffi_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_transfer_public").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.transfer_public(VALID_ID, VALID_ID_2, VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].toBool());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].toString().isEmpty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(transfer_shielded_invalid_keys_json_error) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
// to_keys_json is not valid JSON object -> parse failure.
|
||||
const QJsonObject obj = parseObject(module.transfer_shielded(VALID_ID, QStringLiteral("not-json"), VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].toBool());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_transfer_shielded"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(transfer_shielded_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString keysJson = QStringLiteral("{\"nullifier_public_key\":\"") + QString(64, 'a') + QStringLiteral("\"}");
|
||||
const QJsonObject obj = parseObject(module.transfer_shielded(VALID_ID, keysJson, VALID_U128));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_transfer_shielded"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].toBool());
|
||||
}
|
||||
|
||||
LOGOS_TEST(register_public_account_invalid_hex_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.register_public_account(QStringLiteral("bad")));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].toBool());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_register_public_account"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(register_private_account_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.register_private_account(VALID_ID));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_register_private_account"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].toBool());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pinata claiming
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(claim_pinata_invalid_hex_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.claim_pinata(QStringLiteral("bad"), VALID_ID_2, VALID_U128).isEmpty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_claim_pinata"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(claim_pinata_invalid_solution_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.claim_pinata(VALID_ID, VALID_ID_2, QStringLiteral("ab")).isEmpty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_claim_pinata"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(claim_pinata_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QJsonObject obj = parseObject(module.claim_pinata(VALID_ID, VALID_ID_2, VALID_U128));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_claim_pinata"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].toBool());
|
||||
}
|
||||
|
||||
LOGOS_TEST(claim_pinata_already_initialized_invalid_siblings_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
// siblings json is not an array -> parse failure.
|
||||
const QString result = module.claim_pinata_private_owned_already_initialized(
|
||||
VALID_ID, VALID_ID_2, VALID_U128, 0, QStringLiteral("not-an-array"));
|
||||
LOGOS_ASSERT_TRUE(result.isEmpty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_claim_pinata_private_owned_already_initialized"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(claim_pinata_already_initialized_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString siblings = QStringLiteral("[\"") + QString(64, 'a') + QStringLiteral("\",\"") + QString(64, 'b') + QStringLiteral("\"]");
|
||||
const QJsonObject obj = parseObject(module.claim_pinata_private_owned_already_initialized(
|
||||
VALID_ID, VALID_ID_2, VALID_U128, 1, siblings));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_claim_pinata_private_owned_already_initialized"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].toBool());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wallet lifecycle
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(create_new_success_then_double_open_fails) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_create_new").returns(1); // non-null handle
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.create_new(QStringLiteral("/cfg"), QStringLiteral("/store"), QStringLiteral("pw")),
|
||||
static_cast<int>(SUCCESS));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_create_new"));
|
||||
// Second attempt: already open.
|
||||
LOGOS_ASSERT_EQ(module.create_new(QStringLiteral("/cfg"), QStringLiteral("/store"), QStringLiteral("pw")),
|
||||
static_cast<int>(INTERNAL_ERROR));
|
||||
}
|
||||
|
||||
LOGOS_TEST(create_new_null_handle_returns_internal_error) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_create_new").returns(0); // null handle
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.create_new(QStringLiteral("/cfg"), QStringLiteral("/store"), QStringLiteral("pw")),
|
||||
static_cast<int>(INTERNAL_ERROR));
|
||||
}
|
||||
|
||||
LOGOS_TEST(open_success) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_open").returns(1);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.open(QStringLiteral("/cfg"), QStringLiteral("/store")), static_cast<int>(SUCCESS));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_open"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(save_forwards_return_code) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_save").returns(static_cast<int>(SUCCESS));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.save(), static_cast<int>(SUCCESS));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_save"));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(get_sequencer_addr_returns_string) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_get_sequencer_addr").returns("10.0.0.1:9000");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_EQ(module.get_sequencer_addr(), QStringLiteral("10.0.0.1:9000"));
|
||||
}
|
||||
33
tests/test_execution_zone_integration.cpp
Normal file
33
tests/test_execution_zone_integration.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
// Integration tests for LogosExecutionZoneWalletModule — uses the REAL wallet_ffi
|
||||
// library. No mocking. Limited to network-free, wallet-handle-free pure functions
|
||||
// so the suite stays deterministic and offline.
|
||||
//
|
||||
// Requires the real wallet library (and wallet_ffi.h header) in ../lib at build
|
||||
// time. Skipped automatically when the library is not found (see CMakeLists.txt).
|
||||
|
||||
#include <logos_test.h>
|
||||
#include "logos_execution_zone_wallet_module.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
// account_id_to_base58 and account_id_from_base58 are pure encoding helpers that
|
||||
// do not require an open wallet, so they can be exercised against the real lib.
|
||||
LOGOS_TEST(integration_account_id_base58_round_trip) {
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const QString idHex = QString(64, 'a');
|
||||
|
||||
const QString base58 = module.account_id_to_base58(idHex);
|
||||
LOGOS_ASSERT_FALSE(base58.isEmpty());
|
||||
|
||||
const QString decodedHex = module.account_id_from_base58(base58);
|
||||
LOGOS_ASSERT_FALSE(decodedHex.isEmpty());
|
||||
LOGOS_ASSERT_EQ(decodedHex, idHex);
|
||||
}
|
||||
|
||||
LOGOS_TEST(integration_account_id_from_base58_rejects_garbage) {
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
// Clearly invalid base58 input should not decode to a valid id.
|
||||
LOGOS_ASSERT_TRUE(module.account_id_from_base58(QStringLiteral("!!!not-base58!!!")).isEmpty());
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user