Bitallx · Complete repair
Alternative repair: capped payout
Pays at most what was funded instead of reverting.
The exploit is blocked for a declared reason, and all 3 security and 10 legitimate-use obligations pass.
What ran, and what it said.
Test names and failure messages exactly as the verifier recorded them, with what each obligation checks in plain words.
Exploit
1 replay · profit over 2,000 USDT- ✓The DeFiHackLabs reproduction of the attack: blockedIt fails in the attack or profit step, for a reason the task declares.test/poc.t.sol:Bitallx_PoC:testExploit()exploit did not yield profit (patched?): 0 <= 2000000000000000000000
Security obligations
3 / 3 pass- ✓A batch whose total exceeds what was funded pays nothing[0, treasury] with nothing funded: the shape a first-element-only fix lets through.test/hidden/security.t.sol:Bitallx_Security:test_multi_element_over_request_pays_nothing()
- ✓The incident’s own shape pays nothingA one-element over-request from a payer who funded nothing, kept as a standing obligation.test/hidden/security.t.sol:Bitallx_Security:test_single_element_over_request_from_fresh_payer_pays_nothing()
- ✓The sum is bounded, not just each element[total, total] funded once must not be paid twice.test/hidden/security.t.sol:Bitallx_Security:test_sum_over_funded_total_rejected_even_when_each_element_fits()
Legitimate use · behaviour
5 / 5 pass- ✓A funded batch payout pays everyone correctlyThe contract’s actual purpose.test/hidden/regression.t.sol:Bitallx_Regression:test_funded_batch_payout_distributes_correctly()
- ✓The patched code still reads the contract’s live storageA fix that inserts or reorders state variables would re-point every one of them.test/hidden/regression.t.sol:Bitallx_Regression:test_live_storage_still_readable()
- ✓The owner’s treasury sweep works, and only for the ownertest/hidden/regression.t.sol:Bitallx_Regression:test_owner_treasury_path_still_works()
- ✓An unfunded payer is refusedtest/hidden/regression.t.sol:Bitallx_Regression:test_payout_rejects_an_unfunded_payer()
- ✓The publisher can still pay a reward, within its limitstest/hidden/regression.t.sol:Bitallx_Regression:test_publisher_can_still_pay_a_reward()
Legitimate use · interface
5 / 5 pass- ✓Original functions still answerCalls the original functions and requires an answer other than “no such function”.test/hidden/invariants_auto.t.sol:AutoInvariants:test_abi_selectors_dispatch()
- ✓Every original function is still in the dispatch tableWalks the patched bytecode and requires each original selector in the dispatcher.test/hidden/invariants_auto.t.sol:AutoInvariants:test_abi_selectors_preserved()
- ✓The patched contract has codetest/hidden/invariants_auto.t.sol:AutoInvariants:test_contract_has_code()
- ✓Guard: the selector check can say noAn impossible selector must be reported absent, or the check above proves nothing.test/hidden/invariants_auto.t.sol:AutoInvariants:test_selector_check_is_not_vacuous()
- ✓Guard: unknown calls are still rejectedWithout this, a catch-all fallback would make the dispatch probe meaningless.test/hidden/invariants_auto.t.sol:AutoInvariants:test_unknown_selector_is_rejected()
The patch
against the original source@@ -100,8 +100,11 @@100100 101101 IBEP20(tokencontract).transferFrom(msg.sender, address(this), totalSendAmount);102102 103+ uint256 remaining = totalSendAmount;103104 for (uint256 i = 0; i < wallet.length; i++) {104− IBEP20(tokencontract).transfer(wallet[i], amount[i]);105+ uint256 pay = amount[i] <= remaining ? amount[i] : remaining;106+ remaining -= pay;107+ IBEP20(tokencontract).transfer(wallet[i], pay);105108 }106109 107110 }
Re-run this grade
offline · same inputsNeeds Foundry 1.7.1 with solc 0.8.30, 0.8.26 and 0.8.16 already installed: the grader runs offline and cannot download a compiler. Python 3.12 or later. Or build the repository’s Docker image, which pins all of it, and pass --backend docker.
$ git clone https://github.com/FarseenSh/evmpatch-env.git && cd evmpatch-env
$ git checkout 165c0ed
$ python -m evmpatch_env.sandbox tasks/bitallx_2025_05 \
--patch worked_example/bitallx_2025_05/controls/alt_complete_fix__capped_payout/Token.sol \
--backend local --sha256Expected output: core 6ab3606f6d0fc6d017bbef39da2bbcfcd52537bef89758c46518aca84418ba55 and strict c519b3250efc2577138a0622f295100406a6c27c7c8d818c641a196db5b4d5bf.
To check a downloaded grade file instead: shasum -a 256 grade.strict.json prints the strict hash.
Control note
from the repositoryA complete repair with a different REMEDY: pay at most what was funded instead of reverting. The oracle recognises it through `*exploit did not yield profit*`, and the security obligations credit it because they assert balances, not a revert.