Skip to main content
This page is a placeholder. All examples on this page are currently AI-generated and are not correct. This documentation will be completed in the future with accurate, tested examples.

Overview

Opcode: 0x1c Introduced: Constantinople (EIP-145) SHR performs logical (unsigned) shift right on a 256-bit value, shifting bits toward the least significant position. Vacated bits (on the left) are filled with zeros. This operation efficiently divides by powers of 2 and extracts high-order bits. Before EIP-145, right shifts required expensive DIV + EXP operations (15-60+ gas). SHR reduces this to 3 gas. Note: SHR is for unsigned values. For signed division, use SAR (0x1d).

Specification

Stack Input:
shift (top) - number of bit positions to shift
value - 256-bit value to shift
Stack Output:
value >> shift (logical, zero-fill)
Gas Cost: 3 (GasFastestStep) Hardfork: Constantinople (EIP-145) or later Shift Behavior:
shift < 256: value >> shift (logical, zero-fill)
shift >= 256: 0 (all bits shifted out)

Behavior

SHR pops two values from the stack:
  1. shift - number of bit positions to shift right (0-255)
  2. value - 256-bit value to be shifted
Result is value shifted right by shift positions, with zeros filling vacated high-order bits (logical shift). If shift >= 256, result is 0. Difference from SAR: SHR always fills with zeros (unsigned), while SAR preserves the sign bit (signed).

Examples

Basic Right Shift

import { shr } from '@tevm/voltaire/evm/bitwise';
import { createFrame } from '@tevm/voltaire/evm/Frame';

// Shift 0xFF00 right by 8 bits (divide by 256)
const frame = createFrame({ stack: [8n, 0xFF00n] });
const err = shr(frame);

console.log(frame.stack[0].toString(16));  // 'ff'

Divide by Power of 2

// Shift right by N = divide by 2^N
// 40 >> 3 = 40 / 8 = 5
const frame = createFrame({ stack: [3n, 40n] });
shr(frame);

console.log(frame.stack[0]);  // 5n

Zero Shift (Identity)

// Shift by 0 positions = identity
const value = 0x123456n;
const frame = createFrame({ stack: [0n, value] });
shr(frame);

console.log(frame.stack[0] === value);  // true

Maximum Shift (Underflow)

// Shift >= 256 results in 0
const value = 0xFFFFFFFFn;
const frame = createFrame({ stack: [256n, value] });
shr(frame);

console.log(frame.stack[0]);  // 0n

Extract High Bytes

// Extract upper 128 bits
const value = 0x123456789ABCDEF0n << 128n | 0xFEDCBA9876543210n;
const frame = createFrame({ stack: [128n, value] });
shr(frame);

console.log(frame.stack[0].toString(16));  // '123456789abcdef0'

Logical vs Arithmetic (Negative Number)

// SHR treats as unsigned (zero-fill)
const negativeValue = 1n << 255n;  // MSB set (negative in two's complement)
const frame = createFrame({ stack: [1n, negativeValue] });
shr(frame);

// Result: MSB is now 0 (zero-filled, becomes positive)
const expected = 1n << 254n;  // 0x4000...0000
console.log(frame.stack[0] === expected);  // true

Gas Cost

Cost: 3 gas (GasFastestStep) Pre-EIP-145 equivalent:
// Before Constantinople: expensive!
result = value / (2 ** shift)  // DIV (5 gas) + EXP (10 + 50/byte gas)
// Total: 15-1615 gas

// After Constantinople: cheap!
assembly { result := shr(shift, value) }  // 3 gas
Savings: 12-1612 gas per shift operation.

Edge Cases

Zero Value

// Shifting zero always yields zero
const frame = createFrame({ stack: [100n, 0n] });
shr(frame);

console.log(frame.stack[0]);  // 0n

Zero Shift

// No shift = identity
const value = 0xDEADBEEFn;
const frame = createFrame({ stack: [0n, value] });
shr(frame);

console.log(frame.stack[0] === value);  // true

Shift by 1 (Halve)

// Shift right by 1 = divide by 2 (truncate)
const value = 43n;
const frame = createFrame({ stack: [1n, value] });
shr(frame);

console.log(frame.stack[0]);  // 21n (truncated)

Shift by 255 (Extract MSB)

// Shift to LSB position
const value = 1n << 255n;  // MSB set
const frame = createFrame({ stack: [255n, value] });
shr(frame);

console.log(frame.stack[0]);  // 1n

Shift by 256+ (Complete Underflow)

// Any shift >= 256 yields 0
for (const shift of [256n, 257n, 1000n, (1n << 200n)]) {
  const frame = createFrame({ stack: [shift, 0xFFFFn] });
  shr(frame);
  console.log(frame.stack[0]);  // 0n for all
}

MSB Set (Unsigned Interpretation)

