Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 40 additions & 8 deletions src/lez_core_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ bool hexToBytes32(const std::string& hex, FfiBytes32* output_bytes) {
return true;
}

// The module API declares instruction words uint64_t because LIDL numbers are
// 64-bit only, while the wallet FFI takes uint32_t words. Narrow here, refusing
// a word that does not fit rather than truncating it into a different
// instruction.
bool narrowInstructionWords(const std::vector<uint64_t>& instruction, std::vector<uint32_t>& output) {
output.clear();
output.reserve(instruction.size());
for (const uint64_t word : instruction) {
if (word > UINT32_MAX)
return false;
output.push_back(static_cast<uint32_t>(word));
}
return true;
}

// Builds JSON { success, tx_hash, error } for both success (result + empty error) and failure (nullptr + errorMessage).
std::string transferResultToJson(const FfiTransferResult* result, const std::string& errorMessage) {
nlohmann::json obj = nlohmann::json::object();
Expand Down Expand Up @@ -970,9 +985,15 @@ std::vector<uint8_t> LEZCoreModule::authenticated_transfer_elf() {
std::string LEZCoreModule::send_generic_public_transaction(
const std::vector<std::string>& account_ids,
const std::vector<bool>& signing_requirements,
const std::vector<uint32_t>& instruction,
const std::vector<uint64_t>& instruction,
const std::string& program_id_hex
) {
std::vector<uint32_t> instruction_words;
if (!narrowInstructionWords(instruction, instruction_words)) {
fprintf(stderr, "send_generic_public_transaction: instruction word exceeds 32 bits\n");
return transferResultToJson(nullptr, std::string("send_generic_public_transaction: instruction word exceeds 32 bits"));
}

std::vector<FfiAccountIdentity> identities_resolved;
identities_resolved.reserve(account_ids.size());

Expand All @@ -996,8 +1017,8 @@ std::string LEZCoreModule::send_generic_public_transaction(
const FfiAccountIdentity *account_identities = identities_resolved.data();
uintptr_t account_identities_size = static_cast<uintptr_t>(identities_resolved.size());

const uint32_t* input_instruction_data = instruction.data();
uintptr_t input_instruction_data_size = static_cast<uintptr_t>(instruction.size());
const uint32_t* input_instruction_data = instruction_words.data();
uintptr_t input_instruction_data_size = static_cast<uintptr_t>(instruction_words.size());

std::vector<uint8_t> program_id_bytes;
if (!hexToBytes(program_id_hex, program_id_bytes, 32)) {
Expand Down Expand Up @@ -1034,10 +1055,16 @@ std::string LEZCoreModule::send_generic_public_transaction(

std::string LEZCoreModule::send_generic_private_transaction(
const std::vector<std::string>& account_ids,
const std::vector<uint32_t>& instruction,
const std::vector<uint64_t>& instruction,
const std::vector<uint8_t>& program_elf,
const std::vector<std::vector<uint8_t>>& program_dependencies
) {
std::vector<uint32_t> instruction_words;
if (!narrowInstructionWords(instruction, instruction_words)) {
fprintf(stderr, "send_generic_private_transaction: instruction word exceeds 32 bits\n");
return transferResultToJson(nullptr, std::string("send_generic_private_transaction: instruction word exceeds 32 bits"));
}

std::vector<FfiAccountIdentity> identities_resolved;
identities_resolved.reserve(account_ids.size());

Expand All @@ -1061,8 +1088,8 @@ std::string LEZCoreModule::send_generic_private_transaction(
const FfiAccountIdentity *account_identities = identities_resolved.data();
uintptr_t account_identities_size = static_cast<uintptr_t>(identities_resolved.size());

const uint32_t* input_instruction_data = instruction.data();
uintptr_t input_instruction_data_size = static_cast<uintptr_t>(instruction.size());
const uint32_t* input_instruction_data = instruction_words.data();
uintptr_t input_instruction_data_size = static_cast<uintptr_t>(instruction_words.size());

FfiProgram main_program {};

Expand Down Expand Up @@ -1171,8 +1198,13 @@ std::string LEZCoreModule::create_new(
return mnemonic;
}

int64_t LEZCoreModule::restore_storage(const std::string& mnemonic, const std::string password, uint32_t depth) {
const WalletFfiError error = wallet_ffi_restore_data(walletHandle, mnemonic.c_str(), password.c_str(), depth);
int64_t LEZCoreModule::restore_storage(const std::string& mnemonic, const std::string password, uint64_t depth) {
if (depth > UINT32_MAX) {
fprintf(stderr, "restore_storage: depth exceeds 32 bits\n");
return INVALID_TYPE_CONVERSION;
}

const WalletFfiError error = wallet_ffi_restore_data(walletHandle, mnemonic.c_str(), password.c_str(), static_cast<uint32_t>(depth));
if (error != SUCCESS) {
fprintf(stderr, "restore_storage: wallet FFI error %d\n", error);
return error;
Expand Down
6 changes: 3 additions & 3 deletions src/lez_core_module.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class LEZCoreModule {
int64_t open(const std::string& config_path, const std::string& storage_path);
int64_t save();

int64_t restore_storage(const std::string& mnemonic, const std::string password, uint32_t depth);
int64_t restore_storage(const std::string& mnemonic, const std::string password, uint64_t depth);

// === Account Management ===
std::string create_account_public();
Expand Down Expand Up @@ -77,8 +77,8 @@ class LEZCoreModule {
std::vector<uint8_t> amm_elf();
std::vector<uint8_t> ata_elf();

std::string send_generic_public_transaction(const std::vector<std::string>& account_ids, const std::vector<bool>& signing_requirements, const std::vector<uint32_t>& instruction, const std::string& program_id_hex);
std::string send_generic_private_transaction(const std::vector<std::string>& account_ids, const std::vector<uint32_t>& instruction, const std::vector<uint8_t>& program_elf, const std::vector<std::vector<uint8_t>>& program_dependencies);
std::string send_generic_public_transaction(const std::vector<std::string>& account_ids, const std::vector<bool>& signing_requirements, const std::vector<uint64_t>& instruction, const std::string& program_id_hex);
std::string send_generic_private_transaction(const std::vector<std::string>& account_ids, const std::vector<uint64_t>& instruction, const std::vector<uint8_t>& program_elf, const std::vector<std::vector<uint8_t>>& program_dependencies);
std::string send_program_deployment_transaction(const std::vector<uint8_t>& program_elf);

// === Bridge (L1 Bedrock <-> L2) ===
Expand Down
9 changes: 8 additions & 1 deletion tests/stubs/wallet_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@ extern "C" {
#endif

// Error codes returned by most wallet_ffi_* functions (0 == SUCCESS).
//
// NOTE: this stub does NOT mirror the real wallet_ffi.h — the names below are a
// subset and the values differ (INTERNAL_ERROR is 99 upstream, not 1). Code that
// compiles against this stub can therefore still fail against the real header,
// so only assert on names present in both. INVALID_TYPE_CONVERSION carries its
// upstream value so the narrowing guards can be tested by name.
typedef enum WalletFfiError {
SUCCESS = 0,
INTERNAL_ERROR = 1,
INVALID_INPUT = 2,
NOT_FOUND = 3,
INVALID_ACCOUNT_ID = 4
INVALID_ACCOUNT_ID = 4,
INVALID_TYPE_CONVERSION = 15
} WalletFfiError;

// Opaque wallet handle.
Expand Down
61 changes: 61 additions & 0 deletions tests/test_lez_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <cstring>
#include <string>
#include <vector>

#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -622,6 +623,66 @@ LOGOS_TEST(save_forwards_return_code) {
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_save"));
}

// ============================================================================
// 64-bit API / 32-bit FFI boundary
//
// The module API takes uint64_t because LIDL numbers are 64-bit only, while
// the wallet FFI is 32-bit. A value that does not fit must be refused, not
// truncated into a different depth or a different instruction.
// ============================================================================

LOGOS_TEST(restore_storage_rejects_depth_above_32_bits) {
auto t = LogosTestContext("logos_execution_zone");
LEZCoreModule module;

const uint64_t tooWide = static_cast<uint64_t>(UINT32_MAX) + 1;
LOGOS_ASSERT_EQ(module.restore_storage("mnemonic", "pw", tooWide),
static_cast<int64_t>(INVALID_TYPE_CONVERSION));
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_restore_data"));
}

LOGOS_TEST(restore_storage_accepts_depth_at_32_bit_limit) {
auto t = LogosTestContext("logos_execution_zone");
t.mockCFunction("wallet_ffi_restore_data").returns(static_cast<int>(SUCCESS));
LEZCoreModule module;

LOGOS_ASSERT_EQ(module.restore_storage("mnemonic", "pw", UINT32_MAX),
static_cast<int64_t>(SUCCESS));
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_restore_data"));
}

LOGOS_TEST(send_generic_public_transaction_rejects_wide_instruction_word) {
auto t = LogosTestContext("logos_execution_zone");
LEZCoreModule module;

const std::vector<uint64_t> instruction = {
0x1u, static_cast<uint64_t>(UINT32_MAX) + 1};
const std::string json = module.send_generic_public_transaction(
{VALID_ID}, {true}, instruction, std::string(64, 'c'));

auto obj = parseObject(json);
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
LOGOS_ASSERT_FALSE(
t.cFunctionCalled("wallet_ffi_send_generic_public_transaction"));
}

LOGOS_TEST(send_generic_private_transaction_rejects_wide_instruction_word) {
auto t = LogosTestContext("logos_execution_zone");
LEZCoreModule module;

const std::vector<uint64_t> instruction = {
0x1u, static_cast<uint64_t>(UINT32_MAX) + 1};
const std::string json = module.send_generic_private_transaction(
{VALID_ID}, instruction, {}, {});

auto obj = parseObject(json);
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
LOGOS_ASSERT_FALSE(
t.cFunctionCalled("wallet_ffi_send_generic_private_transaction"));
}

// ============================================================================
// Configuration
// ============================================================================
Expand Down