Benjamin-eecs/openrsi-commit-runtime-assets
0168
1#!/bin/bash2set -uxo pipefail3SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"4cd /workspace5git config --global --add safe.directory /workspace6mkdir -p /logs/verifier7git checkout 84cdbdc97389f41a748c72e57b83f9fe89ec12d4 -- tests/test_gains.py || true8git apply --verbose --reject - <<'EOF_R2E_TEST_PATCH'9diff --git a/tests/test_gains.py b/tests/test_gains.py10index 49d65e1..0685a83 10064411--- a/tests/test_gains.py12+++ b/tests/test_gains.py13@@ -140,6 +140,104 @@ class TestGainsClass:14 with pytest.raises(GainsError):15 FIFOUnits(test_fund, transactions)16 17+ def test_stamp_duty_split_lot_does_not_exceed_paid(self):18+ """When a single purchase lot is consumed across multiple19+ disposals, the sum of stamp duty allocated across those20+ disposals must not exceed the original stamp paid.21+22+ Regression guard for the FIFOUnits.sell bug where a23+ partially-consumed lot was re-queued with the FULL original24+ purchase_tax (instead of the unallocated remainder), causing25+ the per-disposal proportional allocation to re-claim the26+ same stamp on every subsequent disposal from the same lot.27+ Worked example with this 3-way 100/100/100 split of a28+ 300-unit lot with ₹1.25 stamp:29+30+ - Disposal 1: round(1.25 × 100/300, 2) = 0.42 (lot re-queued31+ with remainder 0.83)32+ - Disposal 2: round(0.83 × 100/200, 2) = 0.42 (lot re-queued33+ with remainder 0.41)34+ - Disposal 3: round(0.41 × 100/100, 2) = 0.4135+ - Total claimed = 1.25 = exactly stamp paid ✓36+37+ Under the pre-fix code the lot was re-queued with the full38+ 1.25 each time, producing a total of ~2.29 — an 84% over-39+ claim that grows worse with split depth.40+ """41+ fund = Fund("Split-Lot Fund", "SL", "INF000SL0001", "EQUITY")42+ purchase_dt = date(2020, 1, 1)43+ transactions = [44+ TransactionData(45+ date=purchase_dt,46+ description="Purchase",47+ amount=Decimal("3000.00"),48+ units=Decimal("300.000"),49+ nav=Decimal("10.000"),50+ balance=Decimal("300.000"),51+ type=TransactionType.PURCHASE,52+ dividend_rate=None,53+ ),54+ TransactionData(55+ date=purchase_dt,56+ description="*** Stamp Duty ***",57+ amount=Decimal("1.25"),58+ units=None,59+ nav=None,60+ balance=None,61+ type=TransactionType.STAMP_DUTY_TAX,62+ dividend_rate=None,63+ ),64+ # Three 100-unit redemptions on distinct dates (held > 1yr,65+ # so they're LTCG; specific dates don't matter for the66+ # stamp-allocation invariant).67+ TransactionData(68+ date=date(2022, 1, 1),69+ description="Redemption",70+ amount=Decimal("-2000.00"),71+ units=Decimal("-100.000"),72+ nav=Decimal("20.000"),73+ balance=Decimal("200.000"),74+ type=TransactionType.REDEMPTION,75+ dividend_rate=None,76+ ),77+ TransactionData(78+ date=date(2022, 2, 1),79+ description="Redemption",80+ amount=Decimal("-2000.00"),81+ units=Decimal("-100.000"),82+ nav=Decimal("20.000"),83+ balance=Decimal("100.000"),84+ type=TransactionType.REDEMPTION,85+ dividend_rate=None,86+ ),87+ TransactionData(88+ date=date(2022, 3, 1),89+ description="Redemption",90+ amount=Decimal("-2000.00"),91+ units=Decimal("-100.000"),92+ nav=Decimal("20.000"),93+ balance=Decimal("0.000"),94+ type=TransactionType.REDEMPTION,95+ dividend_rate=None,96+ ),97+ ]98+ fifo = FIFOUnits(fund, transactions)99+100+ # Three disposals, three gain entries.101+ assert len(fifo.gains) == 3102+103+ total_stamp = sum((ge.stamp_duty for ge in fifo.gains), Decimal("0"))104+105+ # Section 48 invariant: deductible transfer expense ≤ paid.106+ assert total_stamp <= Decimal("1.25"), (107+ f"Total stamp claimed ({total_stamp}) exceeds stamp paid (1.25); "108+ f"per-disposal stamps were {[ge.stamp_duty for ge in fifo.gains]}"109+ )110+ # Stronger: with the remainder-aware re-queue the rounding111+ # residual gets absorbed into the final disposal, so the112+ # total equals the paid stamp exactly.113+ assert total_stamp == Decimal("1.25")114+115 116 def _ltcg_entry(fy, fund, purchase_date, sale_date, units="100.000"):117 """Build a minimal LTCG GainEntry (EQUITY, held > 1yr) for the118 119EOF_R2E_TEST_PATCH120R2E_APPLY_RC=$?121if [ "$R2E_APPLY_RC" -ne 0 ]; then122 echo "0.000000" > /logs/verifier/reward.txt123 printf '%s' '{"reward": 0.0, "resolved": false, "parse_status": "test_patch_apply_failed"}' > /logs/verifier/reward-details.json124 echo "R2E: test_patch failed to apply (rc=$R2E_APPLY_RC) — failing closed" >&2125 exit 0126fi127( python -m pytest -v tests/test_gains.py ) > /logs/verifier/test_output.log 2>&1128TEST_EXIT_CODE=$?129cat /logs/verifier/test_output.log130python3 "$SCRIPT_DIR/verifier.py" --log /logs/verifier/test_output.log --f2p "$SCRIPT_DIR/f2p.json" --p2p "$SCRIPT_DIR/p2p.json" --test-cmds 'python -m pytest -v tests/test_gains.py' --exit-code "$TEST_EXIT_CODE" --out-dir /logs/verifier || { [ "$TEST_EXIT_CODE" -eq 0 ] && echo "1.0" > /logs/verifier/reward.txt || echo "0.0" > /logs/verifier/reward.txt; }131git checkout 84cdbdc97389f41a748c72e57b83f9fe89ec12d4 -- tests/test_gains.py || true132exit 0133 