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: 0x13
Introduced: Frontier (EVM genesis)
SGT performs signed greater than comparison on two 256-bit integers interpreted as two’s complement signed values. Returns 1 if the first value is strictly greater than the second, 0 otherwise. Values are in the range -2^255 to 2^255 - 1.
This operation complements SLT for implementing signed conditional logic and range checks.
Specification
Stack Input:
Stack Output:
signed(a) > signed(b) ? 1 : 0
Gas Cost: 3 (GasFastestStep)
Operation:
// Interpret as signed two's complement
signed_a = a >= 2^255 ? a - 2^256 : a
signed_b = b >= 2^255 ? b - 2^256 : b
result = (signed_a > signed_b) ? 1 : 0
Behavior
SGT pops two values from the stack, interprets them as signed 256-bit two’s complement integers, compares them, and pushes 1 if signed(a) > signed(b), otherwise 0:
- If
signed(a) > signed(b): Result is 1 (true)
- If
signed(a) <= signed(b): Result is 0 (false)
Two’s complement interpretation:
- Bit 255 = 0: Positive (0 to 2^255 - 1)
- Bit 255 = 1: Negative (-2^255 to -1)
Examples
Positive Values
import { sgt } from '@tevm/voltaire/evm/comparison';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// 20 > 10 = 1 (both positive)
const frame = createFrame({ stack: [20n, 10n] });
const err = sgt(frame);
console.log(frame.stack); // [1n]
console.log(frame.gasRemaining); // Original - 3
Positive Greater Than Negative
// 10 > -1 = 1 (true)
const NEG_1 = (1n << 256n) - 1n; // Two's complement -1
const frame = createFrame({ stack: [10n, NEG_1] });
sgt(frame);
console.log(frame.stack); // [1n]
Negative Less Than Positive
// -1 > 10 = 0 (false, -1 < 10)
const NEG_1 = (1n << 256n) - 1n;
const frame = createFrame({ stack: [NEG_1, 10n] });
sgt(frame);
console.log(frame.stack); // [0n]
Negative Value Comparison
// -5 > -10 = 1 (true)
const NEG_5 = (1n << 256n) - 5n;
const NEG_10 = (1n << 256n) - 10n;
const frame = createFrame({ stack: [NEG_5, NEG_10] });
sgt(frame);
console.log(frame.stack); // [1n]
Zero Boundary
// 0 > -1 = 1 (true)
const NEG_1 = (1n << 256n) - 1n;
const frame = createFrame({ stack: [0n, NEG_1] });
sgt(frame);
console.log(frame.stack); // [1n]
// 1 > 0 = 1 (true)
const frame2 = createFrame({ stack: [1n, 0n] });
sgt(frame2);
console.log(frame2.stack); // [1n]
Minimum and Maximum
// MAX_INT256 > MIN_INT256 = 1
const MIN_INT256 = 1n << 255n; // -2^255
const MAX_INT256 = (1n << 255n) - 1n; // 2^255 - 1
const frame = createFrame({ stack: [MAX_INT256, MIN_INT256] });
sgt(frame);
console.log(frame.stack); // [1n]
Contrast with Unsigned GT
// 2^255 has bit 255 set
const SIGN_BIT = 1n << 255n;
// SGT: -2^255 > 1 = 0 (false, signed)
const frame1 = createFrame({ stack: [SIGN_BIT, 1n] });
sgt(frame1);
console.log(frame1.stack); // [0n]
// GT: 2^255 > 1 = 1 (true, unsigned - 2^255 is huge positive)
const frame2 = createFrame({ stack: [SIGN_BIT, 1n] });
gt(frame2);
console.log(frame2.stack); // [1n]
Gas Cost
Cost: 3 gas (GasFastestStep)
SGT shares the lowest gas tier with all comparison operations:
- LT, GT, SLT, SGT, EQ (comparisons)
- ISZERO, NOT
- ADD, SUB
Comparison:
- SGT/SLT/GT/LT: 3 gas
- MUL/DIV: 5 gas
- SDIV/SMOD: 5 gas
Edge Cases
Signed Boundary Values
const MIN_INT256 = 1n << 255n; // -2^255
const MAX_INT256 = (1n << 255n) - 1n; // 2^255 - 1
const NEG_1 = (1n << 256n) - 1n; // -1
// MAX > MIN
sgt(createFrame({ stack: [MAX_INT256, MIN_INT256] })); // [1n]
// MIN < MAX
sgt(createFrame({ stack: [MIN_INT256, MAX_INT256] })); // [0n]
// 0 > -1
sgt(createFrame({ stack: [0n, NEG_1] })); // [1n]
// -1 < 0
sgt(createFrame({ stack: [NEG_1, 0n] })); // [0n]
Equal Values
// Any value compared to itself
const NEG_10 = (1n << 256n) - 10n;
sgt(createFrame({ stack: [20n, 20n] })); // [0n]
sgt(createFrame({ stack: [NEG_10, NEG_10] })); // [0n]
sgt(createFrame({ stack: [0n, 0n] })); // [0n]
Sign Bit Boundary
// Just below sign bit (largest positive)
const MAX_POS = (1n << 255n) - 1n;
// Just at sign bit (smallest negative)
const MIN_NEG = 1n << 255n;
// Unsigned: MIN_NEG > MAX_POS
gt(createFrame({ stack: [MIN_NEG, MAX_POS] })); // [1n]
// Signed: MIN_NEG < MAX_POS
sgt(createFrame({ stack: [MIN_NEG, MAX_POS] })); // [0n]
Stack Underflow
// Not enough stack items
const frame = createFrame({ stack: [10n] });
const err = sgt(frame);
console.log(err); // { type: "StackUnderflow" }
console.log(frame.stack); // [10n] (unchanged)
Out of Gas
// Insufficient gas
const frame = createFrame({ stack: [20n, 10n], gasRemaining: 2n });
const err = sgt(frame);
console.log(err); // { type: "OutOfGas" }
console.log(frame.gasRemaining); // 0n
Common Usage
Positive Value Check
// Check if value is positive (> 0)
assembly {
let isPositive := sgt(value, 0)
if iszero(isPositive) {
revert(0, 0)
}
}
Signed Upper Bounds
// require(signedValue <= max) === require(!(signedValue > max))
assembly {
if sgt(signedValue, max) {
revert(0, 0)
}
}
Maximum of Signed Values
// max(a, b) for signed integers
assembly {
let maximum := a
if sgt(b, a) {
maximum := b
}
}
Signed Range Validation
// Check if value in signed range (min, max)
assembly {
let inRange := and(
sgt(value, min), // value > min
iszero(sgt(value, max)) // value <= max
)
}
Non-Negative Check
// require(value >= 0) === require(!(value < 0))
assembly {
if slt(value, 0) {
revert(0, 0)
}
}
// Equivalent:
assembly {
if iszero(or(sgt(value, 0), iszero(value))) {
revert(0, 0)
}
}
Implementation
/**
* SGT opcode (0x13) - Signed greater than comparison
*/
export function handle(frame: FrameType): EvmError | null {
// Consume gas (GasFastestStep = 3)
const gasErr = consumeGas(frame, FastestStep);
if (gasErr) return gasErr;
// Pop operands (b is top, a is second)
const bResult = popStack(frame);
if (bResult.error) return bResult.error;
const b = bResult.value;
const aResult = popStack(frame);
if (aResult.error) return aResult.error;
const a = aResult.value;
// Convert to signed and compare
const aSigned = toSigned256(a);
const bSigned = toSigned256(b);
const result = aSigned > bSigned ? 1n : 0n;
// Push result
const pushErr = pushStack(frame, result);
if (pushErr) return pushErr;
// Increment PC
frame.pc += 1;
return null;
}
/**
* Convert unsigned 256-bit to signed two's complement
*/
function toSigned256(value: bigint): bigint {
const MAX_INT256 = 1n << 255n;
if (value >= MAX_INT256) {
return value - (1n << 256n);
}
return value;
}
Testing
Test Coverage
import { describe, it, expect } from 'vitest';
import { handle as SGT } from './0x13_SGT.js';
describe('SGT (0x13)', () => {
it('returns 1 when a > b (both positive)', () => {
const frame = createFrame([30n, 20n]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
expect(frame.gasRemaining).toBe(997n);
});
it('returns 1 when positive > negative', () => {
const NEG_1 = (1n << 256n) - 1n;
const frame = createFrame([10n, NEG_1]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([1n]); // 10 > -1
});
it('returns 0 when negative < positive', () => {
const NEG_1 = (1n << 256n) - 1n;
const frame = createFrame([NEG_1, 10n]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([0n]); // -1 < 10
});
it('compares negative numbers correctly', () => {
const NEG_5 = (1n << 256n) - 5n;
const NEG_10 = (1n << 256n) - 10n;
const frame = createFrame([NEG_5, NEG_10]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([1n]); // -5 > -10
});
it('handles 0 > -1', () => {
const NEG_1 = (1n << 256n) - 1n;
const frame = createFrame([0n, NEG_1]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('handles MAX_INT256 > MIN_INT256', () => {
const MIN = 1n << 255n;
const MAX = (1n << 255n) - 1n;
const frame = createFrame([MAX, MIN]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([1n]);
});
it('returns 0 when a <= b (equal)', () => {
const frame = createFrame([20n, 20n]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame([10n]);
expect(SGT(frame)).toEqual({ type: 'StackUnderflow' });
});
it('preserves stack below compared values', () => {
const frame = createFrame([100n, 200n, 30n, 20n]);
expect(SGT(frame)).toBeNull();
expect(frame.stack).toEqual([100n, 200n, 1n]);
});
});
Edge Cases Tested
- Positive value comparisons
- Positive greater than negative
- Negative less than positive
- Negative value comparisons (-5 > -10)
- Zero boundary (0 > -1, 1 > 0)
- MAX_INT256 and MIN_INT256
- Equal values
- Stack underflow
- Out of gas
- Stack preservation
Security
Critical: Signed vs Unsigned Confusion
COMMON VULNERABILITY: Using GT instead of SGT for signed values:
// VULNERABLE: Using GT for signed comparison
function isPositive(int256 value) returns (bool) {
// GT treats -1 as 2^256-1 (huge positive!)
assembly {
return(0, gt(value, 0)) // WRONG!
}
// Returns true for negative values!
}
// CORRECT: Use SGT for signed values
function isPositive(int256 value) returns (bool) {
assembly {
return(0, sgt(value, 0)) // Correct
}
}
Type Safety Issues
// VULNERABLE: Mixed signed/unsigned
function checkLimit(uint256 unsigned, int256 signed) {
// Direct comparison uses unsigned semantics
require(unsigned > signed); // Type confusion!
}
// CORRECT: Explicit type handling
function checkLimit(uint256 unsigned, int256 signed) {
require(signed >= 0, "negative value");
require(unsigned > uint256(signed));
}
Overflow in Signed Operations
// VULNERABLE: Overflow before comparison
int256 result = a - b; // May overflow
require(result > 0); // Check may be wrong
// CORRECT: Check before operation
if (a > 0 && b < 0) {
require(a <= type(int256).max + b, "overflow");
}
int256 result = a - b;
Sign Extension Errors
// VULNERABLE: Wrong sign extension
function extend(int8 small) returns (int256) {
// Casting through uint loses sign
return int256(uint256(uint8(small))); // Wrong!
}
// CORRECT: Direct sign extension
function extend(int8 small) returns (int256) {
return int256(small); // Preserves sign
}
Optimizations
Relationship to SLT
// These are equivalent:
// a > b === b < a
assembly {
let greater := sgt(a, b)
// Same as:
let greater := slt(b, a)
}
// Choose based on stack layout to minimize swaps
Positive Check Optimization
// Check if value > 0
assembly {
let isPos := sgt(value, 0) // 3 gas
}
// Equivalent but more expensive:
assembly {
let notNeg := iszero(slt(value, 0)) // 6 gas
let notZero := iszero(iszero(value)) // 6 gas
let isPos := and(notNeg, notZero) // 9 gas total
}
Inversion Pattern
// Direct comparison (preferred)
assembly {
let greater := sgt(a, b) // 3 gas
}
// Inverted (avoid - more expensive)
assembly {
let greater := iszero(or(slt(a, b), eq(a, b))) // 12 gas
}
Benchmarks
SGT performance matches other comparison operations:
Execution time (relative):
- SGT: 1.05x (slightly slower due to sign conversion)
- SLT: 1.05x
- GT/LT/EQ: 1.0x
- ISZERO: 0.95x
Gas efficiency:
- 3 gas per signed comparison
- ~333,333 comparisons per million gas
- Sign conversion adds negligible overhead
References
- SLT - Signed less than
- GT - Unsigned greater than
- LT - Unsigned less than
- SDIV - Signed division
- SMOD - Signed modulo