Skip to content
Merged
3 changes: 3 additions & 0 deletions depends/packages/libcurl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 20 additions & 4 deletions depends/packages/openssl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,31 @@ $(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
$(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_linux=-fPIC -D_GNU_SOURCE
$(package)_config_opts_freebsd=-fPIC
$(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.
# 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
Expand Down
13 changes: 11 additions & 2 deletions src/consensus/dca.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,17 @@ int DynamicCollateralAdjustment::CalculateSystemHealth(CAmount totalCollateral,
CAmount healthCalculation;
const CAmount maxSafeDividend = std::numeric_limits<CAmount>::max() / 100;
if (collateralValueCents > maxSafeDividend) {
// Scale down both numerator and denominator to avoid overflow
healthCalculation = (collateralValueCents / 1000) * 100 / (totalDD / 1000);
// Scale down both numerator and denominator to avoid overflow.
// SECURITY FIX (DGB-SEC-003): Guard against totalDD/1000==0 which
// causes division by zero when totalDD is between 1 and 999.
CAmount scaledDD = totalDD / 1000;
if (scaledDD == 0) {
// totalDD is between 1 and 999 cents (< $10) — collateral dwarfs it,
// so health is at maximum.
healthCalculation = 30000;
} else {
healthCalculation = (collateralValueCents / 1000) * 100 / scaledDD;
}
} else {
healthCalculation = (collateralValueCents * 100) / totalDD;
}
Expand Down
29 changes: 27 additions & 2 deletions src/digidollar/health.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -685,14 +685,39 @@ int SystemHealthMonitor::CalculateSystemHealth(CAmount ddSupply, CAmount collate
return 300; // Perfect health if no DD issued
}

// Guard against invalid price (same pattern as DCA::CalculateSystemHealth)
if (price <= 0) {
return 0; // Cannot calculate without valid price
}

// Calculate collateral value in cents
// price is in cents (100 = $1.00 DGB price)
// collateral is in satoshis
// Formula: (satoshis * price_cents) / COIN = cents
CAmount collateralValue = (collateral * price) / COIN;
// Guard against overflow: divide first when collateral is large
CAmount collateralValue;
const CAmount maxSafe = std::numeric_limits<CAmount>::max() / price;
if (collateral > maxSafe) {
collateralValue = (collateral / COIN) * price;
} else {
collateralValue = (collateral * price) / COIN;
}

// Health = (Collateral Value / DD Value) * 100
int health = static_cast<int>((collateralValue * 100) / ddSupply);
// Guard against overflow in numerator
int health;
const CAmount maxSafeMul = std::numeric_limits<CAmount>::max() / 100;
if (collateralValue > maxSafeMul) {
// When ddSupply is 1-99, ddSupply/100 is 0 due to integer division.
// Return max health since collateral dwarfs the tiny supply.
CAmount scaledSupply = ddSupply / 100;
if (scaledSupply == 0) {
return 300;
}
health = static_cast<int>(collateralValue / scaledSupply);
} else {
health = static_cast<int>((collateralValue * 100) / ddSupply);
}

// Cap at reasonable maximum
return std::min(health, 300);
Expand Down
19 changes: 16 additions & 3 deletions src/digidollar/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <digidollar/validation.h>
#include <digidollar/scripts.h>
#include <digidollar/digidollar.h>
#include <digidollar/health.h>

// Phase 1 metadata tracking support
using DigiDollar::ScriptMetadata;
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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");
}

Expand Down
52 changes: 42 additions & 10 deletions src/test/digidollar_validation_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -50,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"> <type=1> <ddAmount> <lockHeight> <lockTier> <ownerXOnlyPubKey>
CScript MakeDDMintOpReturn(CAmount ddAmount, int64_t lockHeight, int lockTier) {
CScript script;
script << OP_RETURN;
std::vector<unsigned char> 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<unsigned char> keyData(testXOnlyKey.begin(), testXOnlyKey.end());
script << keyData;
return script;
}
};

// ============================================================================
Expand Down Expand Up @@ -313,31 +331,45 @@ 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<uint64_t>(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<unsigned char>{'D', 'D'}
<< CScriptNum(1)
<< CScriptNum(params.ddAmount)
<< CScriptNum(params.lockHeight)
<< CScriptNum(1) // lockTier 1 = 30 days
<< std::vector<unsigned char>(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;
Expand Down