// SHR treats MSB as regular bit (unsigned)
const value = (1n << 255n) | 0xFFn;  // MSB set + lower bits
const frame = createFrame({ stack: [8n, value] });
shr(frame);

// Result: MSB shifted right, zero-filled
const expected = (1n << 247n) | 0n;
console.log(frame.stack[0] === expected);  // true (MSB now at bit 247)

Truncation

// Low bits are discarded
const value = 0xFFFFn;  // Binary: ...1111111111111111
const frame = createFrame({ stack: [4n, value] });
shr(frame);

console.log(frame.stack[0].toString(16));  // 'fff' (lower 4 bits discarded)

Hardfork Check (Pre-Constantinople)

// SHR is invalid before Constantinople
const frame = createFrame({
  stack: [8n, 0xFF00n],
  hardfork: 'byzantium'  // Before Constantinople
});
const err = shr(frame);

console.log(err);  // { type: "InvalidOpcode" }

Stack Underflow

// Insufficient stack items
const frame = createFrame({ stack: [8n] });
const err = shr(frame);

console.log(err);  // { type: "StackUnderflow" }

Out of Gas

// Insufficient gas
const frame = createFrame({ stack: [8n, 0xFF00n], gasRemaining: 2n });
const err = shr(frame);

console.log(err);  // { type: "OutOfGas" }
console.log(frame.gasRemaining);  // 0n

Common Usage

Divide by Power of 2

// Efficient division by 256
assembly {
    result := shr(8, value)  // 3 gas vs DIV (5 gas)
}

Unpack Data Fields

// Extract timestamp from packed data (upper 40 bits)
function extractTimestamp(uint256 packed) pure returns (uint40) {
    return uint40(packed >> 216);  // Shift right 216 bits
    // Or in assembly:
    // assembly { result := shr(216, packed) }
}

Extract High Bits

// Get upper N bits
function getHighBits(uint256 value, uint256 numBits) pure returns (uint256) {
    require(numBits <= 256, "invalid bit count");
    uint256 shift = 256 - numBits;
    return value >> shift;
    // assembly { result := shr(shift, value) }
}

Check MSB (Sign Bit)

// Check if MSB is set (would be negative if signed)
function isMsbSet(uint256 value) pure returns (bool) {
    return (value >> 255) == 1;
    // assembly {
    //     result := eq(shr(255, value), 1)
    // }
}

Bitmap Operations

// Check if bit at position is set
function isBitSet(uint256 bitmap, uint256 bitPos) pure returns (bool) {
    require(bitPos < 256, "bit position out of range");
    return ((bitmap >> bitPos) & 1) == 1;
}

Extract Nibble (4 bits)

// Extract nibble at position (0 = lowest nibble)
function getNibble(uint256 value, uint256 nibblePos) pure returns (uint8) {
    require(nibblePos < 64, "nibble position out of range");
    return uint8((value >> (nibblePos * 4)) & 0xF);
}

Implementation

/**
 * SHR opcode (0x1c) - Logical shift right operation (EIP-145)
 */
export function shr(frame: FrameType): EvmError | null {
  // Check hardfork (Constantinople or later)
  if (frame.hardfork.isBefore('constantinople')) {
    return { type: "InvalidOpcode" };
  }

  // Consume gas (GasFastestStep = 3)
  frame.gasRemaining -= 3n;
  if (frame.gasRemaining < 0n) {
    frame.gasRemaining = 0n;
    return { type: "OutOfGas" };
  }

  // Pop operands
  if (frame.stack.length < 2) return { type: "StackUnderflow" };
  const shift = frame.stack.pop();
  const value = frame.stack.pop();

  // Compute logical shift right (zero-fill)
  const result = shift >= 256n
    ? 0n
    : value >> shift;

  // Push result
  if (frame.stack.length >= 1024) return { type: "StackOverflow" };
  frame.stack.push(result);

  // Increment PC
  frame.pc += 1;

  return null;
}

Testing

Test Coverage

import { describe, it, expect } from 'vitest';
import { shr } from './shr.js';

