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.

Not solved

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.

Exploitnot countedhistorical attack
Security3 / 4obligations pass
Legitimate use9 / 12obligations pass
Canariesbrickedtamper and breakage flags
acceptsA plain pass/fail exploit test would accept this fix: the exploit test fails for a reason the task does not declare. SCAR’s phased exploit check, scope check and state check do not count that as a block.
The checks

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
src/contracts/Token.sol+2 −8
@@ -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 inputs

Needs 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.

Terminal
$ 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 --sha256

Expected 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 repository

transferFrom 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.