Opcode Summary

PropertyValue
Opcode0x11
MnemonicGT
Gas3
Stack Inputa, b
Stack Outputa > b (1 if true, 0 if false)
BehaviorUnsigned 256-bit greater-than comparison. Both operands are treated as unsigned integers. Returns 1 or 0.

Threat Surface

GT is the unsigned greater-than counterpart to LT. Mathematically, GT(a, b) == LT(b, a), and they share the same fundamental threat class. GT appears frequently in:

  • Threshold enforcement: require(amount > 0), require(collateralRatio > MIN_RATIO)
  • Time-lock release conditions: require(block.timestamp > unlockTime)
  • Withdrawal conditions: require(balance > withdrawalAmount)
  • >= compilation: Solidity’s a >= b often compiles to ISZERO(LT(a, b)), but a > b compiles directly to GT(a, b)

The same two fundamental risks from LT apply: unsigned-only semantics (misapplied to signed data) and comparison on overflow-corrupted values. GT has an additional nuance: it’s the opcode most commonly used in time-based access control (block.timestamp > deadline), making it a primary target for timestamp manipulation attacks.


Smart Contract Threats

T1: Unsigned Comparison on Signed Values (Critical)

Identical to LT’s T1 but with inverted consequences. When GT is used on values that should be signed:

  • GT(int256(-1), int256(100)) evaluates as GT(2^256 - 1, 100) = 1 (true)
  • The contract believes -1 is greater than 100

This is dangerous in:

  • PnL calculations: A negative profit appears as an enormous positive value, triggering profit distribution instead of loss accounting.
  • Voting weight comparisons: Negative penalties (slashing) appear as massive positive weights.
  • Assembly/Yul code: if gt(signedVar, threshold) uses unsigned GT when sgt was needed.

T2: Off-By-One in Greater-Than Checks (High)

The difference between > and >= in threshold checks determines whether the exact threshold value is permitted:

// Allows withdrawal only when balance strictly exceeds amount
require(balance > amount);  // GT: fails when balance == amount
 
// Allows withdrawal when balance equals or exceeds amount
require(balance >= amount);  // ISZERO(LT(balance, amount)): passes when balance == amount

In time-lock contracts, require(block.timestamp > unlockTime) prevents withdrawal at exactly unlockTime, while require(block.timestamp >= unlockTime) allows it. For long vesting schedules, this one-second difference is negligible, but for Dutch auctions and flash-loan-sensitive operations where transactions compete for exact block timestamps, the off-by-one can be exploited.

T3: Time-Lock Bypass via Overflow (High)

GT is the standard opcode for time-lock release: require(block.timestamp > unlockTime). If unlockTime is computed with unchecked arithmetic (pre-0.8.0 or in unchecked blocks) and overflows to a small value, the GT check passes immediately:

// Pre-0.8.0
function extendLock(uint256 additionalSeconds) external {
    unlockTime[msg.sender] += additionalSeconds;  // Overflow wraps to small value
}
// GT(block.timestamp, wrappedSmallValue) = true -- funds unlocked immediately

T4: Zero-Amount Bypass (Medium)

Many contracts use require(amount > 0) (compiled to GT(amount, 0)) to reject zero-value operations. But if the amount is computed from arithmetic that overflows or truncates to zero, this check alone is insufficient:

function swap(uint256 amountIn) external {
    uint256 amountOut = computeOutput(amountIn);  // Could return 0 due to rounding
    require(amountOut > 0);  // GT(amountOut, 0) -- passes only if non-zero
    // But what if amountOut rounds to 1 wei? Technically non-zero but economically worthless
}

Attackers exploit rounding to get amountOut = 1 wei that passes the GT check but drains precision value from the pool over many transactions.

T5: Block Timestamp Manipulation (Medium)

EVM block timestamps are set by validators/proposers with some tolerance (typically ±15 seconds). GT-based time checks are vulnerable to minor manipulation:

  • A validator can set block.timestamp slightly in the future to satisfy GT(block.timestamp, deadline) one block early
  • For MEV-sensitive operations (auctions, liquidations), this gives the proposer an unfair advantage

Protocol-Level Threats

P1: No DoS Vector (Low)

