Opcode Summary

PropertyValue
Opcode0x1D
MnemonicSAR
Gas3
Stack Inputshift, value
Stack Outputvalue >> shift (arithmetic, sign-preserving)
BehaviorArithmetic right shift. Shifts value right by shift bits, filling vacated high bits with copies of the sign bit (bit 255). If shift >= 256, the result is 0 if value is non-negative (bit 255 = 0), or MAX_UINT256 (-1) if value is negative (bit 255 = 1).

Threat Surface

SAR (Shift Arithmetic Right) is the EVM’s only sign-preserving shift operation, introduced in Constantinople (EIP-145) alongside SHL and SHR. Its defining property is sign extension: when shifting right, vacated high bits are filled with copies of the sign bit (bit 255), not zeros. This preserves the sign of two’s complement values:

  • SAR(1, -100)-50 (high bits filled with 1s, sign preserved)
  • SHR(1, -100) → huge positive number (high bits filled with 0s, sign destroyed)

SAR’s threat surface is dominated by three issues:

  1. Using SAR on unsigned values: SAR treats all values as signed. If an unsigned value happens to have bit 255 set (i.e., >= 2^255), SAR treats it as negative and fills high bits with 1s, producing a result near MAX_UINT256 instead of the expected small positive number.

  2. Rounding toward negative infinity: SAR rounds differently from SDIV. SAR(1, -3) = -2 (floor toward negative infinity), while SDIV(-3, 2) = -1 (truncation toward zero). This difference has caused compiler bugs and can lead to exploitable discrepancies in financial calculations.

  3. Sign extension on shift >= 256: Unlike SHR (which always returns 0 for shift >= 256), SAR returns 0 for non-negative values but MAX_UINT256 (-1 in two’s complement) for negative values. This asymmetry can be exploited when the shift amount is computed and exceeds 255.