describe('SHR (0x1c)', () => {
  it('shifts right by 8 bits', () => {
    const frame = createFrame({ stack: [8n, 0xFF00n] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(0xFFn);
  });

  it('divides by power of 2', () => {
    const frame = createFrame({ stack: [3n, 40n] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(5n);  // 40 / 2^3
  });

  it('handles zero shift (identity)', () => {
    const value = 0x123456n;
    const frame = createFrame({ stack: [0n, value] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(value);
  });

  it('returns 0 for shift >= 256', () => {
    const frame = createFrame({ stack: [256n, 0xFFFFFFFFn] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(0n);
  });

  it('extracts MSB', () => {
    const value = 1n << 255n;
    const frame = createFrame({ stack: [255n, value] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(1n);
  });

  it('zero-fills on negative values (logical shift)', () => {
    const value = 1n << 255n;  // MSB set (negative if signed)
    const frame = createFrame({ stack: [1n, value] });
    expect(shr(frame)).toBeNull();

    // Logical shift: zero-filled
    expect(frame.stack[0]).toBe(1n << 254n);
  });

  it('truncates low bits', () => {
    const value = 0xFFFFn;
    const frame = createFrame({ stack: [4n, value] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(0xFFFn);
  });

  it('extracts high bits', () => {
    const value = (0x123456n << 128n) | 0xABCDEFn;
    const frame = createFrame({ stack: [128n, value] });
    expect(shr(frame)).toBeNull();
    expect(frame.stack[0]).toBe(0x123456n);
  });

  it('returns InvalidOpcode before Constantinople', () => {
    const frame = createFrame({
      stack: [8n, 0xFF00n],
      hardfork: 'byzantium'
    });
    expect(shr(frame)).toEqual({ type: 'InvalidOpcode' });
  });

  it('returns StackUnderflow with insufficient stack', () => {
    const frame = createFrame({ stack: [8n] });
    expect(shr(frame)).toEqual({ type: 'StackUnderflow' });
  });

  it('returns OutOfGas when insufficient gas', () => {
    const frame = createFrame({ stack: [8n, 0xFF00n], gasRemaining: 2n });
    expect(shr(frame)).toEqual({ type: 'OutOfGas' });
  });
});

Edge Cases Tested

  • Basic shift operations
  • Division by powers of 2
  • Zero shift (identity)
  • Shift >= 256 (underflow to zero)
  • MSB extraction
  • Logical shift (zero-fill) vs arithmetic
  • Bit truncation
  • High bit extraction
  • Zero value shifts
  • Hardfork compatibility
  • Stack underflow
  • Out of gas

Security

Signed vs Unsigned Confusion

// WRONG: Using SHR for signed division
function divideSignedBy2(int256 value) pure returns (int256) {
    return int256(uint256(value) >> 1);  // Incorrect for negative values!
}

// CORRECT: Use SAR for signed division
function divideSignedBy2(int256 value) pure returns (int256) {
    assembly {
        value := sar(1, value)  // Sign-preserving shift
    }
    return value;
}

// Example:
// -8 in two's complement: 0xFFFF...FFF8
// SHR(1, -8) = 0x7FFF...FFFC (large positive, wrong!)
// SAR(1, -8) = 0xFFFF...FFFC (-4, correct)

Truncation Assumptions

// RISKY: Assuming remainder is zero
function divideBy256(uint256 value) pure returns (uint256) {
    uint256 result = value >> 8;
    // Lost information: value % 256 is discarded
    return result;
}

// SAFER: Explicit handling
function divideBy256(uint256 value) pure returns (uint256 quotient, uint256 remainder) {
    quotient = value >> 8;
    remainder = value & 0xFF;  // Lower 8 bits
}

Unchecked Shift Amount

// DANGEROUS: User-controlled shift without validation
function shiftRight(uint256 value, uint256 shift) pure returns (uint256) {
    return value >> shift;  // shift >= 256 → 0 (may not be intended)
}

// SAFER: Validate shift range
function shiftRight(uint256 value, uint256 shift) pure returns (uint256) {
    require(shift < 256, "shift underflow");
    return value >> shift;
}

Incorrect Division

// Use SHR only for powers of 2
function divide(uint256 a, uint256 b) pure returns (uint256) {
    // WRONG: Only works if b is a power of 2
    return a >> b;  // Treats b as exponent, not divisor!
}

// CORRECT: Use DIV for general division
function divide(uint256 a, uint256 b) pure returns (uint256) {
    return a / b;
}

Endianness in Byte Extraction

// Using SHR to extract bytes (alternative to BYTE opcode)
function extractByte(bytes32 data, uint256 byteIndex) pure returns (uint8) {
    require(byteIndex < 32, "index out of range");

    // CAREFUL: Byte ordering matters
    // BYTE(i, x): byte 0 = MSB
    // SHR: shift from MSB side

    uint256 shift = (31 - byteIndex) * 8;  // Convert to bit shift
    return uint8((uint256(data) >> shift) & 0xFF);
}

Benchmarks

SHR is one of the fastest EVM operations: Execution time (relative):
  • SHR: 1.0x (baseline, fastest tier)
  • SHL/SAR: 1.0x (same tier)
  • DIV: 2.5x
Gas comparison (right shift by 8):
MethodGasNotes
SHR (Constantinople+)3Native shift
DIV (pre-EIP-145)5value / 256
EXP + DIV (variable)65+value / 2^shift
Gas savings: 2-1612 gas per shift vs pre-EIP-145 methods.

References