GT costs a fixed 3 gas with no dynamic component. Purely stack-based.

P2: Consensus Safety (Low)

Unsigned greater-than comparison is trivially deterministic. All EVM implementations agree. No known consensus divergence.

P3: No State Impact (None)

GT modifies only the stack.

P4: Compilation Pattern Variance (Low)

Solidity compiles >= as ISZERO(LT(a, b)) rather than GT(a, b) | EQ(a, b). Understanding this is relevant for bytecode auditors who need to distinguish between > and >= at the opcode level. The pattern GT(a, b) always means strict greater-than.


Edge Cases

Edge CaseBehaviorSecurity Implication
GT(0, 0)Returns 0 (false)Correct: 0 is not greater than 0
GT(1, 0)Returns 1 (true)Base case; common amount > 0 check
GT(0, MAX_UINT256)Returns 0 (false)Correct for unsigned
GT(MAX_UINT256, 0)Returns 1 (true)If value represents -1 (signed), comparison is inverted
GT(2^255, 2^255 - 1)Returns 1 (true)For signed: -2^255 > 2^255 - 1 would be false; unsigned says true
GT(a, a)Returns 0 (false)Strict greater-than; equal values return false
GT(block.timestamp, smallOverflowedValue)Returns 1 (true)Time-lock bypass: overflowed unlock time is in the past
GT(amount, 0) where amount was rounded downReturns 0 or 11 wei passes the check but may be economically meaningless

Real-World Exploits

Exploit 1: Time-Lock Overflow Bypass (OWASP Canonical Pattern)

Root cause: Integer overflow in lock time extension causes unlockTime to wrap to a small value, bypassing the GT-based time check.

Details: The OWASP Smart Contract Top 10 documents the “TimeWrapVault” pattern as a canonical exploit. A contract allows users to extend their lock period by adding seconds. In pre-0.8.0 Solidity, if a user passes type(uint256).max - currentUnlockTime + 1 as the additional seconds, the addition overflows and wraps unlockTime to 0 (or a small value). The subsequent require(block.timestamp > unlockTime) check (GT) passes immediately, allowing premature fund withdrawal.

GT’s role: GT is the security gate for time-based access control. It functions correctly on its inputs, but when the unlock time has been corrupted by overflow, the gate opens when it shouldn’t.

References:


Exploit 2: ResupplyFi — Exchange Rate Collapse Bypasses Solvency Check (June 2025)

Root cause: Exchange rate manipulation collapsed vault share price to near-zero, causing solvency ratio calculations to produce 0, which bypassed GT-based health checks.

Details: ResupplyFi’s lending protocol used an ERC4626 vault whose exchange rate could be manipulated through a first-depositor attack variant. The attacker inflated the vault’s totalAssets relative to totalSupply, collapsing the share price. When the protocol calculated the loan-to-value (LTV) ratio, the result was 0 due to integer truncation. The solvency check require(ltv > MIN_LTV) (GT-based) produced inconsistent results — positions appeared healthy with LTV of 0 when they were actually insolvent.

GT’s role: The GT-based solvency check was the protocol’s defense against undercollateralized borrowing. When the exchange rate was manipulated to produce truncated-to-zero LTV values, the comparison became meaningless, and $9.56M was drained.

References:


Exploit 3: Off-By-One in Liquidation Logic (Generic Pattern)

Root cause: Using > instead of >= in liquidation threshold comparison, preventing liquidation of positions at exactly the threshold.

Details: Multiple lending protocols have experienced issues where positions at exactly the minimum collateralization ratio cannot be liquidated due to strict greater-than checks. A position with exactly 150% collateral ratio passes require(ratio > 150) as false, making it unliquidatable. If the collateral value drops from 151% to 150% in one price update, there’s a window where the position is undercollateralized by the protocol’s intent but immune to liquidation by the code’s comparison logic.

GT’s role: The strict > (GT) excludes the boundary value. Using >= (ISZERO(LT(...))) would include it. This is the most common off-by-one pattern in DeFi protocols and has appeared in audit findings for numerous protocols.


Attack Scenarios

Scenario A: Signed/Unsigned Confusion in Funding Rate

