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: 0x16
Introduced: Frontier (EVM genesis)
AND performs bitwise AND on two 256-bit unsigned integers. Each bit in the result is 1 only if the corresponding bits in both operands are 1. This operation is fundamental for bit masking, extracting specific bit ranges, and checking flags.
Primary uses: extracting addresses from uint256, applying bit masks, checking flag combinations.
Specification
Stack Input:
Stack Output:
Gas Cost: 3 (GasFastestStep)
Truth Table (per bit):
a | b | a & b
--|---|------
0 | 0 | 0
0 | 1 | 0
1 | 0 | 0
1 | 1 | 1
Behavior
AND pops two values from the stack, performs bitwise AND on each corresponding bit pair, and pushes the result. The operation is:
- Commutative: a & b = b & a
- Associative: (a & b) & c = a & (b & c)
- Identity element: a & MAX_UINT256 = a
- Null element: a & 0 = 0
Examples
Basic Masking
import { and } from '@tevm/voltaire/evm/bitwise';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// Extract lower 8 bits (least significant byte)
const value = 0x123456789ABCDEFn;
const mask = 0xFFn;
const frame = createFrame({ stack: [value, mask] });
const err = and(frame);
console.log(frame.stack); // [0xEFn]
// Extract 160-bit address from packed uint256
const packed = 0x000000000000000000000000dEaDbEeFcAfE1234567890abcdef1234567890ABn;
const addressMask = (1n << 160n) - 1n; // 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
const frame = createFrame({ stack: [packed, addressMask] });
and(frame);
console.log(frame.stack[0].toString(16));
// 'deadbeefcafe1234567890abcdef1234567890ab'
Check Multiple Flags
// Check if both FLAG_A and FLAG_B are set
const FLAGS_AB = (1n << 0n) | (1n << 1n); // 0b11
const value = 0b1011n; // Has flags 0, 1, 3
const frame = createFrame({ stack: [value, FLAGS_AB] });
and(frame);
const hasBoth = frame.stack[0] === FLAGS_AB;
console.log(hasBoth); // true (bits 0 and 1 are both set)
Isolate Specific Bytes
// Extract bytes 12-15 (middle 4 bytes of address-like value)
const value = 0x1122334455667788990011223344556677889900n;
const mask = 0xFFFFFFFF00000000000000000000000000000000n;
const frame = createFrame({ stack: [value, mask] });
and(frame);
const extracted = frame.stack[0] >> 160n;
console.log(extracted.toString(16)); // '11223344'
Commutative Property
// a & b = b & a
const a = 0xAAAAAAAAn;
const b = 0x55555555n;
const frame1 = createFrame({ stack: [a, b] });
and(frame1);
const frame2 = createFrame({ stack: [b, a] });
and(frame2);
console.log(frame1.stack[0] === frame2.stack[0]); // true (both = 0)
Gas Cost
Cost: 3 gas (GasFastestStep)
AND shares the lowest gas tier with:
- OR (0x17), XOR (0x18), NOT (0x19)
- BYTE (0x1a)
- SHL (0x1b), SHR (0x1c), SAR (0x1d)
- ADD (0x01), SUB (0x03)
- Comparison operations (LT, GT, EQ, etc.)
Edge Cases
Identity Element
// AND with all ones
const MAX = (1n << 256n) - 1n;
const value = 0x123456n;
const frame = createFrame({ stack: [value, MAX] });
and(frame);
console.log(frame.stack[0] === value); // true (identity)
Null Element
// AND with zero
const value = 0x123456n;
const frame = createFrame({ stack: [value, 0n] });
and(frame);
console.log(frame.stack[0]); // 0n
Maximum Values
// MAX & MAX = MAX
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX, MAX] });
and(frame);
console.log(frame.stack[0] === MAX); // true
Alternating Bits
// Alternating bit patterns
const pattern1 = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAn;
const pattern2 = 0x5555555555555555555555555555555555555555555555555555555555555555n;
const frame = createFrame({ stack: [pattern1, pattern2] });
and(frame);
console.log(frame.stack[0]); // 0n (no overlapping bits)
Stack Underflow
// Insufficient stack items
const frame = createFrame({ stack: [0x123n] });
const err = and(frame);
console.log(err); // { type: "StackUnderflow" }
Out of Gas
// Insufficient gas
const frame = createFrame({ stack: [0x123n, 0x456n], gasRemaining: 2n });
const err = and(frame);
console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n
Common Usage
// Storage packs address (160 bits) + flags (96 bits)
assembly {
let packed := sload(slot)
let addr := and(packed, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
let flags := shr(160, packed)
}
Check Permission Flags
// Check if user has both READ and WRITE permissions
uint256 constant READ = 1 << 0;
uint256 constant WRITE = 1 << 1;
uint256 constant EXECUTE = 1 << 2;
function hasReadWrite(uint256 permissions) pure returns (bool) {
return (permissions & (READ | WRITE)) == (READ | WRITE);
}
Align to Boundary
// Align pointer to 32-byte boundary (clear lower 5 bits)
function alignTo32(uint256 ptr) pure returns (uint256) {
return ptr & ~uint256(0x1F); // Clear bits 0-4
}
// Extract specific nibble from bytes32
function getNibble(bytes32 data, uint256 index) pure returns (uint8) {
require(index < 64, "index out of range");
uint256 shift = (63 - index) * 4;
return uint8((uint256(data) >> shift) & 0xF);
}
// Extract RGB channels from packed uint24 (0xRRGGBB)
function unpackRGB(uint24 color) pure returns (uint8 r, uint8 g, uint8 b) {
r = uint8((color >> 16) & 0xFF);
g = uint8((color >> 8) & 0xFF);
b = uint8(color & 0xFF);
}
Implementation
/**
* AND opcode (0x16) - Bitwise AND operation
*/
export function op_and(frame: FrameType): EvmError | null {
// 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 a = frame.stack.pop();
const b = frame.stack.pop();
// Compute bitwise AND
const result = a & b;
// 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 { op_and } from './and.js';
describe('AND (0x16)', () => {
it('performs basic AND', () => {
const frame = createFrame({ stack: [0b1100n, 0b1010n] });
expect(op_and(frame)).toBeNull();
expect(frame.stack[0]).toBe(0b1000n);
});
it('extracts address mask', () => {
const packed = 0x000000000000000000000000deadbeefn;
const mask = (1n << 160n) - 1n;
const frame = createFrame({ stack: [packed, mask] });
expect(op_and(frame)).toBeNull();
expect(frame.stack[0]).toBe(0xdeadbeefn);
});
it('handles identity (AND with all ones)', () => {
const MAX = (1n << 256n) - 1n;
const value = 0x123456n;
const frame = createFrame({ stack: [value, MAX] });
expect(op_and(frame)).toBeNull();
expect(frame.stack[0]).toBe(value);
});
it('handles null element (AND with zero)', () => {
const frame = createFrame({ stack: [0x123456n, 0n] });
expect(op_and(frame)).toBeNull();
expect(frame.stack[0]).toBe(0n);
});
it('is commutative', () => {
const a = 0xAAAAn;
const b = 0x5555n;
const frame1 = createFrame({ stack: [a, b] });
const frame2 = createFrame({ stack: [b, a] });
op_and(frame1);
op_and(frame2);
expect(frame1.stack[0]).toBe(frame2.stack[0]);
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame({ stack: [0x123n] });
expect(op_and(frame)).toEqual({ type: 'StackUnderflow' });
});
it('returns OutOfGas when insufficient gas', () => {
const frame = createFrame({ stack: [0x123n, 0x456n], gasRemaining: 2n });
expect(op_and(frame)).toEqual({ type: 'OutOfGas' });
});
});
Edge Cases Tested
- Basic AND operations (truth table)
- Identity element (AND with MAX_UINT256)
- Null element (AND with 0)
- Address extraction from uint256
- Flag checking
- Commutative property
- Alternating bit patterns
- Stack underflow
- Out of gas
Security
Incorrect Mask Size
// VULNERABLE: Mask doesn't cover full address
function extractAddress(uint256 packed) pure returns (address) {
return address(uint160(packed & 0xFFFFFFFF)); // Only 32 bits!
}
// CORRECT: Full 160-bit mask
function extractAddress(uint256 packed) pure returns (address) {
return address(uint160(packed & type(uint160).max));
}
Flag Checking Logic Errors
// WRONG: Checks if ANY flag is set (should be AND)
function hasPermission(uint256 perms, uint256 required) pure returns (bool) {
return (perms | required) != 0; // Using OR instead of AND
}
// CORRECT: Checks if ALL required flags are set
function hasPermission(uint256 perms, uint256 required) pure returns (bool) {
return (perms & required) == required;
}
Off-by-One in Bit Positions
// WRONG: Flag indices off by one
uint256 constant FLAG_0 = 1 << 1; // Should be 1 << 0
uint256 constant FLAG_1 = 1 << 2; // Should be 1 << 1
// CORRECT: Proper flag definitions
uint256 constant FLAG_0 = 1 << 0; // Bit 0
uint256 constant FLAG_1 = 1 << 1; // Bit 1
Endianness Confusion
// Be aware: BYTE opcode uses big-endian (byte 0 = MSB)
// But bitwise operations are position-agnostic
bytes32 data = 0x0123456789ABCDEF;
assembly {
// This extracts byte 0 (0x01), not byte 31!
let b := and(shr(248, data), 0xFF) // Correct
}
Benchmarks
AND is one of the fastest EVM operations:
Execution time (relative):
- AND: 1.0x (baseline, fastest tier)
- OR/XOR: 1.0x (same tier)
- ADD: 1.0x (same tier)
- MUL: 1.2x
- DIV: 2.5x
Gas efficiency:
- 3 gas per 256-bit AND operation
- ~333,333 AND operations per million gas
- Native hardware instruction on all platforms
References