| title | description | type | network | date | loss_usd | returned_usd | tags | subcategory | vulnerable_contracts | tokens_lost | attacker_addresses | malicious_token | attack_block | attack_txs | reproduction_command | sources | |||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1inch Calldata Corruption |
Exploiting Yul assembly calldata corruption via integer underflow to hijack resolver callbacks and drain funds |
Exploit |
|
2025-03-05 |
5000000 |
4490000 |
|
|
|
|
|
|
forge test --match-contract Exploit_OneInch -vvv |
|
-
Attacker deploys exploit contract that implements EIP-1271 signature validation and exposes a
settle()function that forwards crafted orders toSettlement.settleOrders(). -
Attacker crafts malicious order payload containing 6 nested orders calculated ABI encoding offsets and a negative interaction length value.
-
Attacker calls
Settlement.settleOrders()which processes the nested order chain recursively through_settleOrder(). -
Integer underflow corrupts suffix write location when the vulnerable Yul assembly calculates where to write the order suffix, the negative length causes the write position to underflow, writing the legitimate suffix over zero-padding instead of at the end of calldata.
-
Fake suffix with victim as resolver is read by the
DynamicSuffixlibrary, which reads from the end of calldata where the attacker placed a crafted suffix containing the victim's address as the "resolver". -
Settlement calls
victim.resolveOrders()believing it's the legitimate resolver. Since the caller is the trusted Settlement contract, the victim (TrustedVolumes) transfers funds to the attacker. -
Attacker receives ~$5M across multiple tokens (USDC, WETH) from TrustedVolumes resolver contract.
The 1inch Settlement exploit targeted the deprecated Fusion V1 Settlement contract, exploiting a vulnerability in the _settleOrder() function. The attack represents a calldata corruption technique.
1inch Fusion is a gasless swap protocol where resolvers (market makers) fill user orders. The Settlement contract coordinates order execution by:
- Processing orders through
settleOrders()β_settleOrder() - Appending a "suffix" containing resolver information to each order
- Calling
resolveOrders()on the resolver to execute token transfers
The assumption: only the legitimate resolver should be called, and it trusts the Settlement contract as the caller.
The vulnerability exists in _settleOrder()'s Yul assembly block:
function _settleOrder(
bytes calldata data,
address resolver,
uint256 totalFee,
bytes memory tokensAndAmounts
) private {
// ... order validation ...
assembly {
// [1] Read interaction offset from attacker-controlled calldata
let interactionLengthOffset := calldataload(add(data.offset, 0x40))
let interactionOffset := add(interactionLengthOffset, 0x20)
// [2] Read interaction length
let interactionLength := calldataload(add(data.offset, interactionLengthOffset))
// ... copy calldata to memory at 'ptr' ...
// [3] Calculate suffix write position - Underflow
let offset := add(add(ptr, interactionOffset), interactionLength)
// [4] Write suffix data including resolver address
mstore(add(offset, 0x04), totalFee)
mstore(add(offset, 0x24), resolver) // <-- Resolver written here
mstore(add(offset, 0x44), takerAsset)
// ... more suffix fields ...
}
}The code trusts interactionLength from calldata without validation. By providing a massive value (0xFFFF...FE00 = -512 in two's complement), the offset calculation underflows, causing the suffix to be written in memory.
The attacker constructs a nested order chain with 6 orders. The malicious calldata uses standard ABI-encoded pointers: order offset at 0xE0, signature offset at 0x240, and interaction offset at 0x460.
After the 320-byte order struct, the attacker places 544 bytes of zero-padding (0x260-0x460), then at 0x460 places -512 as uint256 instead of a legitimate interaction length. At 0x480, the fake suffix contains the victim's address as resolver.
When _settleOrder() processes this data:
Normal calculation:
offset = ptr + interactionOffset + interactionLength
= ptr + 0x480 + positive_value
= writes at END of calldata β
Attack calculation:
offset = ptr + interactionOffset + (-512)
= ptr + 0x480 - 0x200
= ptr + 0x280
= writes EARLIER, over zero padding β
The legitimate suffix (with the correct resolver = attacker's contract) gets written to the zero-padding region and is ignored. Meanwhile, the DynamicSuffix library reads from the end of calldata, where the attacker placed their fake suffix.
The fake suffix structure:
bytes memory victimSuffix = abi.encode(
uint256(0), // totalFee (padding)
RESOLVER, // resolver β THE VICTIM ADDRESS!
address(USDC), // takerAsset
uint256(0), // rateBump (padding)
uint256(0), // takingFee (padding)
address(USDC), // token
DRAIN_AMOUNT, // amount to steal
uint256(0x40) // tokensAndAmounts length
);When Settlement processes the finalize interaction:
// Settlement thinks resolver = VICTIM (from fake suffix)
// Calls: VICTIM.resolveOrders(VICTIM, tokensAndAmounts, data)
// VICTIM sees msg.sender = Settlement (trusted!) β transfers fundsEach nested order adds entries to the internal tokensAndAmounts array. The attacker needed sufficient entries so that:
suffixSize = staticFields (160 bytes) + tokensAndAmounts (~320 bytes) + length (32 bytes)
β 512 bytes (0x200)
This makes the -512 offset calculation precisely rewind the write position over the zero-padding.
The Settlement contract uses single-byte flags to control flow:
// From fillOrderInteraction()
if (interaction[20] == 0x00) {
// CONTINUE: Process next nested order
_settleOrder(interaction[21:], ...);
} else if (interaction[20] == 0x01) {
// FINALIZE: Call resolver.resolveOrders()
target.resolveOrders(resolver, tokensAndAmounts, data);
}Orders 1-5 use 0x00 (continue) to build the chain. Order 6 uses 0x01 (finalize) to trigger the drain.
The 1inch Settlement exploit represents a rare class of smart contract vulnerability memory corruption in Yul assembly. Several factors contributed to its success:
-
Deprecated but live code: Fusion V1 was deprecated in mid-2023 but remained deployed for backward compatibility, receiving no security updates.
-
Complex interaction patterns: The nested order mechanism, created attack surface that single-order analysis couldn't reveal.
-
Trusted caller assumption: The victim resolver trusted Settlement as the caller without validating the resolver parameter in the suffix.
The attack was executed across 10 transactions, draining approximately $5M total from the TrustedVolumes resolver. Our PoC reproduces one representative transaction stealing 1M USDC. After on-chain negotiation, most funds were returned, with the attacker keeping a fractional bounty.
Validate that interactionLength does not exceed data.length before using it in offset calculations.
Implement overflow checks on pointer arithmetic in Yul assembly to prevent underflow from negative values.
Resolver contracts should verify the resolver parameter matches their own address, not just trust the Settlement caller.
- Uranium - Arithmetic error in core AMM invariant calculation
- MobiusDAO - Decimal precision error inflates minted amounts
- SIR Trading - Exploiting low-level EVM features (transient storage) for unauthorized access