56517 sc low zeroxswapverifier validates struct but executes external actions enabling direct fund theft
Submitted on Oct 17th 2025 at 07:44:08 UTC by @DarkWingCipher2277 for Audit Comp | Alchemix V3
Report ID: #56517
Report Type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
The ZeroXSwapVerifier library in Alchemix V3 validates only the SlippageAndActions.actions struct field while ignoring the separate external bytes[] actions parameter that the 0x Settler actually executes when processing execute(SlippageAndActions, bytes[]) or executeMetaTxn(SlippageAndActions, bytes[], address, bytes) calls. This struct/executor parameter mismatch allows an attacker to craft calldata where benign actions pass verification but malicious actions—such as transferFrom(adapter, attacker, amount)—are executed by the Settler, enabling direct theft of protocol-controlled funds from any adapter that pre-approves the Settler and forwards verified 0x calldata without re-binding the external actions to the verified struct.
Vulnerability Details
The ZeroXSwapVerifier library is used by MYT strategies to validate 0x swap calldata before forwarding it to the 0x Settler contract for execution. The verifier implements two main flows for validating Settler calls:
EXECUTE Flow (execute(SlippageAndActions saa, bytes[] actions)):
function _verifyExecuteCalldata(bytes calldata data, address owner, address targetToken, uint256 maxSlippageBps) internal view {
// Decodes BOTH struct and external actions array
(SlippageAndActions memory saa, ) = abi.decode(data, (SlippageAndActions, bytes));
// BUG: Only validates struct.actions, discards external bytes[]
_verifyActions(saa.actions, owner, targetToken, maxSlippageBps);
}EXECUTE_META_TXN Flow (executeMetaTxn(SlippageAndActions saa, bytes[] actions, address, bytes)):
Root Cause:
The SlippageAndActions struct contains an embedded bytes[] actions field:
However, the Settler's execute(SlippageAndActions, bytes[]) function signature accepts two separate parameters: the struct and an external bytes[] actions array. The verifier:
Decodes both parameters from the calldata
Validates only
saa.actions(from the struct)Never validates the external
bytes[]actions that the Settler will actually execute
This creates a critical divergence: the verifier checks one set of actions while the Settler executes a completely different set.
The attack is selector-agnostic—while the PoC uses ERC20's transferFrom selector (0x23b872dd) for simplicity, the real 0x Settler actions use custom action selectors like TRANSFER_FROM (0x8d68a156). The core vulnerability exists regardless of specific selector values: any action in the external bytes[] array executes without verification as long as the Settler processes it and the adapter has pre-approved the Settler for token transfers.
Exploitability:
An attacker crafts calldata for
execute(SlippageAndActions, bytes[])where:SlippageAndActions.actionscontains safe/benign actions (e.g., empty or valid swaps)The external
bytes[]actions contains malicious operations liketransferFrom(token, adapter, attacker, largeAmount)
The verifier validates the benign
struct.actionsand returnstrueThe adapter forwards the entire calldata to the Settler, which executes the external malicious actions, not the verified struct actions.
If the adapter pre-approved the Settler with
token.approve(settler, type(uint256).max)(common pattern for gas optimization in swap routes), the malicioustransferFromsucceeds and drains adapter-held funds to the attacker.
Affected Code Locations:
ZeroXSwapVerifier._verifyExecuteCalldata()— validates onlysaa.actions, ignores externalbytes[]ZeroXSwapVerifier._verifyExecuteMetaTxnCalldata()— same pattern for meta-transaction flowAny MYT strategy adapter that calls
ZeroXSwapVerifier.verifySwapCalldata()and then forwards the calldata to a 0x Settler with pre-existing token approvals.
No Binding Mechanism:
There is no check to ensure struct.actions == external actions and no direct validation pass over the external bytes[] parameter, creating an exploitable gap between verification and execution.
Calldata Forwarding Evidence:
The MYTStrategy base contract (inherited by all strategy adapters) is designed to integrate with DEX aggregators including 0x Matcha. While the exact forwarding implementation is strategy-dependent, the verifier's existence and explicit flagging by the team for security review indicates production integration. The vulnerability exists in the verifier logic regardless of specific adapter implementations—any adapter that calls verifySwapCalldata() and subsequently forwards verified calldata to the Settler (via low-level call or direct function invocation) is vulnerable.
grep-ready location: src/MYTStrategy.sol imports ZeroXSwapVerifier at line ~5-10 (exact line varies by version)
Impact Details
Classification: Critical — Direct theft of any user funds
Mechanism of Theft:
When MYT strategy adapters integrate 0x swaps for rebalancing or yield optimization, they typically:
Pre-approve the Settler: To avoid repeated approval gas costs, adapters often execute
IERC20(token).approve(settler, type(uint256).max)during initialization or first swapCall the verifier: Before executing a swap, the adapter calls
ZeroXSwapVerifier.verifySwapCalldata(calldata, address(this), sellToken, maxSlippage)to validate the 0x quoteForward calldata to Settler: After verification passes, the adapter forwards the entire calldata to the Settler via low-level call or direct function invocation
Under the current implementation, an attacker can exploit this flow:
Step 1: Craft Malicious Calldata
Step 2: Pass Verification
The verifier decodes and validates only saa.actions (empty/benign), returning true.
Step 3: Execute Theft
The adapter forwards exploitCalldata to the Settler, which:
Ignores
saa.actionsExecutes
maliciousActions[0]:transferFrom(targetToken, adapter, attacker, balance)Transfers all adapter-held tokens to the attacker in a single atomic transaction
Funds at Risk:
All tokens held by any adapter that:
Uses
ZeroXSwapVerifierfor 0x swap validationPre-approves the 0x Settler for token transfers
Forwards verified calldata without independent re-validation of the external actions parameter
This includes pooled user deposits, treasury funds, and collateral managed by MYT strategy adapters integrated with 0x for DEX aggregation.
Attack Characteristics:
No prerequisites: Works immediately if the adapter has pre-approval and holds funds
Single transaction: Complete theft in one atomic call
No time constraints: Can be executed anytime the adapter holds a non-zero balance
Bypasses all verification: The verifier's
verifySwapCalldatareturnstruefor the exploit payload
Additional Attack Surface:
Even if adapters don't pre-approve the Settler, the mismatch enables:
Slippage manipulation: Execute high-slippage swaps while verifying zero-slippage struct
Recipient override: Send swap proceeds to attacker while verifying adapter as recipient
Token substitution: Swap different tokens than verified, if the Settler allows multi-token operations
Note: Even without pre-approval, the verifier mismatch enables manipulation of slippage parameters, recipient addresses, and token types during "verified" swaps, which may constitute lower-severity impacts depending on adapter implementation.
References
Vulnerable Contract:
ZeroXSwapVerifier.sol: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/utils/ZeroXSwapVerifier.sol
Affected Components:
MYTStrategy.sol (imports verifier): https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/MYTStrategy.sol
All derived strategy adapters (see scope list)
Competition Context:
Immunefi Audit Competition: https://immunefi.com/audit-competition/alchemix-v3-audit-competition/
Technical walkthrough (0x swap focus at 32:52): https://www.youtube.com/watch?v=WleguK1Jh44&t=1972s
Proof of Concept:
Complete runnable Foundry test attached as
ZeroXVerifierMismatch.t.sol
Proof of Concept
Proof of Concept
Overview
This proof-of-concept demonstrates a critical struct/executor parameter mismatch in the ZeroXSwapVerifier library where the verifier validates only the SlippageAndActions.actions struct field while ignoring the separate external bytes[] actions parameter that the 0x Settler actually executes. The PoC shows how an attacker can craft calldata with benign struct actions that pass verification while malicious external actions drain adapter-held pooled funds via transferFrom in a single atomic transaction, achieving direct theft under the guise of a "verified" 0x swap call.
Vulnerability Impact
Severity: Critical Impact Type: Direct theft of any user funds (in-scope per Alchemix V3 Immunefi program)
When triggered, the verifier/executor mismatch allows:
Direct fund drainage from any adapter holding pooled treasury or user deposits
Bypass of all verification gates via the struct/external parameter divergence
Single-transaction theft with no prerequisites beyond adapter pre-approval and balance
Circumvention of slippage, recipient, and token validation encoded in the verified struct
The attack works atomically: the verifier approves calldata based on benign struct actions, then the adapter forwards the full payload to the Settler, which executes the attacker-controlled external actions instead, transferring tokens from the adapter directly to the attacker.
Technical Root Cause
The ZeroXSwapVerifier library validates 0x Settler calls by decoding the execute(SlippageAndActions, bytes[]) or executeMetaTxn(SlippageAndActions, bytes[], address, bytes) calldata and checking only the SlippageAndActions.actions struct field:
The Settler's actual execution consumes the external bytes[] actions parameter, not the struct-embedded actions. This creates a divergence:
Verifier path: Inspects and validates
struct.actionsExecutor path: Runs the external
bytes[]actions
An attacker exploits this by:
Passing benign/empty actions in
SlippageAndActions.actions(verified)Passing malicious
transferFrom(adapter → attacker, amount)in the externalbytes[]
Because adapters typically pre-approve the Settler with token.approve(settler, type(uint256).max) for gas optimization, the malicious transferFrom succeeds without requiring per-call approvals.
Prerequisites
Foundry installed: Latest version via
foundryupSolidity compiler: 0.8.28 (matches project contracts)
No external dependencies: RPC URL, API keys, or live deployment addresses are not required for this unit-test PoC
Setup Instructions
Initialize Foundry project:
Install dependencies:
Create remappings.txt:
Create test file: Save the PoC code below as
test/ZeroXVerifierMismatch.t.sol
Complete PoC Implementation
Running the PoC
Execute Complete Test Suite from the project root, compile and run:
Output Snippet
Test Breakdown
test_Critical_Direct_Theft_From_Pooled_Funds_Via_VerifierMismatch:
Setup: Treasury funds adapter with 1,000e18 pooled tokens; adapter pre-approves Settler for
type(uint256).maxCraft exploit calldata:
Struct data: benign/empty (would pass real verifier's
_verifyActions(struct.actions, ...)checks)External actions:
transferFrom(token, adapter, attacker, 900e18)
Execute via verifier gate:
ZeroXSwapVerifier.decodeAndVerifyExecute(callData)returnstrue(verifies struct, ignores external actions)Adapter forwards calldata to Settler via low-level call
Settler executes external actions:
Decodes and runs
transferFrom(adapter → attacker, 900e18)from external actions arraySucceeds because adapter pre-approved Settler
Assertions:
Attacker balance increases by 900e18 (direct theft)
Adapter balance decreases by 900e18 (funds drained from pooled deposits)
This demonstrates complete bypass of the verifier: the "verified" call results in direct fund loss under a single atomic transaction with no user-visible warning or revert.
Recommended Fixes
Immediate Remediation:
Decode and validate the external bytes[] actions parameter in both _verifyExecuteCalldata and _verifyExecuteMetaTxnCalldata, and enforce equality with struct.actions to prevent desynchronization:
This ensures the verifier inspects exactly what the Settler will execute, eliminating the struct/executor divergence.
Long-term Security Improvements
Explicit owner/from validation: In
_verifyTransferFrom, validate not onlytoken == targetTokenbut also that thefromaddress equals the expectedowner(adapter/strategy address) to prevent unauthorized source transfers even when actions are properly boundReplace removed balance checks: Re-introduce explicit amount bounds or upper limits tied to slippage or quote parameters to defend against oversized transfers if upstream quoting or slippage logic is bypassed
Complete UniswapV3/RFQ parsers: Replace the stub implementations of
_extractTokenFromUniswapFillsand_extractTokenAndAmountFromRFQwith full parsing logic to ensure token and amount extraction is accurate rather than relying on naive fixed-offset decodes that can be bypassedIntegration testing: Add Foundry fork tests that call real adapters using this verifier against mainnet 0x Settler contracts to validate end-to-end binding in production environments
Responsible Disclosure
This vulnerability was discovered during the Alchemix V3 Audit Competition on Immunefi and is disclosed exclusively to the Alchemix team via the Immunefi platform in accordance with responsible disclosure guidelines. All testing was performed on local Foundry environments without interaction with live mainnet or testnet deployments.
Conclusion
This PoC demonstrates a critical struct/executor parameter mismatch in the ZeroXSwapVerifier library that enables direct theft of protocol-controlled pooled funds. The runnable test suite proves:
The verifier validates only
SlippageAndActions.actionsfrom the struct, ignoring the externalbytes[]actions executed by the SettlerAn attacker can craft calldata with benign struct actions (pass verification) and malicious external actions (drain funds via
transferFrom)Adapters that pre-approve the Settler and forward verified calldata suffer complete fund loss in a single atomic transaction
The minimal fix—binding and validating the external actions parameter—eliminates the vulnerability
Was this helpful?