Bitallx · Incomplete repair
Checks the first amount only
Bounds only the first element, so [0, treasury] with nothing funded still drains the contract.
The exploit is blocked, but 2 of 3 security obligations fail: the repair is incomplete.
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()Payout total does not match the funded amount
Security obligations
1 / 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()unfunded multi-element payout drained the contract's own USDT treasury: 0 < 2029473999999999986000
- ✓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()a batch funded once and paid twice drained the contract's own USDT treasury: 1929473999999999986000 < 2029473999999999986000
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@@ -91,6 +91,8 @@9191 uint256 totalSendAmount9292 ) external {9393 require(wallet.length == amount.length, "The length of 2 arrays should be the same");94+ require(amount.length == 0 || amount[0] <= totalSendAmount,95+ "Payout total does not match the funded amount");9496 9597 uint256 allowance = IBEP20(tokencontract).allowance(msg.sender, address(this));9698 require(allowance >= totalSendAmount, "Insufficient token allowance");
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/incomplete_fix__first_element_only/Token.sol \
--backend local --sha256Expected output: core c36f1eea6ce6ab132b76299df29964a9566e8086cdf0a7db00a33666aa9d903a and strict 03db5f251b9d9253f365994b54d0063dc4402e4a32e4ea8f9533566f89b731cd.
To check a downloaded grade file instead: shasum -a 256 grade.strict.json prints the strict hash.
Control note
from the repositoryThe load-bearing incomplete repair: bounds only amount[0], so the one-element PoC is blocked and an exploit oracle alone would grade it like the reference, while a two-element array [0, treasury] with totalSendAmount = 0 still drains the contract.