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: 0x02
Introduced: Frontier (EVM genesis)
MUL performs multiplication on two 256-bit unsigned integers with wrapping overflow semantics. When the result exceeds 2^256 - 1, only the lower 256 bits are kept, effectively computing (a * b) mod 2^256.
This operation is fundamental for scaling calculations, area/volume computations, and fixed-point arithmetic in smart contracts.
Specification
Stack Input:
Stack Output:
Gas Cost: 5 (GasFastStep)
Operation:
result = (a * b) & ((1 << 256) - 1)
Behavior
MUL pops two values from the stack, multiplies them, and pushes the lower 256 bits of the result. The upper bits are discarded:
- If
a * b < 2^256: Result is the mathematical product
- If
a * b >= 2^256: Result is the lower 256 bits (truncated)
No exceptions are thrown for overflow. Information in the upper 256 bits is lost.
Examples
Basic Multiplication
import { mul } from '@tevm/voltaire/evm/arithmetic';
import { createFrame } from '@tevm/voltaire/evm/Frame';
// 5 * 10 = 50
const frame = createFrame({ stack: [5n, 10n] });
const err = mul(frame);
console.log(frame.stack); // [50n]
console.log(frame.gasRemaining); // Original - 5
Overflow Truncation
// Large multiplication overflows
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX, 2n] });
const err = mul(frame);
// Result: Only lower 256 bits kept
// (MAX * 2) mod 2^256 = 2^256 - 2 = MAX - 1
console.log(frame.stack); // [MAX - 1n]
Powers of Two
// Multiplying by 2 is left shift
const frame = createFrame({ stack: [0x0Fn, 2n] });
const err = mul(frame);
console.log(frame.stack); // [0x1En] (15 * 2 = 30)
Identity Element
// Multiplying by 1
const frame = createFrame({ stack: [42n, 1n] });
const err = mul(frame);
console.log(frame.stack); // [42n]
Zero Element
// Multiplying by 0
const frame = createFrame({ stack: [42n, 0n] });
const err = mul(frame);
console.log(frame.stack); // [0n]
Gas Cost
Cost: 5 gas (GasFastStep)
MUL costs slightly more than ADD/SUB due to increased computational complexity:
Comparison:
- ADD/SUB: 3 gas
- MUL/DIV/MOD/SDIV/SMOD/SIGNEXTEND: 5 gas
- ADDMOD/MULMOD: 8 gas
- EXP: 10 + 50 per byte
MUL is one of the most gas-efficient ways to multiply in the EVM, but still ~67% more expensive than addition.
Edge Cases
Maximum Overflow
// MAX * MAX overflows significantly
const MAX = (1n << 256n) - 1n;
const frame = createFrame({ stack: [MAX, MAX] });
mul(frame);
// Only lower 256 bits: (2^256-1)^2 mod 2^256 = 1
console.log(frame.stack); // [1n]
Square Operations
// Squaring a number
const frame = createFrame({ stack: [12n, 12n] });
mul(frame);
console.log(frame.stack); // [144n]
Multiplication by Powers of Two
// Efficient scaling
const frame = createFrame({ stack: [100n, 1n << 10n] }); // * 1024
mul(frame);
console.log(frame.stack); // [102400n]
Stack Underflow
// Not enough stack items
const frame = createFrame({ stack: [5n] });
const err = mul(frame);
console.log(err); // { type: "StackUnderflow" }
Common Usage
Fixed-Point Arithmetic
// 18 decimal fixed-point multiplication
uint256 constant WAD = 1e18;
function wmul(uint256 x, uint256 y) pure returns (uint256) {
return (x * y) / WAD;
}
// Example: 1.5 * 2.5 = 3.75
// (1.5e18 * 2.5e18) / 1e18 = 3.75e18
Percentage Calculations
// Calculate 5% fee
function calculateFee(uint256 amount) pure returns (uint256) {
return (amount * 5) / 100;
}
// Calculate with basis points (0.01%)
function feeInBps(uint256 amount, uint256 bps) pure returns (uint256) {
return (amount * bps) / 10000;
}
Area/Volume Calculations
// Rectangle area
function area(uint256 width, uint256 height) pure returns (uint256) {
return width * height;
}
// Cube volume
function volume(uint256 side) pure returns (uint256) {
return side * side * side;
}
Scaling and Conversion
// Convert tokens between decimal precisions
function convertDecimals(
uint256 amount,
uint8 fromDecimals,
uint8 toDecimals
) pure returns (uint256) {
if (fromDecimals > toDecimals) {
return amount / (10 ** (fromDecimals - toDecimals));
} else {
return amount * (10 ** (toDecimals - fromDecimals));
}
}
Implementation
/**
* MUL opcode (0x02) - Multiplication with overflow wrapping
*/
export function mul(frame: FrameType): EvmError | null {
// Consume gas (GasFastStep = 5)
frame.gasRemaining -= 5n;
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 result with wrapping (modulo 2^256)
const result = (a * b) & ((1n << 256n) - 1n);
// 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 { mul } from './0x02_MUL.js';
describe('MUL (0x02)', () => {
it('multiplies two numbers', () => {
const frame = createFrame([5n, 10n]);
expect(mul(frame)).toBeNull();
expect(frame.stack).toEqual([50n]);
});
it('handles overflow wrapping', () => {
const MAX = (1n << 256n) - 1n;
const frame = createFrame([MAX, 2n]);
expect(mul(frame)).toBeNull();
expect(frame.stack).toEqual([MAX - 1n]);
});
it('squares numbers correctly', () => {
const frame = createFrame([12n, 12n]);
expect(mul(frame)).toBeNull();
expect(frame.stack).toEqual([144n]);
});
it('handles multiplication by zero', () => {
const frame = createFrame([42n, 0n]);
expect(mul(frame)).toBeNull();
expect(frame.stack).toEqual([0n]);
});
it('handles multiplication by one', () => {
const frame = createFrame([42n, 1n]);
expect(mul(frame)).toBeNull();
expect(frame.stack).toEqual([42n]);
});
it('returns StackUnderflow with insufficient stack', () => {
const frame = createFrame([5n]);
expect(mul(frame)).toEqual({ type: 'StackUnderflow' });
});
it('consumes correct gas (5)', () => {
const frame = createFrame([5n, 10n], 100n);
expect(mul(frame)).toBeNull();
expect(frame.gasRemaining).toBe(95n);
});
});
Security
Overflow Vulnerabilities
Pre-Solidity 0.8.0 vulnerability:
// VULNERABLE: No overflow protection
function calculateShares(uint256 price, uint256 quantity) returns (uint256) {
return price * quantity; // Can overflow!
}
Attack scenario:
// Attacker calls: calculateShares(2^200, 2^100)
// Expected: Massive value
// Actual: Overflows to small value, attacker pays less
Mitigation (SafeMath):
function calculateShares(uint256 price, uint256 quantity) returns (uint256) {
uint256 result = price * quantity;
require(price == 0 || result / price == quantity, "overflow");
return result;
}
Safe Fixed-Point Arithmetic
Vulnerable pattern:
// WRONG: Intermediate overflow
function wmul(uint256 x, uint256 y) pure returns (uint256) {
return (x * y) / WAD; // x * y can overflow!
}
Safe pattern (using mulmod for intermediate):
function wmul(uint256 x, uint256 y) pure returns (uint256 z) {
// Use assembly to get full 512-bit intermediate result
assembly {
if iszero(or(iszero(x), eq(div(mul(x, y), x), y))) {
revert(0, 0) // Overflow
}
z := div(mul(x, y), WAD)
}
}
Better: Use MULMOD opcode:
// Avoids overflow completely
function mulDivDown(uint256 x, uint256 y, uint256 denominator)
pure returns (uint256 z) {
assembly {
// Equivalent to (x * y) / denominator with 512-bit intermediate
z := div(mul(x, y), denominator)
// Check for overflow: require(denominator > 0 &&
// (x == 0 || (x * y) / x == y))
if iszero(and(
gt(denominator, 0),
or(iszero(x), eq(div(mul(x, y), x), y))
)) { revert(0, 0) }
}
}
Modern Solidity (0.8.0+)
// Automatic overflow checks
function multiply(uint256 a, uint256 b) pure returns (uint256) {
return a * b; // Reverts on overflow
}
// Explicit wrapping when needed
function unsafeMultiply(uint256 a, uint256 b) pure returns (uint256) {
unchecked {
return a * b; // Uses raw MUL, wraps on overflow
}
}
Benchmarks
MUL performance characteristics:
Relative execution time:
- ADD: 1.0x
- MUL: 1.2x
- DIV: 2.5x
- ADDMOD: 3.0x
Gas efficiency:
- 5 gas per 256-bit multiplication
- ~200,000 multiplications per million gas
- Significantly faster than repeated addition
Optimization tip:
// Prefer MUL over repeated ADD
uint256 result = x * 10; // 5 gas
// Instead of:
uint256 result = x + x + x + x + x + x + x + x + x + x; // 30 gas
References