Skip to content

Commit ecca9ff

Browse files
committed
fix: resolve split-payments lint failures
1 parent 029051b commit ecca9ff

2 files changed

Lines changed: 158 additions & 50 deletions

File tree

src/mpp/methods/tempo/intents.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def _parse_memo_bytes(memo: str | None) -> bytes | None:
5959
hex_str = memo[2:] if memo.startswith("0x") else memo
6060
try:
6161
b = bytes.fromhex(hex_str)
62-
except ValueError:
63-
raise VerificationError(f"Invalid memo hex: {memo}")
62+
except ValueError as err:
63+
raise VerificationError(f"Invalid memo hex: {memo}") from err
6464
if len(b) != 32:
6565
raise VerificationError(f"Memo must be exactly 32 bytes, got {len(b)}")
6666
return b
@@ -87,11 +87,13 @@ def get_transfers(
8787
the top-level memo. Split transfers follow in declaration order.
8888
"""
8989
if not splits:
90-
return [Transfer(
91-
amount=total_amount,
92-
recipient=primary_recipient,
93-
memo=_parse_memo_bytes(primary_memo),
94-
)]
90+
return [
91+
Transfer(
92+
amount=total_amount,
93+
recipient=primary_recipient,
94+
memo=_parse_memo_bytes(primary_memo),
95+
)
96+
]
9597

9698
if len(splits) > MAX_SPLITS:
9799
raise VerificationError(f"Too many splits: {len(splits)} (max {MAX_SPLITS})")
@@ -104,23 +106,27 @@ def get_transfers(
104106
if amt <= 0:
105107
raise VerificationError("Split amount must be greater than zero")
106108
split_sum += amt
107-
split_transfers.append(Transfer(
108-
amount=amt,
109-
recipient=s.recipient,
110-
memo=_parse_memo_bytes(s.memo),
111-
))
109+
split_transfers.append(
110+
Transfer(
111+
amount=amt,
112+
recipient=s.recipient,
113+
memo=_parse_memo_bytes(s.memo),
114+
)
115+
)
112116

113117
if split_sum >= total_amount:
114118
raise VerificationError(
115119
f"Sum of splits ({split_sum}) must be less than total amount ({total_amount})"
116120
)
117121

118122
primary_amount = total_amount - split_sum
119-
transfers = [Transfer(
120-
amount=primary_amount,
121-
recipient=primary_recipient,
122-
memo=_parse_memo_bytes(primary_memo),
123-
)]
123+
transfers = [
124+
Transfer(
125+
amount=primary_amount,
126+
recipient=primary_recipient,
127+
memo=_parse_memo_bytes(primary_memo),
128+
)
129+
]
124130
transfers.extend(split_transfers)
125131
return transfers
126132

@@ -506,12 +512,16 @@ def _verify_transfer_logs(
506512
if len(expected) == 1:
507513
t = expected[0]
508514
return self._verify_single_transfer_log(
509-
receipt, request.currency, t.recipient, t.amount, t.memo,
515+
receipt,
516+
request.currency,
517+
t.recipient,
518+
t.amount,
519+
t.memo,
510520
expected_sender,
511521
)
512522

513523
# Multi-transfer: order-insensitive matching
514-
sorted_expected = sorted(expected, key=lambda t: (0 if t.memo else 1))
524+
sorted_expected = sorted(expected, key=lambda t: 0 if t.memo else 1)
515525
logs = receipt.get("logs", [])
516526
used_logs: set[int] = set()
517527

@@ -547,7 +557,10 @@ def _verify_transfer_logs(
547557
amount = int(data[2:66], 16)
548558
memo_topic = topics[3]
549559
expected_memo_hex = "0x" + transfer.memo.hex()
550-
if amount == transfer.amount and memo_topic.lower() == expected_memo_hex.lower():
560+
if (
561+
amount == transfer.amount
562+
and memo_topic.lower() == expected_memo_hex.lower()
563+
):
551564
used_logs.add(log_idx)
552565
found = True
553566
break
@@ -778,7 +791,7 @@ def _validate_calls(self, calls: tuple, request: ChargeRequest) -> None:
778791
request.methodDetails.splits,
779792
)
780793

781-
sorted_expected = sorted(expected, key=lambda t: (0 if t.memo else 1))
794+
sorted_expected = sorted(expected, key=lambda t: 0 if t.memo else 1)
782795
used_calls: set[int] = set()
783796

784797
for transfer in sorted_expected:
@@ -830,7 +843,7 @@ def _validate_transaction_payload(self, signature: str, request: ChargeRequest)
830843
request.methodDetails.splits,
831844
)
832845

833-
sorted_expected = sorted(expected, key=lambda t: (0 if t.memo else 1))
846+
sorted_expected = sorted(expected, key=lambda t: 0 if t.memo else 1)
834847
used_calls: set[int] = set()
835848

836849
for transfer in sorted_expected:
@@ -843,7 +856,9 @@ def _validate_transaction_payload(self, signature: str, request: ChargeRequest)
843856
call_to_bytes, call_data_bytes = call_item[0], call_item[2]
844857
if not call_to_bytes or not call_data_bytes:
845858
continue
846-
to_hex = call_to_bytes.hex() if isinstance(call_to_bytes, bytes) else str(call_to_bytes)
859+
to_hex = (
860+
call_to_bytes.hex() if isinstance(call_to_bytes, bytes) else str(call_to_bytes)
861+
)
847862
if ("0x" + to_hex).lower() != request.currency.lower():
848863
continue
849864
raw = call_data_bytes

tests/test_tempo.py

Lines changed: 120 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
TRANSFER_WITH_MEMO_SELECTOR,
2929
TRANSFER_WITH_MEMO_TOPIC,
3030
ChargeIntent,
31-
Transfer,
3231
_match_single_transfer_calldata,
3332
_match_transfer_calldata,
3433
_parse_memo_bytes,
@@ -2498,23 +2497,54 @@ def test_empty_splits_returns_single_transfer(self) -> None:
24982497
assert len(transfers) == 1
24992498

25002499
def test_single_split(self) -> None:
2501-
splits = [Split(amount="300000", recipient="0x1111111111111111111111111111111111111111")]
2502-
transfers = get_transfers(1_000_000, "0x2222222222222222222222222222222222222222", None, splits)
2500+
splits = [
2501+
Split(
2502+
amount="300000",
2503+
recipient="0x1111111111111111111111111111111111111111",
2504+
)
2505+
]
2506+
transfers = get_transfers(
2507+
1_000_000,
2508+
"0x2222222222222222222222222222222222222222",
2509+
None,
2510+
splits,
2511+
)
25032512
assert len(transfers) == 2
25042513
assert transfers[0].amount == 700_000 # primary gets remainder
25052514
assert transfers[1].amount == 300_000
25062515

25072516
def test_primary_inherits_memo(self) -> None:
25082517
memo = "0x" + "ab" * 32
2509-
splits = [Split(amount="100000", recipient="0x1111111111111111111111111111111111111111")]
2510-
transfers = get_transfers(1_000_000, "0x2222222222222222222222222222222222222222", memo, splits)
2518+
splits = [
2519+
Split(
2520+
amount="100000",
2521+
recipient="0x1111111111111111111111111111111111111111",
2522+
)
2523+
]
2524+
transfers = get_transfers(
2525+
1_000_000,
2526+
"0x2222222222222222222222222222222222222222",
2527+
memo,
2528+
splits,
2529+
)
25112530
assert transfers[0].memo is not None
25122531
assert transfers[1].memo is None
25132532

25142533
def test_split_with_memo(self) -> None:
25152534
split_memo = "0x" + "cd" * 32
2516-
splits = [Split(amount="100000", recipient="0x1111111111111111111111111111111111111111", memo=split_memo)]
2517-
transfers = get_transfers(1_000_000, "0x2222222222222222222222222222222222222222", None, splits)
2535+
splits = [
2536+
Split(
2537+
amount="100000",
2538+
recipient="0x1111111111111111111111111111111111111111",
2539+
memo=split_memo,
2540+
)
2541+
]
2542+
transfers = get_transfers(
2543+
1_000_000,
2544+
"0x2222222222222222222222222222222222222222",
2545+
None,
2546+
splits,
2547+
)
25182548
assert transfers[1].memo is not None
25192549
assert transfers[1].memo[0] == 0xCD
25202550

@@ -2524,7 +2554,12 @@ def test_multiple_splits_preserve_order(self) -> None:
25242554
Split(amount="200000", recipient="0x2222222222222222222222222222222222222222"),
25252555
Split(amount="50000", recipient="0x3333333333333333333333333333333333333333"),
25262556
]
2527-
transfers = get_transfers(1_000_000, "0x4444444444444444444444444444444444444444", None, splits)
2557+
transfers = get_transfers(
2558+
1_000_000,
2559+
"0x4444444444444444444444444444444444444444",
2560+
None,
2561+
splits,
2562+
)
25282563
assert len(transfers) == 4
25292564
assert transfers[0].amount == 650_000 # primary
25302565
assert transfers[1].amount == 100_000
@@ -2548,18 +2583,21 @@ def test_rejects_zero_split_amount(self) -> None:
25482583

25492584
def test_rejects_too_many_splits(self) -> None:
25502585
splits = [
2551-
Split(amount="1000", recipient=f"0x{hex(i + 2)[2:].zfill(40)}")
2552-
for i in range(11)
2586+
Split(amount="1000", recipient=f"0x{hex(i + 2)[2:].zfill(40)}") for i in range(11)
25532587
]
25542588
with pytest.raises(VerificationError, match="Too many splits"):
25552589
get_transfers(1_000_000, "0x0000000000000000000000000000000000000001", None, splits)
25562590

25572591
def test_max_splits_allowed(self) -> None:
25582592
splits = [
2559-
Split(amount="1000", recipient=f"0x{hex(i + 2)[2:].zfill(40)}")
2560-
for i in range(10)
2593+
Split(amount="1000", recipient=f"0x{hex(i + 2)[2:].zfill(40)}") for i in range(10)
25612594
]
2562-
transfers = get_transfers(1_000_000, "0x0000000000000000000000000000000000000001", None, splits)
2595+
transfers = get_transfers(
2596+
1_000_000,
2597+
"0x0000000000000000000000000000000000000001",
2598+
None,
2599+
splits,
2600+
)
25632601
assert len(transfers) == 11
25642602
assert transfers[0].amount == 990_000
25652603

@@ -2778,18 +2816,50 @@ class TestMatchSingleTransferCalldata:
27782816
AMOUNT = 1000000
27792817
MEMO = bytes.fromhex("ab" * 32)
27802818

2781-
def _build_calldata(self, selector: str, recipient: str, amount: int, memo_hex: str = "") -> str:
2819+
def _build_calldata(
2820+
self,
2821+
selector: str,
2822+
recipient: str,
2823+
amount: int,
2824+
memo_hex: str = "",
2825+
) -> str:
27822826
to_padded = recipient[2:].lower().zfill(64)
27832827
amount_padded = hex(amount)[2:].zfill(64)
27842828
return f"{selector}{to_padded}{amount_padded}{memo_hex}"
27852829

27862830
def test_memo_requires_transfer_with_memo_selector(self) -> None:
2787-
calldata = self._build_calldata(TRANSFER_SELECTOR, self.RECIPIENT, self.AMOUNT, "ab" * 32)
2788-
assert _match_single_transfer_calldata(calldata, self.RECIPIENT, self.AMOUNT, self.MEMO) is False
2831+
calldata = self._build_calldata(
2832+
TRANSFER_SELECTOR,
2833+
self.RECIPIENT,
2834+
self.AMOUNT,
2835+
"ab" * 32,
2836+
)
2837+
assert (
2838+
_match_single_transfer_calldata(
2839+
calldata,
2840+
self.RECIPIENT,
2841+
self.AMOUNT,
2842+
self.MEMO,
2843+
)
2844+
is False
2845+
)
27892846

27902847
def test_memo_accepts_correct_selector(self) -> None:
2791-
calldata = self._build_calldata(TRANSFER_WITH_MEMO_SELECTOR, self.RECIPIENT, self.AMOUNT, "ab" * 32)
2792-
assert _match_single_transfer_calldata(calldata, self.RECIPIENT, self.AMOUNT, self.MEMO) is True
2848+
calldata = self._build_calldata(
2849+
TRANSFER_WITH_MEMO_SELECTOR,
2850+
self.RECIPIENT,
2851+
self.AMOUNT,
2852+
"ab" * 32,
2853+
)
2854+
assert (
2855+
_match_single_transfer_calldata(
2856+
calldata,
2857+
self.RECIPIENT,
2858+
self.AMOUNT,
2859+
self.MEMO,
2860+
)
2861+
is True
2862+
)
27932863

27942864
def test_no_memo_rejects_transfer_with_memo_selector(self) -> None:
27952865
"""When no memo expected, transferWithMemo calldata must be rejected."""
@@ -2832,10 +2902,14 @@ def test_single_transfer_rejects_transfer_with_memo_log(self) -> None:
28322902
methodDetails=MethodDetails(),
28332903
)
28342904
receipt = {
2835-
"logs": [self._make_log(
2836-
TRANSFER_WITH_MEMO_TOPIC, self.RECIPIENT, self.AMOUNT,
2837-
memo="0x" + "ff" * 32,
2838-
)],
2905+
"logs": [
2906+
self._make_log(
2907+
TRANSFER_WITH_MEMO_TOPIC,
2908+
self.RECIPIENT,
2909+
self.AMOUNT,
2910+
memo="0x" + "ff" * 32,
2911+
)
2912+
],
28392913
}
28402914
assert intent._verify_transfer_logs(receipt, request) is False
28412915

@@ -2856,7 +2930,9 @@ def test_multi_split_rejects_transfer_with_memo_log_for_memoless(self) -> None:
28562930
self._make_log(TRANSFER_TOPIC, self.RECIPIENT, 700000),
28572931
# split as TransferWithMemo (should be rejected)
28582932
self._make_log(
2859-
TRANSFER_WITH_MEMO_TOPIC, self.SPLIT_RECIPIENT, 300000,
2933+
TRANSFER_WITH_MEMO_TOPIC,
2934+
self.SPLIT_RECIPIENT,
2935+
300000,
28602936
memo="0x" + "ff" * 32,
28612937
),
28622938
],
@@ -2869,8 +2945,8 @@ class TestSplitsFeePayerRejection:
28692945

28702946
@pytest.mark.anyio
28712947
async def test_splits_with_fee_payer_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
2872-
from mpp.server import Mpp
28732948
from mpp.methods.tempo import tempo
2949+
from mpp.server import Mpp
28742950

28752951
monkeypatch.setenv("MPP_SECRET_KEY", "test-secret-key")
28762952
server = Mpp.create(
@@ -2883,7 +2959,12 @@ async def test_splits_with_fee_payer_raises(self, monkeypatch: pytest.MonkeyPatc
28832959
await server.charge(
28842960
authorization=None,
28852961
amount="1.00",
2886-
splits=[{"amount": "300000", "recipient": "0x1111111111111111111111111111111111111111"}],
2962+
splits=[
2963+
{
2964+
"amount": "300000",
2965+
"recipient": "0x1111111111111111111111111111111111111111",
2966+
}
2967+
],
28872968
fee_payer=True,
28882969
)
28892970

@@ -2900,11 +2981,23 @@ def test_short_primary_memo_raises(self) -> None:
29002981
get_transfers(1_000_000, "0x01", "0x" + "ab" * 10, None)
29012982

29022983
def test_invalid_split_memo_raises(self) -> None:
2903-
splits = [Split(amount="100000", recipient="0x1111111111111111111111111111111111111111", memo="badhex")]
2984+
splits = [
2985+
Split(
2986+
amount="100000",
2987+
recipient="0x1111111111111111111111111111111111111111",
2988+
memo="badhex",
2989+
)
2990+
]
29042991
with pytest.raises(VerificationError, match="Invalid memo hex"):
29052992
get_transfers(1_000_000, "0x01", None, splits)
29062993

29072994
def test_short_split_memo_raises(self) -> None:
2908-
splits = [Split(amount="100000", recipient="0x1111111111111111111111111111111111111111", memo="0x" + "ab" * 5)]
2995+
splits = [
2996+
Split(
2997+
amount="100000",
2998+
recipient="0x1111111111111111111111111111111111111111",
2999+
memo="0x" + "ab" * 5,
3000+
)
3001+
]
29093002
with pytest.raises(VerificationError, match="exactly 32 bytes"):
29103003
get_transfers(1_000_000, "0x01", None, splits)

0 commit comments

Comments
 (0)