MCAI · Breaks legitimate use
transferFrom disabled
Every transferFrom reverts. The exploit test fails for a reason the task does not declare, and every legitimate spender is locked out too.
The exploit test fails for a reason the task does not declare, which never counts as a block. 1 of 4 security obligations fails and 3 legitimate-use obligations fail.
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 11 ETH- –The DeFiHackLabs reproduction of the attack: not countedIt fails for a reason the task does not declare, which never counts as a block.test/poc.t.sol:MCAI_PoC:testExploit()transferFrom disabled
Security obligations
3 / 4 pass- ✓The tax wallet cannot move an ordinary holder’s tokens without approvalThe bug stated for every holder, not just the pool the exploit drained.test/hidden/security.t.sol:MCAI_Security:test_taxwallet_cannot_move_holder_tokens_without_approval()
- ✓The tax wallet cannot move the pool’s tokens without approvalChecked on the balance change itself, so a fix that breaks the allowance view cannot game it.test/hidden/security.t.sol:MCAI_Security:test_taxwallet_cannot_move_pair_tokens_without_approval()
- ✗With approval, the tax wallet’s allowance is debited by exactly what it movesThe original moved the tokens and debited nothing.test/hidden/security.t.sol:MCAI_Security:test_taxwallet_transferFrom_debits_allowance()transferFrom disabled
- ✓No one can burn a holder’s tokens to the dead wallet without approvalThe second branch of the same broken helper. A fix aimed only at the tax wallet leaves it open.test/hidden/security.t.sol:MCAI_Security:test_unapproved_spender_cannot_burn_to_dead_wallet()
Legitimate use · behaviour
4 / 7 pass- ✓Buying through the pool the incident used still worksIncludes the token’s fee path.test/hidden/regression.t.sol:MCAI_Regression:test_amm_buy_works()
- ✗Approve, then transferFrom, works and debits the allowance exactlyA fix that disables transferFrom, or never debits, fails here.test/hidden/regression.t.sol:MCAI_Regression:test_approved_transferFrom_works_and_debits_allowance()transferFrom disabled
- ✓Token metadata and the pool’s balance are intactDecimals still 9, supply and symbol present, the pool still holds MCAI.test/hidden/regression.t.sol:MCAI_Regression:test_metadata_preserved()
- ✓A holder-to-holder transfer workstest/hidden/regression.t.sol:MCAI_Regression:test_normal_transfer_works()
- ✗With approval, the tax wallet can still spend like anyone elseA fix cannot close the hole by blocking the tax wallet forever.test/hidden/regression.t.sol:MCAI_Regression:test_taxwallet_transferFrom_with_approval_moves_tokens()transferFrom disabled
- ✗transferFrom takes exactly the amount out of the senderCatches a fix that quietly credits the sender back: an unlimited mint.test/hidden/regression.t.sol:MCAI_Regression:test_transferFrom_debits_the_sender_exactly()transferFrom disabled
- ✓A spender with no approval still cannot move tokensA fix that removes the allowance check altogether fails here.test/hidden/regression.t.sol:MCAI_Regression:test_unapproved_spender_cannot_move_tokens()
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@@ -264,14 +264,8 @@264264 address recipient,265265 uint256 amount266266 ) public override returns (bool) {267− _transfer(sender, recipient, amount);268− uint256 _amount = _decreaseAllowance(sender, recipient, amount);269− _approve(270− sender,271− _msgSender(),272− _allowances[sender][_msgSender()].sub( _amount, "ERC20: transfer amount exceeds allowance")273− );274− return true;267+ sender; recipient; amount;268+ revert("transferFrom disabled");275269 }276270277271 function _approve(address owner, address spender, uint256 amount) private {
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/mcai_2025_01 \
--patch worked_example/mcai_2025_01/controls/revert_all_transferFrom/Token.sol \
--backend local --sha256Expected output: core eb1734ba697795540dd9ee76fc7fa6662a9032e79543223375f292189e0ac059 and strict 55cd27a343e05eb91b884f97548c8132053d16ae4227537f043e2a2be92fb530.
To check a downloaded grade file instead: shasum -a 256 grade.strict.json prints the strict hash.
Control note
from the repositorytransferFrom always reverts: the exploit is 'blocked' but every legitimate transferFrom is too. Expected: not_solved, hidden regression test_approved_transferFrom_works_and_debits_allowance fails, canary bricked.