mirror of
https://github.com/logos-blockchain/logos-execution-zone-module.git
synced 2026-07-08 11:40:18 +00:00
feat: add identifiers, withdraw methods and vault claim functions
This commit is contained in:
parent
d2e9400ac0
commit
38515bd560
4
flake.lock
generated
4
flake.lock
generated
@ -1585,13 +1585,13 @@
|
||||
"lastModified": 1782310268,
|
||||
"narHash": "sha256-tIIIO+V+w/C/KLtKnLIv8O8/GoJ4PzgJSWrlQCXJ2P4=",
|
||||
"owner": "logos-blockchain",
|
||||
"repo": "lssa",
|
||||
"repo": "logos-execution-zone",
|
||||
"rev": "fb8cbac40e0bda4f152415ff4f181cdc6bca6d4a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "logos-blockchain",
|
||||
"repo": "lssa",
|
||||
"repo": "logos-execution-zone",
|
||||
"rev": "fb8cbac40e0bda4f152415ff4f181cdc6bca6d4a",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
inputs = {
|
||||
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
||||
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
|
||||
logos-execution-zone.url = "github:logos-blockchain/lssa?rev=fb8cbac40e0bda4f152415ff4f181cdc6bca6d4a"; # lez-core-v0.2.0
|
||||
logos-execution-zone.url = "github:logos-blockchain/logos-execution-zone?rev=fb8cbac40e0bda4f152415ff4f181cdc6bca6d4a"; # lez-core-v0.2.0
|
||||
};
|
||||
|
||||
outputs = inputs@{ logos-module-builder, ... }:
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
@ -53,6 +54,7 @@ constexpr auto Nonce = "nonce";
|
||||
constexpr auto Data = "data";
|
||||
constexpr auto NullifierPublicKey = "nullifier_public_key";
|
||||
constexpr auto ViewingPublicKey = "viewing_public_key";
|
||||
constexpr auto Identifier = "identifier";
|
||||
constexpr auto AccountId = "account_id";
|
||||
constexpr auto IsPublic = "is_public";
|
||||
constexpr auto Secrets = "secrets";
|
||||
@ -176,6 +178,35 @@ std::string ffiPrivateAccountKeysToJson(const FfiPrivateAccountKeys& keys) {
|
||||
return obj.dump();
|
||||
}
|
||||
|
||||
// Nothing in this codebase currently emits an "identifier" field in to_keys_json — NPK/VPK
|
||||
// identify a key group, not one specific account in it, so get_private_account_keys never
|
||||
// attaches one. Kept for forward compatibility (e.g. a hand-crafted or future payload that
|
||||
// targets one specific account within a group) and to make the fallback below explicit.
|
||||
bool jsonExtractIdentifier(const std::string& json, FfiU128* out_identifier) {
|
||||
nlohmann::json doc = nlohmann::json::parse(json, nullptr, false);
|
||||
if (doc.is_discarded() || !doc.is_object())
|
||||
return false;
|
||||
if (!doc.contains(JsonKeys::Identifier) || !doc[JsonKeys::Identifier].is_string())
|
||||
return false;
|
||||
std::vector<uint8_t> buffer;
|
||||
if (!hexToBytes(doc[JsonKeys::Identifier].get<std::string>(), buffer, 16))
|
||||
return false;
|
||||
memcpy(out_identifier->data, buffer.data(), 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
// A foreign recipient's identifier isn't known to the sender; the recipient's wallet
|
||||
// recovers it from the encrypted transfer payload the next time it runs sync-private.
|
||||
FfiU128 randomFfiU128() {
|
||||
static std::mt19937_64 rng(std::random_device{}());
|
||||
FfiU128 value{};
|
||||
for (int i = 0; i < 16; i += 8) {
|
||||
uint64_t chunk = rng();
|
||||
memcpy(value.data + i, &chunk, sizeof(chunk));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool jsonToFfiPrivateAccountKeys(const std::string& json, FfiPrivateAccountKeys* output_keys) {
|
||||
nlohmann::json doc = nlohmann::json::parse(json, nullptr, false);
|
||||
if (doc.is_discarded() || !doc.is_object())
|
||||
@ -366,6 +397,11 @@ std::string LogosExecutionZoneWalletModule::get_private_account_keys(const std::
|
||||
fprintf(stderr, "get_private_account_keys: wallet FFI error %d\n", error);
|
||||
return {};
|
||||
}
|
||||
|
||||
// NPK/VPK identify the key group a private account belongs to, not one specific
|
||||
// account within it — there's no real identifier to attach here. (wallet_ffi_resolve_private_account
|
||||
// can't supply one either: for a regular owned account it returns AccountIdentity::PrivateOwned,
|
||||
// which carries no identifier and defaults to zero — not a real value.)
|
||||
std::string result = ffiPrivateAccountKeysToJson(keys);
|
||||
wallet_ffi_free_private_account_keys(&keys);
|
||||
return result;
|
||||
@ -591,13 +627,17 @@ std::string LogosExecutionZoneWalletModule::transfer_shielded(
|
||||
return transferResultToJson(nullptr, "transfer_shielded: amount_le16_hex must be 32 hex characters (16 bytes)");
|
||||
}
|
||||
|
||||
// ToDo: Bandaid, I am not sure, how exactly identifiers should be used.
|
||||
FfiU128 identifier {};
|
||||
// to_keys_json never carries an identifier in this codebase (NPK/VPK name a key group,
|
||||
// not one account in it) — pick a random one, which the recipient's wallet will recover
|
||||
// from the encrypted transfer payload on its next sync-private. See jsonExtractIdentifier.
|
||||
FfiU128 toIdentifier{};
|
||||
if (!jsonExtractIdentifier(to_keys_json, &toIdentifier))
|
||||
toIdentifier = randomFfiU128();
|
||||
// ToDo: Add keycard support
|
||||
const char *key_path = nullptr;
|
||||
|
||||
FfiTransferResult result{};
|
||||
const WalletFfiError error = wallet_ffi_transfer_shielded(walletHandle, &fromId, &toKeys, &identifier, &amount, key_path, &result);
|
||||
const WalletFfiError error = wallet_ffi_transfer_shielded(walletHandle, &fromId, &toKeys, &toIdentifier, &amount, key_path, &result);
|
||||
free(const_cast<uint8_t*>(toKeys.viewing_public_key));
|
||||
if (error != SUCCESS) {
|
||||
fprintf(stderr, "transfer_shielded: wallet FFI error %d\n", error);
|
||||
@ -660,11 +700,13 @@ std::string LogosExecutionZoneWalletModule::transfer_private(
|
||||
return transferResultToJson(nullptr, "transfer_private: amount_le16_hex must be 32 hex characters (16 bytes)");
|
||||
}
|
||||
|
||||
// ToDo: Bandaid, I am not sure, how exactly identifiers should be used.
|
||||
FfiU128 identifier {};
|
||||
|
||||
// See transfer_shielded above: to_keys_json never carries an identifier, so always pick
|
||||
// a random one for the recipient's wallet to recover via sync-private.
|
||||
FfiU128 toIdentifier{};
|
||||
if (!jsonExtractIdentifier(to_keys_json, &toIdentifier))
|
||||
toIdentifier = randomFfiU128();
|
||||
FfiTransferResult result{};
|
||||
const WalletFfiError error = wallet_ffi_transfer_private(walletHandle, &fromId, &toKeys, &identifier, &amount, &result);
|
||||
const WalletFfiError error = wallet_ffi_transfer_private(walletHandle, &fromId, &toKeys, &toIdentifier, &amount, &result);
|
||||
free(const_cast<uint8_t*>(toKeys.viewing_public_key));
|
||||
if (error != SUCCESS) {
|
||||
fprintf(stderr, "transfer_private: wallet FFI error %d\n", error);
|
||||
@ -751,6 +793,103 @@ std::string LogosExecutionZoneWalletModule::register_public_account(const std::s
|
||||
return resultJson;
|
||||
}
|
||||
|
||||
// === Bridge (L1 Bedrock <-> L2) ===
|
||||
|
||||
std::string LogosExecutionZoneWalletModule::bridge_withdraw(
|
||||
const std::string& from_hex,
|
||||
const std::string& bedrock_account_pk_hex,
|
||||
const uint64_t amount
|
||||
) {
|
||||
FfiBytes32 fromId{}, bedrockAccountPk{};
|
||||
if (!hexToBytes32(from_hex, &fromId) || !hexToBytes32(bedrock_account_pk_hex, &bedrockAccountPk)) {
|
||||
fprintf(stderr, "bridge_withdraw: invalid account id or bedrock account pk hex\n");
|
||||
return transferResultToJson(nullptr, "bridge_withdraw: invalid account id or bedrock account pk hex");
|
||||
}
|
||||
|
||||
FfiTransferResult result{};
|
||||
const WalletFfiError error = wallet_ffi_bridge_withdraw(
|
||||
walletHandle, &fromId, amount, &bedrockAccountPk, &result);
|
||||
if (error != SUCCESS) {
|
||||
fprintf(stderr, "bridge_withdraw: wallet FFI error %d\n", error);
|
||||
return transferResultToJson(nullptr, "bridge_withdraw: wallet FFI error " + std::to_string(error));
|
||||
}
|
||||
std::string resultJson = transferResultToJson(&result, std::string());
|
||||
wallet_ffi_free_transfer_result(&result);
|
||||
return resultJson;
|
||||
}
|
||||
|
||||
// === Vault claiming ===
|
||||
|
||||
std::string LogosExecutionZoneWalletModule::get_vault_balance(const std::string& owner_account_id_hex) {
|
||||
FfiBytes32 ownerId{};
|
||||
if (!hexToBytes32(owner_account_id_hex, &ownerId)) {
|
||||
fprintf(stderr, "get_vault_balance: invalid owner_account_id_hex\n");
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t balance[16] = {0};
|
||||
const WalletFfiError error = wallet_ffi_get_vault_balance(walletHandle, &ownerId, &balance);
|
||||
if (error != SUCCESS) {
|
||||
fprintf(stderr, "get_vault_balance: wallet FFI error %d\n", error);
|
||||
return {};
|
||||
}
|
||||
return balanceLe16ToDecimalString(balance);
|
||||
}
|
||||
|
||||
std::string LogosExecutionZoneWalletModule::vault_claim(
|
||||
const std::string& owner_account_id_hex,
|
||||
const std::string& amount_le16_hex
|
||||
) {
|
||||
FfiBytes32 ownerId{};
|
||||
if (!hexToBytes32(owner_account_id_hex, &ownerId)) {
|
||||
fprintf(stderr, "vault_claim: invalid owner_account_id_hex\n");
|
||||
return transferResultToJson(nullptr, "vault_claim: invalid owner_account_id_hex");
|
||||
}
|
||||
|
||||
uint8_t amount[16];
|
||||
if (!hexToU128(amount_le16_hex, &amount)) {
|
||||
fprintf(stderr, "vault_claim: amount_le16_hex must be 32 hex characters (16 bytes)\n");
|
||||
return transferResultToJson(nullptr, "vault_claim: amount_le16_hex must be 32 hex characters (16 bytes)");
|
||||
}
|
||||
|
||||
FfiTransferResult result{};
|
||||
const WalletFfiError error = wallet_ffi_vault_claim(walletHandle, &ownerId, &amount, &result);
|
||||
if (error != SUCCESS) {
|
||||
fprintf(stderr, "vault_claim: wallet FFI error %d\n", error);
|
||||
return transferResultToJson(nullptr, "vault_claim: wallet FFI error " + std::to_string(error));
|
||||
}
|
||||
std::string resultJson = transferResultToJson(&result, std::string());
|
||||
wallet_ffi_free_transfer_result(&result);
|
||||
return resultJson;
|
||||
}
|
||||
|
||||
std::string LogosExecutionZoneWalletModule::vault_claim_private(
|
||||
const std::string& owner_account_id_hex,
|
||||
const std::string& amount_le16_hex
|
||||
) {
|
||||
FfiBytes32 ownerId{};
|
||||
if (!hexToBytes32(owner_account_id_hex, &ownerId)) {
|
||||
fprintf(stderr, "vault_claim_private: invalid owner_account_id_hex\n");
|
||||
return transferResultToJson(nullptr, "vault_claim_private: invalid owner_account_id_hex");
|
||||
}
|
||||
|
||||
uint8_t amount[16];
|
||||
if (!hexToU128(amount_le16_hex, &amount)) {
|
||||
fprintf(stderr, "vault_claim_private: amount_le16_hex must be 32 hex characters (16 bytes)\n");
|
||||
return transferResultToJson(nullptr, "vault_claim_private: amount_le16_hex must be 32 hex characters (16 bytes)");
|
||||
}
|
||||
|
||||
FfiTransferResult result{};
|
||||
const WalletFfiError error = wallet_ffi_vault_claim_private(walletHandle, &ownerId, &amount, &result);
|
||||
if (error != SUCCESS) {
|
||||
fprintf(stderr, "vault_claim_private: wallet FFI error %d\n", error);
|
||||
return transferResultToJson(nullptr, "vault_claim_private: wallet FFI error " + std::to_string(error));
|
||||
}
|
||||
std::string resultJson = transferResultToJson(&result, std::string());
|
||||
wallet_ffi_free_transfer_result(&result);
|
||||
return resultJson;
|
||||
}
|
||||
|
||||
std::string LogosExecutionZoneWalletModule::register_private_account(const std::string& account_id_hex) {
|
||||
FfiBytes32 id{};
|
||||
if (!hexToBytes32(account_id_hex, &id)) {
|
||||
|
||||
@ -79,7 +79,7 @@ public:
|
||||
|
||||
std::string send_generic_public_transaction(
|
||||
const std::vector<std::string>& account_ids,
|
||||
const std::vector<bool>& signing_requirements,
|
||||
const std::vector<bool>& signing_requirements,
|
||||
const std::vector<uint32_t>& instruction,
|
||||
const std::vector<uint8_t>& program_elf,
|
||||
const std::vector<std::vector<uint8_t>>& program_dependencies
|
||||
@ -94,6 +94,14 @@ public:
|
||||
const std::vector<uint8_t>& program_elf
|
||||
);
|
||||
|
||||
// === Bridge (L1 Bedrock <-> L2) ===
|
||||
std::string bridge_withdraw(const std::string& from_hex, const std::string& bedrock_account_pk_hex, uint64_t amount);
|
||||
|
||||
// === Vault claiming (L1 deposits credited to an owner's vault account) ===
|
||||
std::string get_vault_balance(const std::string& owner_account_id_hex);
|
||||
std::string vault_claim(const std::string& owner_account_id_hex, const std::string& amount_le16_hex);
|
||||
std::string vault_claim_private(const std::string& owner_account_id_hex, const std::string& amount_le16_hex);
|
||||
|
||||
// === Configuration ===
|
||||
std::string get_sequencer_addr();
|
||||
|
||||
|
||||
@ -14,9 +14,16 @@ extern "C" {
|
||||
#include <wallet_ffi.h>
|
||||
}
|
||||
|
||||
#include "mock_wallet_ffi_capture.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
namespace MockWalletFfiCapture {
|
||||
uint8_t lastTransferShieldedIdentifier[16] = {0};
|
||||
uint8_t lastTransferPrivateIdentifier[16] = {0};
|
||||
} // namespace MockWalletFfiCapture
|
||||
|
||||
namespace {
|
||||
|
||||
// A single non-null sentinel handle handed back by create_new / open.
|
||||
@ -342,11 +349,14 @@ WalletFfiError wallet_ffi_transfer_public(
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_shielded(
|
||||
WalletHandle*, const FfiBytes32*, const FfiPrivateAccountKeys*, const FfiU128*,
|
||||
const uint8_t (*)[16],
|
||||
WalletHandle*, const FfiBytes32*, const FfiPrivateAccountKeys*, const FfiU128* identifier,
|
||||
const uint8_t (*)[16],
|
||||
const char*,
|
||||
FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_shielded");
|
||||
if (identifier) {
|
||||
memcpy(MockWalletFfiCapture::lastTransferShieldedIdentifier, identifier->data, 16);
|
||||
}
|
||||
return fillTransferResult("wallet_ffi_transfer_shielded", out_result);
|
||||
}
|
||||
|
||||
@ -357,14 +367,17 @@ WalletFfiError wallet_ffi_transfer_deshielded(
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_private(
|
||||
WalletHandle*, const FfiBytes32*, const FfiPrivateAccountKeys*, const FfiU128*,
|
||||
WalletHandle*, const FfiBytes32*, const FfiPrivateAccountKeys*, const FfiU128* identifier,
|
||||
const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_private");
|
||||
if (identifier) {
|
||||
memcpy(MockWalletFfiCapture::lastTransferPrivateIdentifier, identifier->data, 16);
|
||||
}
|
||||
return fillTransferResult("wallet_ffi_transfer_private", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_transfer_shielded_owned(
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16],
|
||||
WalletHandle*, const FfiBytes32*, const FfiBytes32*, const uint8_t (*)[16],
|
||||
const char *,
|
||||
FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_transfer_shielded_owned");
|
||||
@ -477,6 +490,41 @@ FfiTransactionResult *out_result) {
|
||||
return fillTransactionResult("wallet_ffi_program_deployment", out_result);
|
||||
}
|
||||
|
||||
// === Bridge (L1 Bedrock <-> L2) ===
|
||||
|
||||
WalletFfiError wallet_ffi_bridge_withdraw(
|
||||
WalletHandle*, const FfiBytes32*, uint64_t, const FfiBytes32*, FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_bridge_withdraw");
|
||||
return fillTransferResult("wallet_ffi_bridge_withdraw", out_result);
|
||||
}
|
||||
|
||||
// === Vault claiming ===
|
||||
|
||||
WalletFfiError wallet_ffi_get_vault_balance(WalletHandle*, const FfiBytes32*, uint8_t (*out_balance)[16]) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_get_vault_balance");
|
||||
const int err = LOGOS_CMOCK_RETURN(int, "wallet_ffi_get_vault_balance");
|
||||
if (err == 0 && out_balance) {
|
||||
const uint64_t value = static_cast<uint64_t>(LOGOS_CMOCK_RETURN(int, "get_vault_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);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_vault_claim(
|
||||
WalletHandle*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_vault_claim");
|
||||
return fillTransferResult("wallet_ffi_vault_claim", out_result);
|
||||
}
|
||||
|
||||
WalletFfiError wallet_ffi_vault_claim_private(
|
||||
WalletHandle*, const FfiBytes32*, const uint8_t (*)[16], FfiTransferResult* out_result) {
|
||||
LOGOS_CMOCK_RECORD("wallet_ffi_vault_claim_private");
|
||||
return fillTransferResult("wallet_ffi_vault_claim_private", out_result);
|
||||
}
|
||||
|
||||
// === Configuration ===
|
||||
|
||||
char* wallet_ffi_get_sequencer_addr(WalletHandle*) {
|
||||
|
||||
17
tests/mocks/mock_wallet_ffi_capture.h
Normal file
17
tests/mocks/mock_wallet_ffi_capture.h
Normal file
@ -0,0 +1,17 @@
|
||||
#ifndef MOCK_WALLET_FFI_CAPTURE_H
|
||||
#define MOCK_WALLET_FFI_CAPTURE_H
|
||||
|
||||
// LogosCMockStore (logos_clib_mock.h) only lets tests control return values, not
|
||||
// inspect call arguments. The transfer_shielded/transfer_private identifier logic
|
||||
// needs the latter, so capture the relevant args here in plain static storage.
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MockWalletFfiCapture {
|
||||
|
||||
extern uint8_t lastTransferShieldedIdentifier[16];
|
||||
extern uint8_t lastTransferPrivateIdentifier[16];
|
||||
|
||||
} // namespace MockWalletFfiCapture
|
||||
|
||||
#endif // MOCK_WALLET_FFI_CAPTURE_H
|
||||
@ -36,6 +36,11 @@ typedef struct FfiBytes16 {
|
||||
uint8_t data[16];
|
||||
} FfiBytes16;
|
||||
|
||||
// 16-byte little-endian u128 (e.g. a private account identifier).
|
||||
typedef struct FfiU128 {
|
||||
uint8_t data[16];
|
||||
} FfiU128;
|
||||
|
||||
// Full account record.
|
||||
typedef struct FfiAccount {
|
||||
FfiBytes32 program_owner;
|
||||
@ -90,13 +95,6 @@ typedef enum FfiAccountIdentityKind {
|
||||
PRIVATE_PDA_SHARED = 8,
|
||||
} FfiAccountIdentityKind;
|
||||
|
||||
/**
|
||||
* U128 - 16 bytes little endian.
|
||||
*/
|
||||
typedef struct FfiU128 {
|
||||
uint8_t data[16];
|
||||
} FfiU128;
|
||||
|
||||
typedef struct FfiInstructionWords {
|
||||
uint32_t *instruction_words;
|
||||
uintptr_t instruction_words_size;
|
||||
@ -236,7 +234,7 @@ WalletFfiError wallet_ffi_transfer_public(
|
||||
WalletFfiError wallet_ffi_transfer_shielded(
|
||||
WalletHandle* handle, const FfiBytes32* from, const FfiPrivateAccountKeys* to_keys,
|
||||
const FfiU128 *to_identifier,
|
||||
const uint8_t (*amount)[16],
|
||||
const uint8_t (*amount)[16],
|
||||
const char *key_path,
|
||||
FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_deshielded(
|
||||
@ -248,7 +246,7 @@ WalletFfiError wallet_ffi_transfer_private(
|
||||
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],
|
||||
const uint8_t (*amount)[16],
|
||||
const char *key_path,
|
||||
FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_transfer_private_owned(
|
||||
@ -296,6 +294,20 @@ void wallet_ffi_free_transfer_result(FfiTransferResult* result);
|
||||
void wallet_ffi_free_transaction_result(FfiTransactionResult *result);
|
||||
void wallet_ffi_free_ffi_program(FfiProgram *ffi_program);
|
||||
|
||||
// === Bridge (L1 Bedrock <-> L2) ===
|
||||
|
||||
WalletFfiError wallet_ffi_bridge_withdraw(
|
||||
WalletHandle* handle, const FfiBytes32* from, uint64_t amount,
|
||||
const FfiBytes32* bedrock_account_pk, FfiTransferResult* out_result);
|
||||
|
||||
// === Vault claiming ===
|
||||
|
||||
WalletFfiError wallet_ffi_get_vault_balance(WalletHandle* handle, const FfiBytes32* owner, uint8_t (*out_balance)[16]);
|
||||
WalletFfiError wallet_ffi_vault_claim(
|
||||
WalletHandle* handle, const FfiBytes32* owner, const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
WalletFfiError wallet_ffi_vault_claim_private(
|
||||
WalletHandle* handle, const FfiBytes32* owner, const uint8_t (*amount)[16], FfiTransferResult* out_result);
|
||||
|
||||
// === Configuration ===
|
||||
|
||||
char* wallet_ffi_get_sequencer_addr(WalletHandle* handle);
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
|
||||
#include <logos_test.h>
|
||||
#include "logos_execution_zone_wallet_module.h"
|
||||
#include "mocks/mock_wallet_ffi_capture.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
@ -296,6 +298,78 @@ LOGOS_TEST(transfer_shielded_success_json) {
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
}
|
||||
|
||||
// to_keys_json carrying an explicit "identifier" must be forwarded to the FFI call as-is.
|
||||
LOGOS_TEST(transfer_shielded_with_identifier_forwards_it_unchanged) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const std::string identifierHex(32, '7'); // 16 bytes of 0x77
|
||||
const std::string keysJson = std::string("{\"nullifier_public_key\":\"") + std::string(64, 'a')
|
||||
+ "\",\"identifier\":\"" + identifierHex + "\"}";
|
||||
|
||||
const nlohmann::json obj = parseObject(module.transfer_shielded(VALID_ID, keysJson, VALID_U128));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
|
||||
uint8_t expected[16];
|
||||
memset(expected, 0x77, sizeof(expected));
|
||||
LOGOS_ASSERT(memcmp(MockWalletFfiCapture::lastTransferShieldedIdentifier, expected, sizeof(expected)) == 0);
|
||||
}
|
||||
|
||||
// to_keys_json never carries an identifier in practice -> a random, non-zero one must be
|
||||
// generated each call (regression guard against reintroducing the old hardcoded-zero identifier).
|
||||
LOGOS_TEST(transfer_shielded_without_identifier_uses_random_nonzero_identifier) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const std::string keysJson = std::string("{\"nullifier_public_key\":\"") + std::string(64, 'a') + "\"}";
|
||||
|
||||
module.transfer_shielded(VALID_ID, keysJson, VALID_U128);
|
||||
uint8_t first[16];
|
||||
memcpy(first, MockWalletFfiCapture::lastTransferShieldedIdentifier, sizeof(first));
|
||||
|
||||
module.transfer_shielded(VALID_ID, keysJson, VALID_U128);
|
||||
const uint8_t* second = MockWalletFfiCapture::lastTransferShieldedIdentifier;
|
||||
|
||||
uint8_t zero[16] = {0};
|
||||
LOGOS_ASSERT_FALSE(memcmp(first, zero, sizeof(first)) == 0);
|
||||
LOGOS_ASSERT_FALSE(memcmp(first, second, sizeof(first)) == 0);
|
||||
}
|
||||
|
||||
// transfer_private mirrors transfer_shielded's identifier handling exactly (see above).
|
||||
LOGOS_TEST(transfer_private_with_identifier_forwards_it_unchanged) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const std::string identifierHex(32, '7'); // 16 bytes of 0x77
|
||||
const std::string keysJson = std::string("{\"nullifier_public_key\":\"") + std::string(64, 'a')
|
||||
+ "\",\"identifier\":\"" + identifierHex + "\"}";
|
||||
|
||||
const nlohmann::json obj = parseObject(module.transfer_private(VALID_ID, keysJson, VALID_U128));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
|
||||
uint8_t expected[16];
|
||||
memset(expected, 0x77, sizeof(expected));
|
||||
LOGOS_ASSERT(memcmp(MockWalletFfiCapture::lastTransferPrivateIdentifier, expected, sizeof(expected)) == 0);
|
||||
}
|
||||
|
||||
LOGOS_TEST(transfer_private_without_identifier_uses_random_nonzero_identifier) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const std::string keysJson = std::string("{\"nullifier_public_key\":\"") + std::string(64, 'a') + "\"}";
|
||||
|
||||
module.transfer_private(VALID_ID, keysJson, VALID_U128);
|
||||
uint8_t first[16];
|
||||
memcpy(first, MockWalletFfiCapture::lastTransferPrivateIdentifier, sizeof(first));
|
||||
|
||||
module.transfer_private(VALID_ID, keysJson, VALID_U128);
|
||||
const uint8_t* second = MockWalletFfiCapture::lastTransferPrivateIdentifier;
|
||||
|
||||
uint8_t zero[16] = {0};
|
||||
LOGOS_ASSERT_FALSE(memcmp(first, zero, sizeof(first)) == 0);
|
||||
LOGOS_ASSERT_FALSE(memcmp(first, second, sizeof(first)) == 0);
|
||||
}
|
||||
|
||||
LOGOS_TEST(register_public_account_invalid_hex_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
@ -314,6 +388,148 @@ LOGOS_TEST(register_private_account_success_json) {
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Bridge (L1 Bedrock <-> L2)
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(bridge_withdraw_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.bridge_withdraw(VALID_ID, VALID_ID_2, 100));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_bridge_withdraw"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_EQ(obj["tx_hash"].get<std::string>(), std::string("0xmocktxhash"));
|
||||
LOGOS_ASSERT_TRUE(obj["error"].get<std::string>().empty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(bridge_withdraw_invalid_hex_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.bridge_withdraw("bad", VALID_ID_2, 100));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_bridge_withdraw"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(bridge_withdraw_ffi_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_bridge_withdraw").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.bridge_withdraw(VALID_ID, VALID_ID_2, 100));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Vault claiming
|
||||
// ============================================================================
|
||||
|
||||
LOGOS_TEST(get_vault_balance_invalid_hex_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.get_vault_balance("not-hex").empty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_get_vault_balance"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_vault_balance_returns_decimal_string) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("get_vault_balance_value").returns(42);
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const std::string balance = module.get_vault_balance(VALID_ID);
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_get_vault_balance"));
|
||||
LOGOS_ASSERT_EQ(balance, std::string("42"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(get_vault_balance_ffi_error_returns_empty) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_get_vault_balance").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
LOGOS_ASSERT_TRUE(module.get_vault_balance(VALID_ID).empty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim(VALID_ID, VALID_U128));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_vault_claim"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_EQ(obj["tx_hash"].get<std::string>(), std::string("0xmocktxhash"));
|
||||
LOGOS_ASSERT_TRUE(obj["error"].get<std::string>().empty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_invalid_hex_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim("bad", VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_vault_claim"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_invalid_amount_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim(VALID_ID, "ff"));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_CONTAINS(obj["error"].get<std::string>(), std::string("amount"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_ffi_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_vault_claim").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim(VALID_ID, VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_private_success_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim_private(VALID_ID, VALID_U128));
|
||||
LOGOS_ASSERT(t.cFunctionCalled("wallet_ffi_vault_claim_private"));
|
||||
LOGOS_ASSERT_TRUE(obj["success"].get<bool>());
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_private_invalid_hex_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim_private("bad", VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_FALSE(t.cFunctionCalled("wallet_ffi_vault_claim_private"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_private_invalid_amount_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim_private(VALID_ID, "ff"));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_CONTAINS(obj["error"].get<std::string>(), std::string("amount"));
|
||||
}
|
||||
|
||||
LOGOS_TEST(vault_claim_private_ffi_error_json) {
|
||||
auto t = LogosTestContext("logos_execution_zone");
|
||||
t.mockCFunction("wallet_ffi_vault_claim_private").returns(static_cast<int>(INTERNAL_ERROR));
|
||||
LogosExecutionZoneWalletModule module;
|
||||
|
||||
const nlohmann::json obj = parseObject(module.vault_claim_private(VALID_ID, VALID_U128));
|
||||
LOGOS_ASSERT_FALSE(obj["success"].get<bool>());
|
||||
LOGOS_ASSERT_FALSE(obj["error"].get<std::string>().empty());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pinata claiming
|
||||
// ============================================================================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user