Summary
_deriveReceivedItemsHash() in both ImmutableSignedZoneV2 and ImmutableSignedZoneV3 uses Math.mulDiv() (floor division) to reconstruct the original consideration amount from a partial fill. Because Seaport also floors when scaling down, two consecutive floor operations produce an irreversible rounding error — causing Substandard6Violation reverts on 90%+ of partial fill ratios.
Affected Files
contracts/trading/seaport/zones/immutable-signed-zone/v2/ImmutableSignedZoneV2.sol (L565-585)
contracts/trading/seaport16/zones/immutable-signed-zone/v3/ImmutableSignedZoneV3.sol (same function)
- Deployed at:
0x1004f9615E79462c711Ff05a386BdbA91a7628C3 (Immutable zkEVM)
Root Cause
For a partial fill of 33/100 with original consideration = 10:
- Seaport floors DOWN:
floor(10 × 33 / 100) = 3
- Zone floors DOWN again:
Math.mulDiv(3, 100, 33) = 9 (should be 10)
- Strict equality:
hash(9) ≠ hash(10) → Substandard6Violation
Reproduction
forge init poc && cd poc
# Save test file below as test/Sub6.t.sol
forge test -vv --fork-url https://rpc.immutable.com
// test/Sub6.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
contract Sub6Test is Test {
function test_doublFloor() public pure {
uint256 originalOffer = 100;
uint256 originalCons = 10;
uint256 failCount = 0;
for (uint256 f = 1; f < 100; f++) {
uint256 actual = (originalCons * f) / originalOffer;
uint256 reconstructed = (actual * originalOffer) / f;
if (reconstructed != originalCons) failCount++;
}
assertEq(failCount, 90); // 90/99 fills produce wrong result
}
}
Impact
- 90/99 (90.9%) of partial fill ratios revert for a 100/10 order
- 999/999 (100%) revert for a 1000/7 order (realistic game asset scenario)
- Substandard 6 is designed for "best-efforts partial fulfilment scenarios" but fails for nearly all of them
Suggested Fix
Replace floor division with ceiling:
- Math.mulDiv(receivedItems[i].amount, scalingFactorNumerator, scalingFactorDenominator)
+ Math.mulDiv(receivedItems[i].amount, scalingFactorNumerator, scalingFactorDenominator, Math.Rounding.Ceil)
Summary
_deriveReceivedItemsHash()in bothImmutableSignedZoneV2andImmutableSignedZoneV3usesMath.mulDiv()(floor division) to reconstruct the original consideration amount from a partial fill. Because Seaport also floors when scaling down, two consecutive floor operations produce an irreversible rounding error — causingSubstandard6Violationreverts on 90%+ of partial fill ratios.Affected Files
contracts/trading/seaport/zones/immutable-signed-zone/v2/ImmutableSignedZoneV2.sol(L565-585)contracts/trading/seaport16/zones/immutable-signed-zone/v3/ImmutableSignedZoneV3.sol(same function)0x1004f9615E79462c711Ff05a386BdbA91a7628C3(Immutable zkEVM)Root Cause
For a partial fill of 33/100 with original consideration = 10:
floor(10 × 33 / 100) = 3Math.mulDiv(3, 100, 33) = 9(should be 10)hash(9) ≠ hash(10)→Substandard6ViolationReproduction
Impact
Suggested Fix
Replace floor division with ceiling: