From af641e99ec06aa41fcaa3a888ee71f9139bd1914 Mon Sep 17 00:00:00 2001 From: GTO90 Date: Thu, 12 Feb 2026 21:16:11 -0600 Subject: [PATCH 1/7] security: [T1-08] add regtest-only runtime guards to MockOracleManager (DGB-SEC-005) MockOracleManager::GetCurrentPrice() was called without a REGTEST check in validation.cpp, meaning mock oracle prices could leak into consensus code on mainnet/testnet if the singleton was instantiated. Fix adds guards at two layers: - validation.cpp: gate mock oracle access behind REGTEST chain check - MockOracleManager: GetCurrentPrice() and SetMockPrice() now reject calls on non-REGTEST networks as defense-in-depth --- src/oracle/mock_oracle.cpp | 10 ++++++++++ src/validation.cpp | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/oracle/mock_oracle.cpp b/src/oracle/mock_oracle.cpp index 52c4cd4fd2..4ac8de508d 100644 --- a/src/oracle/mock_oracle.cpp +++ b/src/oracle/mock_oracle.cpp @@ -68,12 +68,22 @@ MockOracleManager& MockOracleManager::GetInstance() CAmount MockOracleManager::GetCurrentPrice() const { + // SECURITY (DGB-SEC-005): Runtime guard — reject on non-REGTEST networks + if (Params().GetChainType() != ChainType::REGTEST) { + return 0; + } LOCK(cs_price); return mockPriceMicroUSD; } void MockOracleManager::SetMockPrice(CAmount price_micro_usd) { + // SECURITY (DGB-SEC-005): Runtime guard — mock oracle must only operate in REGTEST + if (Params().GetChainType() != ChainType::REGTEST) { + LogPrintf("MockOracleManager: SECURITY - SetMockPrice rejected on non-REGTEST network\n"); + return; + } + LOCK(cs_price); // Validate price is reasonable diff --git a/src/validation.cpp b/src/validation.cpp index 2570c49450..4bbf9bcf2c 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1827,10 +1827,11 @@ CAmount GetOraclePriceForTransaction(const CTransaction& tx, int nHeight) { return oracle_price_micro_usd; } - // For regtest, check the mock oracle + // For regtest only, check the mock oracle // Mock oracle now returns price directly in micro-USD (no conversion needed) // e.g., 6500 micro-USD = $0.0065 per DGB - if (MockOracleManager::GetInstance().IsEnabled()) { + // SECURITY FIX (DGB-SEC-005): Guard mock oracle access behind REGTEST check + if (Params().GetChainType() == ChainType::REGTEST && MockOracleManager::GetInstance().IsEnabled()) { CAmount mock_price_micro_usd = MockOracleManager::GetInstance().GetCurrentPrice(); if (mock_price_micro_usd > 0) { LogPrintf("DigiDollar: Using mock oracle price: %lld micro-USD ($%.6f)\n", From 5725e32f73f975253b20b246e43768a7a0bd8ce2 Mon Sep 17 00:00:00 2001 From: GTO90 Date: Thu, 12 Feb 2026 21:42:21 -0600 Subject: [PATCH 2/7] build: fix CI depends failures for macOS ARM64 and Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OpenSSL 1.1.1w Configure misparses multi-word flags like "-arch arm64" when passed as positional args — the shell splits them and Configure treats the second word as a target name, causing "target already defined" on ARM64 macOS. Pass CFLAGS/CPPFLAGS as VAR=value assignments per OpenSSL's official INSTALL docs. libcurl 8.5.0 fopen.c uses fileno/fdopen (POSIX) which are hidden by the depends system's strict -std=c11. Add -D_GNU_SOURCE for Linux. --- depends/packages/libcurl.mk | 3 +++ depends/packages/openssl.mk | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/depends/packages/libcurl.mk b/depends/packages/libcurl.mk index 6376e37653..2a3e5ddce6 100644 --- a/depends/packages/libcurl.mk +++ b/depends/packages/libcurl.mk @@ -15,6 +15,9 @@ define $(package)_set_vars $(package)_config_opts += --disable-tftp --without-brotli --without-zstd --without-libidn2 $(package)_config_opts += --without-libpsl --without-nghttp2 --disable-dependency-tracking $(package)_config_opts_linux=--with-pic + # -D_GNU_SOURCE exposes POSIX functions (fileno, fdopen) that libcurl's + # fopen.c needs but are hidden by the depends system's strict -std=c11. + $(package)_cppflags_linux=-D_GNU_SOURCE $(package)_config_env_linux=LIBS="-ldl -lpthread" $(package)_config_opts_mingw32=--with-pic $(package)_config_env_mingw32=LIBS="-lws2_32 -lcrypt32" diff --git a/depends/packages/openssl.mk b/depends/packages/openssl.mk index 1bbcb671cf..83f676062d 100644 --- a/depends/packages/openssl.mk +++ b/depends/packages/openssl.mk @@ -11,7 +11,13 @@ define $(package)_set_vars $(package)_config_opts+=no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-shared $(package)_config_opts+=no-ssl-trace no-ssl2 no-ssl3 no-tests no-unit-test no-weak-ssl-ciphers $(package)_config_opts+=no-zlib no-zlib-dynamic no-static-engine no-comp no-afalgeng - $(package)_config_opts+=no-engine no-hw no-asm $($(package)_cflags) $($(package)_cppflags) + $(package)_config_opts+=no-engine no-hw no-asm + # Pass compiler flags as VAR=value assignments (per OpenSSL INSTALL docs) + # rather than positional args, because multi-word flags like "-arch arm64" + # get split by the shell and OpenSSL's Configure misparses the second word + # as a target name, causing "target already defined" errors on ARM64 macOS. + $(package)_config_opts+=CFLAGS="$($(package)_cflags)" + $(package)_config_opts+=CPPFLAGS="$($(package)_cppflags)" $(package)_config_opts_linux=-fPIC -D_GNU_SOURCE $(package)_config_opts_freebsd=-fPIC $(package)_config_opts_x86_64_linux=linux-x86_64 From 42272564a215302b4c398a2cf3aadf162102ec43 Mon Sep 17 00:00:00 2001 From: GTO90 Date: Fri, 13 Feb 2026 11:23:23 -0600 Subject: [PATCH 3/7] build: fix deferred evaluation for OpenSSL CFLAGS in depends Use $$ deferred evaluation for CFLAGS/CPPFLAGS assignments so OS-specific flags (cflags_linux, cppflags_linux) appended by funcs.mk after set_vars are included. Route -fPIC and -D_GNU_SOURCE through proper cflags/cppflags variables instead of bare config_opts to avoid OpenSSL's "Mixing make variables" rejection. --- depends/packages/openssl.mk | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/depends/packages/openssl.mk b/depends/packages/openssl.mk index 83f676062d..45b730600c 100644 --- a/depends/packages/openssl.mk +++ b/depends/packages/openssl.mk @@ -16,10 +16,13 @@ define $(package)_set_vars # rather than positional args, because multi-word flags like "-arch arm64" # get split by the shell and OpenSSL's Configure misparses the second word # as a target name, causing "target already defined" errors on ARM64 macOS. - $(package)_config_opts+=CFLAGS="$($(package)_cflags)" - $(package)_config_opts+=CPPFLAGS="$($(package)_cppflags)" - $(package)_config_opts_linux=-fPIC -D_GNU_SOURCE - $(package)_config_opts_freebsd=-fPIC + # Use $$ for deferred evaluation so OS-specific flags (cflags_linux, etc.) + # appended by funcs.mk after set_vars runs are included in the expansion. + $(package)_config_opts+=CFLAGS="$$($(package)_cflags)" + $(package)_config_opts+=CPPFLAGS="$$($(package)_cppflags)" + $(package)_cflags_linux=-fPIC + $(package)_cppflags_linux=-D_GNU_SOURCE + $(package)_cflags_freebsd=-fPIC $(package)_config_opts_x86_64_linux=linux-x86_64 $(package)_config_opts_i686_linux=linux-generic32 $(package)_config_opts_arm_linux=linux-generic32 From 86372e6e3833a9e5fccff9036b518d173de62286 Mon Sep 17 00:00:00 2001 From: GTO90 Date: Fri, 13 Feb 2026 11:53:32 -0600 Subject: [PATCH 4/7] build: remove empty ARFLAGS from OpenSSL config_env The depends system never defines $(package)_arflags, so ARFLAGS=$($(package)_arflags) in config_env exported an empty ARFLAGS="" to the environment. When CFLAGS/CPPFLAGS are passed as VAR=value assignments (not positional flags), OpenSSL 1.1.1w's Configure sets $anyuseradd=false and falls back to reading env vars. The empty ARFLAGS overrides the target default "r" from Configurations/00-base-templates.conf, producing a Makefile with ARFLAGS= (empty). This causes "ar: two different operation options specified" on Linux and "ar: illegal option -- /" on macOS during build_libs, as ar interprets the archive path as flags. Fix: remove ARFLAGS from config_env entirely, letting Configure use its target default ARFLAGS="r". Verified locally: - Linux x86_64: ar r apps/libapps.a ... (ARFLAGS=r in Makefile) - macOS ARM64: ar r apps/libapps.a ... (ARFLAGS=r in Makefile) - Full build_libs completes successfully on darwin64-arm64-cc --- depends/packages/openssl.mk | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/depends/packages/openssl.mk b/depends/packages/openssl.mk index 45b730600c..5780089a92 100644 --- a/depends/packages/openssl.mk +++ b/depends/packages/openssl.mk @@ -5,7 +5,14 @@ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8 define $(package)_set_vars - $(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" + # Do NOT export ARFLAGS in config_env. The depends system never defines + # $(package)_arflags, so it expands to empty string. When CFLAGS/CPPFLAGS + # are passed as VAR=value (no positional flags), OpenSSL's Configure sets + # $anyuseradd=false and falls back to reading env vars. An empty ARFLAGS + # in the environment overrides the target default ("r"), producing a + # Makefile with ARFLAGS= (empty), which causes "ar: two different + # operation options specified" at build time. + $(package)_config_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" $(package)_config_env_android=ANDROID_NDK_ROOT=$(host_prefix)/native $(package)_config_opts=no-capieng no-dso no-dtls1 no-ec_nistp_64_gcc_128 no-gost $(package)_config_opts+=no-md2 no-rc5 no-rdrand no-rfc3779 no-sctp no-shared From 0884c5c2604742170500a36c503aa5340830cfab Mon Sep 17 00:00:00 2001 From: GTO90 Date: Fri, 13 Feb 2026 13:38:17 -0600 Subject: [PATCH 5/7] test: add diagnostic messages to CI-failing mint validation tests Replace BOOST_CHECK with BOOST_CHECK_MESSAGE in 5 mint validation tests that fail on CI but pass locally. Diagnostic output captures the exact reject reason, script sizes, collateral amounts, and script type identification to help debug the CI-specific failure. --- src/test/digidollar_validation_tests.cpp | 47 +++++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/test/digidollar_validation_tests.cpp b/src/test/digidollar_validation_tests.cpp index 0ded290409..06f4171a41 100644 --- a/src/test/digidollar_validation_tests.cpp +++ b/src/test/digidollar_validation_tests.cpp @@ -342,8 +342,14 @@ BOOST_FIXTURE_TEST_CASE(transaction_validation_mint_tx, DigiDollarValidationTest CTransaction tx(mtx); TxValidationState state; - BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); - BOOST_CHECK(state.IsValid()); + bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); + BOOST_CHECK_MESSAGE(result, "[transaction_validation_mint_tx] Expected valid, got reject: " + state.GetRejectReason() + + " | collateralScript.size()=" + std::to_string(collateralScript.size()) + + " ddScript.size()=" + std::to_string(ddScript.size()) + + " requiredCollateral=" + std::to_string(requiredCollateral) + + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) + + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); + BOOST_CHECK_MESSAGE(state.IsValid(), "[transaction_validation_mint_tx] state invalid: " + state.GetRejectReason()); } BOOST_FIXTURE_TEST_CASE(transaction_validation_invalid_mint_amount, DigiDollarValidationTestSetup) @@ -466,8 +472,14 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_valid_basic_mint, DigiDollarValidationTe TxValidationState state; // This should pass when implementation is complete - BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); - BOOST_CHECK(state.IsValid()); + bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); + BOOST_CHECK_MESSAGE(result, "[mint_validation_valid_basic_mint] Expected valid, got reject: " + state.GetRejectReason() + + " | collateralScript.size()=" + std::to_string(collateralScript.size()) + + " ddScript.size()=" + std::to_string(ddScript.size()) + + " requiredCollateral=" + std::to_string(requiredCollateral) + + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) + + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); + BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_valid_basic_mint] state invalid: " + state.GetRejectReason()); } BOOST_FIXTURE_TEST_CASE(mint_validation_insufficient_collateral, DigiDollarValidationTestSetup) @@ -724,8 +736,13 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_dca_multiplier_adjustment, DigiDollarVal TxValidationState state; // Should pass with adjusted collateral - BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); - BOOST_CHECK(state.IsValid()); + bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); + BOOST_CHECK_MESSAGE(result, "[mint_validation_dca_multiplier] Expected valid, got reject: " + state.GetRejectReason() + + " | systemCollateral=" + std::to_string(validationContext.systemCollateral) + + " adjustedCollateral=" + std::to_string(adjustedCollateral) + + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) + + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); + BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_dca_multiplier] state invalid: " + state.GetRejectReason()); // Test with original 500% collateral - should fail CAmount originalCollateral = (static_cast(ddAmount) * COIN * 500 * 100) / mockOraclePrice; @@ -881,8 +898,13 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_edge_case_exact_minimum, DigiDollarValid CTransaction tx(mtx); TxValidationState state; - BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); - BOOST_CHECK(state.IsValid()); + bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); + BOOST_CHECK_MESSAGE(result, "[mint_validation_edge_case_exact_minimum] Expected valid, got reject: " + state.GetRejectReason() + + " | ddAmount=" + std::to_string(ddAmount) + + " exactCollateral=" + std::to_string(exactCollateral) + + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) + + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); + BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_edge_case_exact_minimum] state invalid: " + state.GetRejectReason()); } BOOST_FIXTURE_TEST_CASE(mint_validation_edge_case_exact_maximum, DigiDollarValidationTestSetup) @@ -917,8 +939,13 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_edge_case_exact_maximum, DigiDollarValid CTransaction tx(mtx); TxValidationState state; - BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); - BOOST_CHECK(state.IsValid()); + bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); + BOOST_CHECK_MESSAGE(result, "[mint_validation_edge_case_exact_maximum] Expected valid, got reject: " + state.GetRejectReason() + + " | ddAmount=" + std::to_string(ddAmount) + + " requiredCollateral=" + std::to_string(requiredCollateral) + + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) + + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); + BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_edge_case_exact_maximum] state invalid: " + state.GetRejectReason()); } // ============================================================================= From 1a137ee5bf4202591ede404f041d50756bea65c9 Mon Sep 17 00:00:00 2001 From: GTO90 Date: Fri, 13 Feb 2026 13:46:07 -0600 Subject: [PATCH 6/7] test: fix cross-suite state pollution causing CI-only mint test failures Replace ClearFreeze() with ClearHistory() in the validation test fixture. ClearFreeze() only clears freeze flags, but UpdateState() (called during ValidateDigiDollarTransaction) recalculates volatility from stale price history left by earlier test suites (e.g., digidollar_health_tests) and re-sets the freeze. This caused all "valid mint" tests to fail with "minting-frozen-volatility" on CI where the test suite ordering (linker-dependent) places health_tests before validation_tests. ClearHistory() resets price history, volatility state, and all freeze flags, ensuring each test starts from a clean state. --- src/test/digidollar_validation_tests.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/digidollar_validation_tests.cpp b/src/test/digidollar_validation_tests.cpp index 06f4171a41..f83a28f6a2 100644 --- a/src/test/digidollar_validation_tests.cpp +++ b/src/test/digidollar_validation_tests.cpp @@ -37,8 +37,10 @@ struct DigiDollarValidationTestSetup : public TestingSetup { testPubKey = testKey.GetPubKey(); testXOnlyKey = XOnlyPubKey(testPubKey); - // Clear volatility freeze state from previous tests - DigiDollar::Volatility::VolatilityMonitor::ClearFreeze(); + // Clear ALL volatility state from previous test suites. + // ClearFreeze() alone is insufficient — UpdateState() recalculates + // from stale price history and can re-set freeze flags. + DigiDollar::Volatility::VolatilityMonitor::ClearHistory(); // Validation context is initialized in member initializer list } From 07d64894eb22ba0c6c684e793ecac1571972a4a7 Mon Sep 17 00:00:00 2001 From: GTO90 Date: Fri, 13 Feb 2026 14:51:18 -0600 Subject: [PATCH 7/7] fix: resolve bad auto-merge that dropped DD amount consistency check The merge with feature/digidollar-v1 silently dropped critical code: 1. DD amount double-counting prevention in ValidateMintTransaction() The OP_RETURN and DD token output both encode ddAmount. Without the consistency check, totalDD was counted twice (e.g. 10000 + 10000 = 20000), requiring 2x the collateral and failing all valid mint tests. 2. Missing #include 3. Improved log format for insufficient-collateral diagnostic Also adopts the base branch's test file which includes: - DD OP_RETURN outputs required for T1-04b NUMS verification - Proper NUMS key usage via GetCollateralNUMSKey() - ClearHistory() fix for cross-suite volatility state pollution --- src/digidollar/validation.cpp | 19 ++++- src/test/digidollar_validation_tests.cpp | 93 +++++++++++++----------- 2 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/digidollar/validation.cpp b/src/digidollar/validation.cpp index 8ea1ad5962..9ef339c0b8 100644 --- a/src/digidollar/validation.cpp +++ b/src/digidollar/validation.cpp @@ -5,6 +5,7 @@ #include #include #include +#include // Phase 1 metadata tracking support using DigiDollar::ScriptMetadata; @@ -870,7 +871,18 @@ bool ValidateMintTransaction(const CTransaction& tx, LogPrintf("DigiDollar: Invalid DD amount: %d\n", ddAmount); return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-dd-amount"); } - totalDD += ddAmount; + // If totalDD was already set from OP_RETURN, verify consistency + // rather than double-counting the DD amount. + if (totalDD > 0) { + if (ddAmount != totalDD) { + LogPrintf("DigiDollar: DD amount mismatch: token output=%lld, OP_RETURN=%lld\n", + (long long)ddAmount, (long long)totalDD); + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-dd-amount-mismatch", + "DD token output amount does not match OP_RETURN amount"); + } + } else { + totalDD += ddAmount; + } } // If we can't extract (cross-node validation), we'll calculate after loop } @@ -1001,8 +1013,9 @@ bool ValidateMintTransaction(const CTransaction& tx, // Verify sufficient collateral if (totalCollateral < requiredCollateral) { - LogPrintf("DigiDollar: Insufficient collateral: provided %d, required %d\n", - totalCollateral, requiredCollateral); + LogPrintf("DigiDollar: Insufficient collateral: provided %lld, required %lld (totalDD=%lld, lockPeriod=%lld)\n", + (long long)totalCollateral, (long long)requiredCollateral, + (long long)totalDD, (long long)lockPeriod); return state.Invalid(TxValidationResult::TX_CONSENSUS, "insufficient-collateral"); } diff --git a/src/test/digidollar_validation_tests.cpp b/src/test/digidollar_validation_tests.cpp index fea9021140..b788410e73 100644 --- a/src/test/digidollar_validation_tests.cpp +++ b/src/test/digidollar_validation_tests.cpp @@ -52,6 +52,22 @@ struct DigiDollarValidationTestSetup : public TestingSetup { int mockSystemCollateral; int mockHeight; DigiDollar::ValidationContext validationContext; + + // Helper: Build a DD mint OP_RETURN output script + // Format: OP_RETURN <"DD"> + CScript MakeDDMintOpReturn(CAmount ddAmount, int64_t lockHeight, int lockTier) { + CScript script; + script << OP_RETURN; + std::vector dd_marker = {'D', 'D'}; + script << dd_marker; + script << CScriptNum(1); // Type = MINT + script << CScriptNum::serialize(ddAmount); + script << CScriptNum::serialize(lockHeight); + script << CScriptNum(lockTier); + std::vector keyData(testXOnlyKey.begin(), testXOnlyKey.end()); + script << keyData; + return script; + } }; // ============================================================================ @@ -315,43 +331,51 @@ BOOST_FIXTURE_TEST_CASE(script_validation_non_dd_script, DigiDollarValidationTes BOOST_FIXTURE_TEST_CASE(transaction_validation_mint_tx, DigiDollarValidationTestSetup) { - // Create a mock mint transaction + // Create a valid mint transaction with all required components: + // - Collateral input + // - DD OP_RETURN with owner pubkey (required for NUMS verification per T1-04b) + // - Collateral output (P2TR with NUMS internal key) + // - DD token output CMutableTransaction mtx; mtx.nVersion = 0x01000770; // DD_TX_MINT (type=1 in bits 24-31, marker=0x0770 in bits 0-15) - // Add collateral input (simplified for test) + // Add collateral input mtx.vin.resize(1); mtx.vin[0].prevout = COutPoint(uint256S("0x1234"), 0); - // Add collateral output + // Set up mint parameters DigiDollar::MintParams params; params.ddAmount = 10000; // $100.00 - params.lockHeight = mockHeight + 30 * 24 * 60 * 4; + params.lockHeight = mockHeight + 30 * 24 * 60 * 4; // 30-day lock params.ownerKey = testXOnlyKey; - params.internalKey = testXOnlyKey; + params.internalKey = DigiDollar::GetCollateralNUMSKey(); params.oracleKeys = DigiDollar::GetOracleKeys(15); CScript collateralScript = DigiDollar::CreateCollateralP2TR(params); CAmount requiredCollateral = (static_cast(params.ddAmount) * COIN * 500 * 100) / mockOraclePrice; - mtx.vout.resize(2); - mtx.vout[0] = CTxOut(requiredCollateral, collateralScript); + // DD OP_RETURN with owner pubkey (required for NUMS verification) + CScript opReturn = CScript() << OP_RETURN + << std::vector{'D', 'D'} + << CScriptNum(1) + << CScriptNum(params.ddAmount) + << CScriptNum(params.lockHeight) + << CScriptNum(1) // lockTier 1 = 30 days + << std::vector(testXOnlyKey.begin(), testXOnlyKey.end()); + + mtx.vout.resize(3); + mtx.vout[0] = CTxOut(0, opReturn); + mtx.vout[1] = CTxOut(requiredCollateral, collateralScript); // Add DD token output CScript ddScript = DigiDollar::CreateDigiDollarP2TR(testXOnlyKey, params.ddAmount); - mtx.vout[1] = CTxOut(0, ddScript); // DD tokens have no DGB value + mtx.vout[2] = CTxOut(0, ddScript); // DD tokens have no DGB value CTransaction tx(mtx); TxValidationState state; - bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); - BOOST_CHECK_MESSAGE(result, "[transaction_validation_mint_tx] Expected valid, got reject: " + state.GetRejectReason() - + " | collateralScript.size()=" + std::to_string(collateralScript.size()) - + " ddScript.size()=" + std::to_string(ddScript.size()) - + " requiredCollateral=" + std::to_string(requiredCollateral) - + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) - + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); - BOOST_CHECK_MESSAGE(state.IsValid(), "[transaction_validation_mint_tx] state invalid: " + state.GetRejectReason()); + BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); + BOOST_CHECK(state.IsValid()); } BOOST_FIXTURE_TEST_CASE(transaction_validation_invalid_mint_amount, DigiDollarValidationTestSetup) @@ -494,13 +518,9 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_valid_basic_mint, DigiDollarValidationTe // This should pass when implementation is complete bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); - BOOST_CHECK_MESSAGE(result, "[mint_validation_valid_basic_mint] Expected valid, got reject: " + state.GetRejectReason() - + " | collateralScript.size()=" + std::to_string(collateralScript.size()) - + " ddScript.size()=" + std::to_string(ddScript.size()) - + " requiredCollateral=" + std::to_string(requiredCollateral) - + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) - + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); - BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_valid_basic_mint] state invalid: " + state.GetRejectReason()); + BOOST_TEST_MESSAGE("mint_validation_valid_basic_mint result: " + std::to_string(result) + " reason: " + state.GetRejectReason()); + BOOST_CHECK(result); + BOOST_CHECK(state.IsValid()); } BOOST_FIXTURE_TEST_CASE(mint_validation_insufficient_collateral, DigiDollarValidationTestSetup) @@ -779,13 +799,8 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_dca_multiplier_adjustment, DigiDollarVal TxValidationState state; // Should pass with adjusted collateral - bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); - BOOST_CHECK_MESSAGE(result, "[mint_validation_dca_multiplier] Expected valid, got reject: " + state.GetRejectReason() - + " | systemCollateral=" + std::to_string(validationContext.systemCollateral) - + " adjustedCollateral=" + std::to_string(adjustedCollateral) - + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) - + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); - BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_dca_multiplier] state invalid: " + state.GetRejectReason()); + BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); + BOOST_CHECK(state.IsValid()); // Test with original 500% collateral - should fail CAmount originalCollateral = (static_cast(ddAmount) * COIN * 500 * 100) / mockOraclePrice; @@ -962,13 +977,8 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_edge_case_exact_minimum, DigiDollarValid CTransaction tx(mtx); TxValidationState state; - bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); - BOOST_CHECK_MESSAGE(result, "[mint_validation_edge_case_exact_minimum] Expected valid, got reject: " + state.GetRejectReason() - + " | ddAmount=" + std::to_string(ddAmount) - + " exactCollateral=" + std::to_string(exactCollateral) - + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) - + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); - BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_edge_case_exact_minimum] state invalid: " + state.GetRejectReason()); + BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); + BOOST_CHECK(state.IsValid()); } BOOST_FIXTURE_TEST_CASE(mint_validation_edge_case_exact_maximum, DigiDollarValidationTestSetup) @@ -1013,13 +1023,8 @@ BOOST_FIXTURE_TEST_CASE(mint_validation_edge_case_exact_maximum, DigiDollarValid CTransaction tx(mtx); TxValidationState state; - bool result = DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state); - BOOST_CHECK_MESSAGE(result, "[mint_validation_edge_case_exact_maximum] Expected valid, got reject: " + state.GetRejectReason() - + " | ddAmount=" + std::to_string(ddAmount) - + " requiredCollateral=" + std::to_string(requiredCollateral) - + " collateralType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(collateralScript))) - + " ddType=" + std::to_string(static_cast(DigiDollar::IdentifyScriptType(ddScript)))); - BOOST_CHECK_MESSAGE(state.IsValid(), "[mint_validation_edge_case_exact_maximum] state invalid: " + state.GetRejectReason()); + BOOST_CHECK(DigiDollar::ValidateDigiDollarTransaction(tx, validationContext, state)); + BOOST_CHECK(state.IsValid()); } // =============================================================================