diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 335eb30..e2175e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,33 +1,57 @@ name: CI - on: push: branches: [main] pull_request: branches: [main] - jobs: ci: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - - name: Cache uses: Swatinem/rust-cache@v2 - - name: Format check run: cargo fmt --all -- --check - - name: Clippy run: cargo clippy --all-targets --all-features -- -D warnings - + - name: Verify fee constants match smart contract + run: | + curl -sf https://raw.githubusercontent.com/Nodus-protocol/Nodus-Protocol-Smart-Contract/main/src/math.rs -o /tmp/sc_math.rs + extract_value() { + grep -E "^\s*pub const ${1}:" "$2" | sed -E 's/.*=\s*([0-9_]+)\s*;.*/\1/' | tr -d '_' + } + SC_FEE_NUM=$(extract_value FEE_NUMERATOR /tmp/sc_math.rs) + SC_FEE_DEN=$(extract_value FEE_DENOMINATOR /tmp/sc_math.rs) + SC_MIN_LIQ=$(extract_value MINIMUM_LIQUIDITY /tmp/sc_math.rs) + CE_FEE_NUM=$(extract_value FEE_NUMERATOR src/pool/math.rs) + CE_FEE_DEN=$(extract_value FEE_DENOMINATOR src/pool/math.rs) + CE_MIN_LIQ=$(extract_value MINIMUM_LIQUIDITY src/pool/math.rs) + echo "Smart Contract: FEE_NUMERATOR=$SC_FEE_NUM FEE_DENOMINATOR=$SC_FEE_DEN MINIMUM_LIQUIDITY=$SC_MIN_LIQ" + echo "Core Engine: FEE_NUMERATOR=$CE_FEE_NUM FEE_DENOMINATOR=$CE_FEE_DEN MINIMUM_LIQUIDITY=$CE_MIN_LIQ" + FAIL=0 + if [ "$SC_FEE_NUM" != "$CE_FEE_NUM" ]; then + echo "::error::FEE_NUMERATOR mismatch: SmartContract=$SC_FEE_NUM CoreEngine=$CE_FEE_NUM" + FAIL=1 + fi + if [ "$SC_FEE_DEN" != "$CE_FEE_DEN" ]; then + echo "::error::FEE_DENOMINATOR mismatch: SmartContract=$SC_FEE_DEN CoreEngine=$CE_FEE_DEN" + FAIL=1 + fi + if [ "$SC_MIN_LIQ" != "$CE_MIN_LIQ" ]; then + echo "::error::MINIMUM_LIQUIDITY mismatch: SmartContract=$SC_MIN_LIQ CoreEngine=$CE_MIN_LIQ" + FAIL=1 + fi + if [ "$FAIL" = "1" ]; then + echo "AMM constants have drifted between Smart Contract and Core Engine repos. Update src/pool/math.rs to match src/math.rs in Nodus-Protocol-Smart-Contract." + exit 1 + fi + echo "AMM constants verified in sync with Smart Contract." - name: Test run: cargo test --all-features - - name: Build run: cargo build --release diff --git a/src/pool/math.rs b/src/pool/math.rs index 2a12f67..df94cd2 100644 --- a/src/pool/math.rs +++ b/src/pool/math.rs @@ -1,5 +1,18 @@ // AMM constant-product math — mirrors src/math.rs in the Smart Contract repo. -// Any formula change in the contract must be reflected here. +// +// SYNCHRONIZATION WARNING: +// These constants MUST match `src/math.rs` in the Nodus-Protocol-Smart-Contract +// repository exactly. CI verifies this automatically (see +// .github/workflows/ci.yml, "Verify fee constants match smart contract" step), +// which fetches the live Smart Contract source and fails the build on drift. +// +// If you change a value here, you must also change it in the Smart Contract +// repo in the same release cycle, or swap quotes from this engine will not +// match what the deployed contract actually executes. +// +// Tracking: see https://github.com/Nodus-protocol/Nodus-Protocol-Core-Engine/issues/61 +// for the longer-term fix (a shared `nodus-amm-types` crate that both repos +// depend on, eliminating this manual sync requirement entirely). pub const FEE_NUMERATOR: u128 = 997; pub const FEE_DENOMINATOR: u128 = 1_000; @@ -171,4 +184,25 @@ mod tests { let large = price_impact_bps(500_000, 1_000_000); assert!(large > small); } + + /// Defense-in-depth: pins the expected constant values directly in a test, + /// so a local `cargo test` run (not just CI's network-dependent fetch step) + /// catches an accidental change to these numbers before it's committed. + /// If you are intentionally changing the fee, update this test AND the + /// Smart Contract repo's src/math.rs in the same PR/release. + #[test] + fn fee_constants_match_known_smart_contract_values() { + assert_eq!( + FEE_NUMERATOR, 997, + "FEE_NUMERATOR must match Smart Contract src/math.rs" + ); + assert_eq!( + FEE_DENOMINATOR, 1_000, + "FEE_DENOMINATOR must match Smart Contract src/math.rs" + ); + assert_eq!( + MINIMUM_LIQUIDITY, 1_000, + "MINIMUM_LIQUIDITY must match Smart Contract src/math.rs" + ); + } }