Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions .github/actions/rtc-auto-bounty/award_rtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,37 @@
# Accepted forms:
# wallet: RTCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Wallet: RTCxxxx...
# wallet: my-github-username
# payout wallet: RTCxxxx...
# payout address if accepted: RTCxxxx...
# RTC wallet: RTCxxxx...
# miner ID for payout if accepted: contributor-id
# .rtc-wallet: RTCxxxx...
_RTC_ADDRESS_RE = re.compile(r"\bRTC[0-9a-fA-F]{40}\b")

_WALLET_RE = re.compile(
r"(?:^|\n)\s*(?:wallet|\.rtc-wallet)\s*:\s*(\S+)\s*(?:\n|$)",
re.IGNORECASE,
r"""(?:^|\n)\s*
(?:
wallet
| \.rtc-wallet
| payout\s+wallet
| payout\s+address(?:\s+if\s+accepted)?
| rtc\s+wallet
)
\s*:\s*(\S+)\s*(?:\n|$)
""",
re.IGNORECASE | re.VERBOSE,
)

_MINER_ID_RE = re.compile(
r"""(?:^|\n)\s*
miner[_\s]+id(?:\s+for\s+payout(?:\s+if\s+accepted)?)?
\s*:\s*(\S+)\s*(?:\n|$)
""",
re.IGNORECASE | re.VERBOSE,
)

_RTC_CONTEXT_LABELS = ("payout", "wallet", "address")

# Payment-amount override in the PR body (owner can specify a custom amount).
# bounty: 100 RTC
# bounty: 75.5 RTC
Expand Down Expand Up @@ -170,11 +194,27 @@ def validate(self) -> Optional[str]:
# ---------------------------------------------------------------------------


def _clean_wallet_candidate(value: str) -> str:
return value.strip().rstrip(",.;").strip("`")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cleaner still leaves common wrappers such as (RTC...), [RTC...], or <RTC...> intact. Because _WALLET_RE matched an explicit payout directive, resolve_wallet_from_pr_body() returns that wrapped string immediately and never reaches the _RTC_ADDRESS_RE fallback below, even though the valid RTC address is present inside the token. I would either strip ()[]<> here or, safer, have the explicit directive path extract _RTC_ADDRESS_RE from the captured value when the cleaned token is not already a valid RTC/lazy-pay target. A regression test like Payout wallet: (RTC + 40 hex) would lock this down.



def resolve_wallet_from_pr_body(pr_body: str) -> Optional[str]:
"""Extract wallet address from a ``wallet: <addr>`` directive in the PR body."""
"""Extract wallet or lazy-pay miner ID from a PR body directive."""
match = _WALLET_RE.search(pr_body)
if match:
return match.group(1).strip().rstrip(",")
return _clean_wallet_candidate(match.group(1))

match = _MINER_ID_RE.search(pr_body)
if match:
return _clean_wallet_candidate(match.group(1))

for line in pr_body.splitlines():
lowered = line.lower()
if not any(label in lowered for label in _RTC_CONTEXT_LABELS):
continue
rtc_match = _RTC_ADDRESS_RE.search(line)
if rtc_match:
return rtc_match.group(0)
return None


Expand Down
50 changes: 50 additions & 0 deletions .github/actions/rtc-auto-bounty/test_award_rtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,56 @@ def test_github_username_as_wallet(self):
body = "wallet: some-contributor\n"
self.assertEqual(resolve_wallet_from_pr_body(body), "some-contributor")

def test_payout_wallet_directive(self):
wallet = "RTC" + "a" * 40
body = f"Fixes payout routing\n\nPayout wallet: {wallet}\n"
self.assertEqual(resolve_wallet_from_pr_body(body), wallet)

def test_payout_address_directive(self):
wallet = "RTC" + "b" * 40
body = f"Payout address: {wallet}\n"
self.assertEqual(resolve_wallet_from_pr_body(body), wallet)

def test_payout_address_if_accepted_directive(self):
wallet = "RTC" + "c" * 40
body = f"Payout address if accepted: {wallet}\n"
self.assertEqual(resolve_wallet_from_pr_body(body), wallet)

def test_rtc_wallet_directive_variants(self):
wallet = "RTC" + "d" * 40
samples = [
f"RTC Wallet: {wallet}\n",
f"RTC wallet: {wallet}\n",
]
for body in samples:
with self.subTest(body=body):
self.assertEqual(resolve_wallet_from_pr_body(body), wallet)

def test_labeled_rtc_address_line_without_exact_directive(self):
wallet = "RTC" + "e" * 40
body = f"Accepted payout destination -> {wallet}\n"
self.assertEqual(resolve_wallet_from_pr_body(body), wallet)

def test_unlabeled_rtc_address_is_ignored(self):
wallet = "RTC" + "f" * 40
body = f"Implementation note: {wallet}\n"
self.assertIsNone(resolve_wallet_from_pr_body(body))

def test_lazy_pay_miner_id_variants(self):
samples = {
"miner ID for payout if accepted: github:alice\n": "github:alice",
"miner ID: github:bob\n": "github:bob",
"miner_id: lazy-pay-carol\n": "lazy-pay-carol",
}
for body, expected in samples.items():
with self.subTest(body=body):
self.assertEqual(resolve_wallet_from_pr_body(body), expected)

def test_strips_markdown_backticks_and_sentence_punctuation(self):
wallet = "RTC" + "1" * 40
body = f"Payout wallet: `{wallet}`.\n"
self.assertEqual(resolve_wallet_from_pr_body(body), wallet)


class TestResolveWalletFromFile(unittest.TestCase):
"""Test reading wallet from .rtc-wallet file."""
Expand Down
Loading