contract VulnerablePerpetual {
    function applyFundingRate(int256 rate, uint256 position) external {
        uint256 payment;
        assembly {
            // BUG: gt() is unsigned. If rate is negative (e.g., int256(-50)),
            // it looks like a huge positive number, always "greater than" 0
            if gt(rate, 0) {
                // Intended: positive rate means longs pay shorts
                // Actual: negative rate also enters this branch!
                payment := mul(position, rate)  // Enormous payment computed
            }
        }
        _transferFunding(msg.sender, payment);
    }
}

Scenario B: Time-Lock Bypass via Overflow

// Solidity < 0.8.0
contract VulnerableTimelock {
    mapping(address => uint256) public unlockTime;
    mapping(address => uint256) public balances;
    
    function deposit() external payable {
        balances[msg.sender] += msg.value;
        unlockTime[msg.sender] = block.timestamp + 1 weeks;
    }
    
    function extendLock(uint256 extraSeconds) external {
        unlockTime[msg.sender] += extraSeconds;  // Overflow wraps to small value
    }
    
    function withdraw() external {
        require(block.timestamp > unlockTime[msg.sender]);  // GT passes if overflowed
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;
        payable(msg.sender).transfer(amount);
    }
}

Scenario C: Rounding Bypass of Zero-Amount Check

contract VulnerableAMM {
    function swap(uint256 amountIn) external {
        // amountOut = (amountIn * reserveOut) / (reserveIn + amountIn)
        // For tiny amountIn relative to reserveIn, this rounds to 0
        uint256 amountOut = (amountIn * reserveOut) / (reserveIn + amountIn);
        require(amountOut > 0);  // GT: passes if amountOut == 1 wei
        
        // Attacker swaps repeatedly with minimum input to extract 1 wei each time
        // Each swap also shifts the reserves, amplifying rounding in the attacker's favor
        _executeSwap(msg.sender, amountIn, amountOut);
    }
}

Mitigations

ThreatMitigationImplementation
T1: Signed/unsigned confusionUse SGT for signed comparisons in assemblysgt(a, b) not gt(a, b) for int256 values
T2: Off-by-oneExplicitly document whether boundary is inclusive or exclusiveTest at boundary, boundary-1, boundary+1; use >= when inclusive
T3: Time-lock overflowUse checked arithmetic for all time calculationsSolidity >= 0.8.0 default; cap maximum extension periods
T4: Zero-amount bypassEnforce minimum economic thresholds, not just non-zerorequire(amountOut >= MIN_OUTPUT) instead of > 0
T5: Timestamp manipulationDesign for ±15 second tolerance in time-sensitive operationsUse block.number for relative timing; avoid sub-minute precision

Compiler/EIP-Based Protections

  • Solidity 0.8.0+: Automatic overflow checks prevent time-lock and arithmetic bypass. The compiler uses GT/LT correctly based on Solidity types (uint vs int).
  • Solidity type system: int256 > int256 compiles to SGT automatically. GT is only emitted for unsigned comparisons in high-level Solidity.
  • Slither detectors: timestamp detector flags block.timestamp usage in comparisons. incorrect-equality flags strict equality where comparison operators should be used.

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalMediumAssembly-level DeFi bugs
T2Smart ContractHighHighLiquidation off-by-one in multiple protocols
T3Smart ContractHighHigh (pre-0.8) / Low (post-0.8)OWASP TimeWrapVault pattern
T4Smart ContractMediumMediumRounding exploitation in AMMs
T5Smart ContractMediumMediumMEV-sensitive operations
P1ProtocolLowN/A
P2ProtocolLowN/A

OpcodeRelationship
LT (0x10)Unsigned less-than; LT(a, b) == GT(b, a). Mirror of GT with identical threat class
SGT (0x13)Signed greater-than; must be used instead of GT when comparing int256 values
SLT (0x12)Signed less-than; signed counterpart to LT
EQ (0x14)Equality; GT and EQ combined express >=
ISZERO (0x15)Used to negate GT: a <= b compiles to ISZERO(GT(a, b))
TIMESTAMP (0x42)Block timestamp; GT is used in time-lock release patterns against TIMESTAMP
ADD (0x01)Overflow in ADD can corrupt values compared by GT