The historical context is important: before EIP-145 (Constantinople, February 2019), there was no SAR instruction. Solidity emulated signed right shift using SDIV, which has different rounding behavior. The transition from SDIV-emulated shifts to native SAR was a breaking change (Solidity PR #4084) that affected contracts relying on the SDIV rounding direction.


Smart Contract Threats

T1: SAR on Unsigned Values — False Negative Interpretation (Critical)

The most dangerous SAR misuse: applying it to values that are logically unsigned but numerically have bit 255 set. In uint256, values >= 2^255 are perfectly valid large positive numbers. But SAR interprets bit 255 as the sign bit, treating these values as negative:

assembly {
    let x := 0x8000000000000000000000000000000000000000000000000000000000000001
    // As uint256: ~5.79 * 10^76 (large but valid)
    // SAR interpretation: negative (bit 255 = 1)
    
    let result := sar(1, x)
    // Returns: 0xC000000000000000000000000000000000000000000000000000000000000000
    // High bits filled with 1s (sign extension)!
    // As uint256: ~8.68 * 10^76 (LARGER than input -- completely wrong for unsigned division)
    
    // SHR would correctly return:
    // 0x4000000000000000000000000000000000000000000000000000000000000000
    // ~2.89 * 10^76 (half the input)
}

This can occur in DeFi when token amounts, prices, or hash values exceed 2^255. While uncommon for single-token balances, it easily happens in intermediate calculations (e.g., price * amount producing a value > 2^255).

T2: Rounding Direction Differs from SDIV and SHR (High)

The three right-shift-equivalent operations in the EVM round differently:

Operation(-3) shifted right by 1Rounding Direction
SAR(1, -3)-2Toward negative infinity (floor)
SDIV(-3, 2)-1Toward zero (truncation)
SHR(1, -3 as uint256)Huge positiveWrong for signed

The SAR vs SDIV difference means that int256(-3) >> 1 and int256(-3) / 2 produce different results. This was the root cause of Solidity Issue #3847: before Constantinople, the compiler used SDIV for signed right shifts, producing -1 instead of the mathematically correct -2.

In financial contexts, this rounding difference matters:

  • Interest calculations: Negative interest (penalty) that rounds toward zero (SDIV) is smaller than one that rounds toward negative infinity (SAR)
  • Price adjustments: Downward price corrections rounded differently produce systematically different valuations
  • Reward distribution: Negative adjustments rounded toward zero give users more than they’re owed

T3: Sign Extension on Shift >= 256 (High)

SAR has asymmetric behavior for large shift amounts:

InputSAR(256, input)Explanation
Any non-negative (bit 255 = 0)0Same as SHR
Any negative (bit 255 = 1)MAX_UINT256 (-1)Sign extension fills all 256 bits with 1

If the shift amount is user-controlled or computed from external data and can reach 256+, a negative input produces MAX_UINT256 instead of 0. When interpreted as uint256, this is the maximum possible value:

assembly {
    // Shift amount from external source
    let result := sar(300, 0x8000000000000000000000000000000000000000000000000000000000000001)
    // result = MAX_UINT256 (all bits set)
    // As uint256: ~1.16 * 10^77
}

T4: Pre-Constantinople Emulation Correctness (Medium)

Contracts compiled for pre-Constantinople targets (or using older Solidity versions that emulate SAR) may have incorrect signed shift behavior. The emulation using SDIV rounds toward zero instead of toward negative infinity. Contracts relying on the exact rounding behavior of SAR but compiled with SDIV emulation produce different results for negative odd values.

Solidity fixed this in PR #4084, implementing proper SAR emulation on pre-Constantinople targets using SHR + SIGNEXTEND. However, contracts compiled with older compilers retain the buggy SDIV-based emulation.

T5: Mixing SAR with Unsigned Comparisons (Medium)

After a SAR operation on a negative value, the result is still negative (in two’s complement). If this result is then compared using unsigned operations (LT, GT) instead of signed operations (SLT, SGT), the negative value appears as a huge positive number:

assembly {
    let shifted := sar(1, sub(0, 100))  // SAR(1, -100) = -50
    // shifted = 0xFFFF...FFCE (-50 in two's complement)
    
    let isSmall := lt(shifted, 100)     // Unsigned: 0xFFFF...FFCE < 100? No!
    // isSmall = 0 (false) -- wrong!
    
    let isSmallSigned := slt(shifted, 100)  // Signed: -50 < 100? Yes!
    // isSmallSigned = 1 (true) -- correct
}

Protocol-Level Threats

P1: No DoS Vector (Low)

SAR costs a fixed 3 gas regardless of operand values.

P2: Consensus Safety — Rounding Specification (Medium)

SAR’s rounding behavior (toward negative infinity) is precisely specified in EIP-145. However, alternative EVM implementations that derive SAR from SDIV (which rounds toward zero) will produce different results for negative odd values. This is a potential consensus divergence vector for new EVM implementations.

The EIP-145 specification is explicit: SAR performs arithmetic right shift with sign extension, equivalent to floor(value / 2^shift) where floor rounds toward negative infinity. Implementations that use truncation toward zero are non-compliant.

P3: No State Impact (None)

SAR modifies only the stack.


Edge Cases

Edge CaseBehaviorSecurity Implication
SAR(0, x)Returns xNo shift; identity
SAR(1, -1)Returns -1 (MAX_UINT256)-1 >> any amount stays -1 (all bits are 1)
SAR(1, -2)Returns -1 (MAX_UINT256)-2 / 2 rounds to -1 (toward negative infinity)
SAR(1, -3)Returns -2Floor division: -3/2 = -2, not -1
SAR(255, -1)Returns -1 (MAX_UINT256)Sign extends to fill all 256 bits
SAR(256, positive)Returns 0Same as SHR for non-negative
SAR(256, negative)Returns MAX_UINT256 (-1)Sign extension: all bits become 1
SAR(MAX_UINT256, 1)Returns 0Positive value, massive shift
SAR(MAX_UINT256, -1)Returns MAX_UINT256 (-1)Negative, massive shift = all sign bits
SAR(1, 0)Returns 0Zero is non-negative; high bits filled with 0
SAR(n, uint > 2^255)Sign-extends as negativeUnsigned values with MSB set are treated as negative
SAR(1, MIN_INT256)Returns 0xC000...0000 (-2^254)Correct: -2^255 / 2 = -2^254

Real-World Exploits

Exploit 1: Solidity SDIV/SAR Rounding Discrepancy (Issue #3847 / PR #4084, 2018)

Root cause: Solidity used SDIV to emulate signed right shift, but SDIV rounds toward zero while SAR rounds toward negative infinity. This produced incorrect results for negative odd values.

Details: Before the Constantinople hard fork introduced native SAR, Solidity compiled int256 x >> n as x / 2^n using SDIV. The rounding difference:

  • SAR(1, -3) = -2 (floor toward negative infinity: -3/2 = -1.5, floor = -2)
  • SDIV(-3, 2) = -1 (truncation toward zero: -3/2 = -1.5, truncate = -1)

This was a documented breaking change when Solidity switched to native SAR (PR #4084). Contracts that depended on the SDIV rounding behavior (truncation toward zero) would produce different results after the compiler update.

SAR’s role: SAR’s rounding toward negative infinity is the mathematically correct behavior for arithmetic right shift. The pre-SAR emulation was wrong, and fixing it changed program semantics.

Impact: Subtle rounding changes in signed arithmetic for all contracts recompiled with the fix. Particularly relevant for financial calculations involving negative interest rates, price deltas, or penalty computations.

References:


Exploit 2: Disallowing Shift by Signed Types (Solidity Issue #8822)

Root cause: Allowing signed types as the shift amount could produce negative shift values, which have undefined or confusing semantics in the EVM.

Details: Solidity Issue #8822 identified that using signed integers as shift amounts (e.g., uint256 result = x >> int256(-1)) could lead to confusion and bugs. A negative shift amount, when interpreted as uint256, becomes an astronomically large number (>= 256), causing SAR to return either 0 or MAX_UINT256 depending on the sign of the shifted value.

Solidity resolved this by disallowing signed types as the shift amount at the compiler level. The shift amount must always be unsigned, preventing accidental interpretation of negative values as huge shifts.

SAR’s role: SAR’s asymmetric behavior for shift >= 256 (returns 0 for positive, MAX_UINT256 for negative) makes negative shift amounts especially dangerous — the result depends not just on the magnitude of the shift but also on the sign of the value being shifted.

References:


Exploit 3: Rounding-Direction Exploitation in DeFi Negative Adjustments (Recurring Pattern)

Root cause: Financial calculations using SAR-based division on negative values round differently from SDIV-based division, creating exploitable discrepancies.

Details: DeFi protocols that handle negative adjustments (slashing penalties, negative rebasing, fee deductions) must carefully choose their rounding direction. SAR rounds toward negative infinity (making penalties larger in absolute terms), while SDIV rounds toward zero (making penalties smaller).

The OWASP Smart Contract Security Top 10 (2026) identifies arithmetic/rounding errors (SC07) as a critical vulnerability class, noting that rounding inconsistencies between deposits and withdrawals have been exploited in multiple protocols. When protocols use SAR for some calculations and SDIV for others on the same negative values, the inconsistency creates extractable value.

For example, a lending protocol that calculates interest using SAR-rounding but applies rebates using SDIV-rounding creates a systematic discrepancy: borrowers pay more interest (SAR rounds the negative rate floor-ward, making the absolute deduction larger) but receive less in rebates (SDIV rounds toward zero, making the rebate smaller).

SAR’s role: SAR’s rounding direction is mathematically correct but differs from what many developers expect (truncation toward zero). The mismatch between expectation and behavior creates bugs when SAR and SDIV are mixed.

References:


Attack Scenarios

Scenario A: SAR on Unsigned Value > 2^255

contract VulnerablePool {
    function computeHalfPrice(uint256 sqrtPriceX96) external pure returns (uint256) {
        uint256 halfPrice;
        assembly {
            // Bug: sar instead of shr on unsigned value
            halfPrice := sar(1, sqrtPriceX96)
            // If sqrtPriceX96 >= 2^255 (possible with very high prices):
            // SAR treats it as negative, sign-extends, produces ~MAX_UINT256/2
            // instead of sqrtPriceX96/2
        }
        return halfPrice;
    }
}

Attack: Manipulate pool price above 2^255 (via flash loan). SAR produces an enormous number instead of half the price. Use the inflated value to extract liquidity.

Scenario B: Rounding Direction Exploitation

contract LendingPool {
    int256 public negativeInterestRate = -3;  // -3 basis points per period
    
    function computeAdjustment(uint256 principal) external view returns (int256) {
        int256 signedPrincipal = int256(principal);
        int256 adjustment;
        assembly {
            // SAR: -3 >> 1 = -2 (floor division, larger absolute penalty)
            // If SDIV were used: -3 / 2 = -1 (truncation, smaller penalty)
            adjustment := sar(1, mul(signedPrincipal, sload(negativeInterestRate.slot)))
        }
        return adjustment;
    }
}

Attack: If the protocol inconsistently uses SAR in some paths and SDIV in others, arbitrage the rounding difference across the two paths.

Scenario C: Sign Extension on Large Shift

contract VulnerableScaler {
    function scaleDown(int256 value, uint256 decimals) external pure returns (int256) {
        int256 result;
        assembly {
            // Bug: if decimals > 77 (since 4*78 > 256), shift >= 256
            // For negative value: SAR returns MAX_UINT256 (-1)
            // For positive value: SAR returns 0
            result := sar(mul(decimals, 4), value)
        }
        return result;
        // With decimals = 100 and value = -5:
        // shift = 400 >= 256, result = MAX_UINT256 = -1 (not 0!)
    }
}

Scenario D: Mixing SAR Result with Unsigned Comparison

contract VulnerableCheck {
    function isSmallAdjustment(int256 adjustment) external pure returns (bool) {
        uint256 absAdj;
        assembly {
            // Compute absolute value of adjustment
            let mask := sar(255, adjustment)  // All 1s if negative, all 0s if positive
            absAdj := xor(add(adjustment, mask), mask)  // XOR trick for abs
        }
        // Bug: if the SAR + XOR + ADD chain has an error,
        // absAdj could be wrong
        return absAdj < 1000;
    }
}

Mitigations

ThreatMitigationImplementation
T1: SAR on unsigned valuesNever use SAR on uint256; use SHRCode review: flag sar() in assembly that operates on unsigned variables
T1: Type enforcementUse Solidity’s >> operator (automatically selects SAR for int, SHR for uint)Avoid assembly for shift operations when possible
T2: Rounding directionDocument intended rounding; use consistent division methodChoose SAR or SDIV, never mix for the same calculation
T2: Financial roundingRound against the user (pro-protocol)For penalties: SAR (larger absolute deduction). For rebates: SDIV (smaller rebate)
T3: Sign extension overflowValidate shift amount < 256require(shift < 256) or handle >= 256 case explicitly
T3: Asymmetric resultTest with both positive and negative values at shift boundariesFuzz: (positive, 256), (negative, 256), (positive, 255), (negative, 255)
T4: Pre-Constantinople compatRecompile with modern Solidity; verify behavior matches SAR semanticsTest signed right shift with negative odd values: -3, -5, -7
T5: Unsigned comparisonUse SLT/SGT after SAR, not LT/GTslt(sarResult, threshold) instead of lt(sarResult, threshold)

Key Decision: SAR vs SHR vs SDIV

When to use…OperationRoundingSign handling
SHRUnsigned division by 2^nToward zero (floor)Unsigned only; destroys sign
SARSigned division by 2^nToward negative infinityPreserves sign via extension
SDIVSigned division by any valueToward zero (truncation)Preserves sign via result sign
DIVUnsigned division by any valueToward zero (floor)Unsigned only

Use SHR for uint256, SAR for int256, and never mix them. Let the Solidity compiler choose the right instruction via the >> operator on the appropriate type.


Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalMediumSAR on large unsigned values in DeFi
T2Smart ContractHighMediumSolidity SDIV/SAR rounding discrepancy (#3847)
T3Smart ContractHighLowSign extension on large shift amounts
T4Smart ContractMediumLowPre-Constantinople emulation bugs
T5Smart ContractMediumMediumUnsigned comparison after SAR
P1ProtocolLowN/A
P2ProtocolMediumLowEVM implementation rounding disagreements

OpcodeRelationship
SHR (0x1C)Logical right shift (unsigned). Fills high bits with 0, not sign bit. Use SHR for uint256, SAR for int256
SHL (0x1B)Left shift; no signed variant exists. SHL is used for both signed and unsigned left shifts
SDIV (0x05)Signed division; rounds toward zero (truncation). Different rounding from SAR for negative odd values
DIV (0x04)Unsigned division; SAR on values with bit 255 = 0 is equivalent to DIV by power of 2
SIGNEXTEND (0x0B)Sign-extends a sub-256-bit value. Used in pre-Constantinople SAR emulation and for correcting dirty sign bits
SLT (0x12)Signed less-than comparison. Must use SLT (not LT) after SAR to correctly compare signed results
SGT (0x13)Signed greater-than comparison. Must use SGT (not GT) after SAR for correct comparison
ISZERO (0x15)Used to check if SAR result is zero; works correctly regardless of sign interpretation