add tests using the module test framework

add tests using the module test framework

test fix

update flake
This commit is contained in:
Iuri Matias 2026-06-08 17:39:55 -04:00
parent 5d42559db8
commit 310b6df8ec
9 changed files with 6396 additions and 167 deletions

40
.github/workflows/ci.yml vendored Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@ -18,5 +18,9 @@
packages.default = "wallet";
};
};
tests = {
dir = ./tests;
mockCLibs = [ "wallet_ffi" ];
};
};
}

55
tests/CMakeLists.txt Normal file
View 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
View File

@ -0,0 +1,3 @@
#include <logos_test.h>
LOGOS_TEST_MAIN()

View 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
View 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

View 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"));
}

View 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());
}