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: 0x47 Introduced: Istanbul (EIP-1884) SELFBALANCE retrieves the balance of the currently executing contract in wei. This is a gas-efficient alternative to ADDRESS followed by BALANCE for querying the executing contract’s own balance.

Specification

Stack Input:
(none)
Stack Output:
balance (wei as u256)
Gas Cost: 5 (GasFastStep) Operation:
stack.push(balance(address(this)))
Hardfork: Available from Istanbul onwards

Behavior

SELFBALANCE pushes the current contract’s balance onto the stack as a 256-bit unsigned integer in wei:
Balance: 1.5 ETH
In wei:  1500000000000000000
As u256: 0x14d1120d7b160000
This is significantly cheaper than the equivalent sequence:
  • ADDRESS (2 gas) + BALANCE (cold: 2600 gas, warm: 100 gas) = 2602+ gas
  • SELFBALANCE: 5 gas

Examples

Basic Usage

import { selfbalance } from '@tevm/voltaire/evm/block';
import { createFrame } from '@tevm/voltaire/evm/Frame';

const contractAddress = new Uint8Array(20).fill(0xaa);

const frame = createFrame({
  stack: [],
  hardfork: 'ISTANBUL',
  address: contractAddress,
  evm: {
    get_balance: (addr) => {
      if (addr.every((b, i) => b === contractAddress[i])) {
        return 1_500_000_000_000_000_000n; // 1.5 ETH
      }
      return 0n;
    }
  }
});

const err = selfbalance(frame);
console.log(frame.stack); // [1500000000000000000n]
console.log(frame.gasRemaining); // Original - 5

Pre-Istanbul Error

// Before Istanbul hardfork
const preIstanbulFrame = createFrame({
  hardfork: 'PETERSBURG',
  address: contractAddress
});

const err = selfbalance(preIstanbulFrame);
console.log(err); // { type: "InvalidOpcode" }

Balance Checks

// Check if contract has sufficient balance
selfbalance(frame);
const balance = frame.stack[0];
const required = 1_000_000_000_000_000_000n; // 1 ETH

const hasFunds = balance >= required;
console.log(`Sufficient funds: ${hasFunds}`);

Gas Cost

Cost: 5 gas (GasFastStep) SELFBALANCE is dramatically cheaper than the alternative: Comparison:
  • SELFBALANCE: 5 gas
  • ADDRESS + BALANCE (cold): 2 + 2600 = 2602 gas (520x more expensive!)
  • ADDRESS + BALANCE (warm): 2 + 100 = 102 gas (20x more expensive)
This makes SELFBALANCE one of the most cost-effective operations introduced in Istanbul.

Common Usage

Minimum Balance Guard

contract MinBalanceGuard {
    uint256 public constant MIN_BALANCE = 0.1 ether;

    modifier requireMinBalance() {
        require(address(this).balance >= MIN_BALANCE, "Insufficient balance");
        _;
    }

    function protectedOperation() external requireMinBalance {
        // Execute only if contract has minimum balance
    }
}

Balance Tracking

contract BalanceTracker {
    event BalanceChanged(uint256 oldBalance, uint256 newBalance);

    uint256 public lastKnownBalance;

    function updateBalance() external {
        uint256 current = address(this).balance;

        if (current != lastKnownBalance) {
            emit BalanceChanged(lastKnownBalance, current);
            lastKnownBalance = current;
        }
    }
}

Payment Verification

contract PaymentVerifier {
    uint256 public balanceBeforePayment;

    function expectPayment(uint256 amount) external {
        balanceBeforePayment = address(this).balance;
        // ... trigger payment flow
    }

    function verifyPayment(uint256 expectedAmount) external view returns (bool) {
        uint256 received = address(this).balance - balanceBeforePayment;
        return received >= expectedAmount;
    }
}

Withdrawal Logic

contract SafeWithdrawal {
    function withdraw(uint256 amount) external {
        require(amount <= address(this).balance, "Insufficient balance");
        payable(msg.sender).transfer(amount);
    }

    function withdrawAll() external {
        uint256 balance = address(this).balance;
        require(balance > 0, "No balance");
        payable(msg.sender).transfer(balance);
    }

    function availableBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

Fee Collection

contract FeeCollector {
    uint256 public collectedFees;

    function collectFee() external payable {
        collectedFees += msg.value;
    }

    function verifyCollection() external view returns (bool) {
        // Verify balance matches expected fees
        return address(this).balance >= collectedFees;
    }

    function withdrawFees() external {
        uint256 amount = address(this).balance;
        payable(owner).transfer(amount);
    }
}

Auction Reserve Check

contract Auction {
    uint256 public reservePrice;

    function finalize() external {
        require(address(this).balance >= reservePrice, "Reserve not met");
        // Transfer to seller
        payable(seller).transfer(address(this).balance);
    }
}

Security Considerations

Reentrancy and Balance Changes

Balance can change during execution:
contract ReentrancyAware {
    // VULNERABLE: Balance check before external call
    function vulnerable() external {
        uint256 balance = address(this).balance;
        // External call could send ETH back (reentrancy)
        externalContract.call{value: balance}("");
        // balance value is now stale!
    }

    // SAFE: Check balance after operations
    function safe() external {
        externalContract.call("");
        uint256 finalBalance = address(this).balance;
        // Use current balance
    }
}

SELFDESTRUCT Interaction

Contracts can receive ETH from SELFDESTRUCT without receive function:
contract SelfdestructRecipient {
    function checkBalance() external view returns (uint256) {
        // Balance could be > 0 even without receive/fallback
        // if another contract selfdestructed to this address
        return address(this).balance;
    }
}

Balance vs State Accounting

Don’t rely solely on balance for accounting:
contract BadAccounting {
    // VULNERABLE: No internal accounting
    function withdraw() external {
        uint256 share = address(this).balance / totalShares;
        // Balance could include unexpected ETH from SELFDESTRUCT
        payable(msg.sender).transfer(share);
    }
}

contract GoodAccounting {
    mapping(address => uint256) public balances;

    // SAFE: Track deposits explicitly
    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw() external {
        uint256 amount = balances[msg.sender];
        balances[msg.sender] = 0;
        payable(msg.sender).transfer(amount);
    }
}

Gas Cost Changes

Pre-Istanbul, querying self balance was expensive:
contract GasAware {
    // Pre-Istanbul: 2602+ gas
    // Post-Istanbul: 5 gas
    function getBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

Implementation

/**
 * SELFBALANCE opcode (0x47) - Get balance of executing contract
 * Available: Istanbul+
 */
export function selfbalance(frame: FrameType): EvmError | null {
  // Check hardfork availability
  if (frame.evm.hardfork.isBefore('ISTANBUL')) {
    return { type: "InvalidOpcode" };
  }

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

  // Get balance of current contract
  const balance = frame.evm.get_balance(frame.address);

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

  frame.pc += 1;
  return null;
}

Edge Cases

Pre-Istanbul Execution

// Before Istanbul: InvalidOpcode
const frame = createFrame({
  hardfork: 'CONSTANTINOPLE',
  address: contractAddress
});

const err = selfbalance(frame);
console.log(err); // { type: "InvalidOpcode" }

Zero Balance

// Contract with no ETH
const frame = createFrame({
  hardfork: 'ISTANBUL',
  address: contractAddress,
  evm: {
    get_balance: () => 0n
  }
});

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

Maximum Balance

// Theoretical maximum balance
const frame = createFrame({
  hardfork: 'ISTANBUL',
  address: contractAddress,
  evm: {
    get_balance: () => (1n << 256n) - 1n
  }
});

selfbalance(frame);
console.log(frame.stack); // [max u256]

During ETH Transfer

// Balance mid-execution (after receive)
const frame = createFrame({
  hardfork: 'ISTANBUL',
  address: contractAddress,
  evm: {
    get_balance: () => 1_000_000_000_000_000_000n + 500_000_000_000_000_000n
    // Original 1 ETH + 0.5 ETH just received
  }
});

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

Practical Patterns

Balance-Based State Machine

contract BalanceStateMachine {
    enum State { Empty, Funded, Operating, Closing }

    function currentState() public view returns (State) {
        uint256 balance = address(this).balance;

        if (balance == 0) return State.Empty;
        if (balance < 1 ether) return State.Funded;
        if (balance < 10 ether) return State.Operating;
        return State.Closing;
    }
}

Efficient Balance Queries

contract EfficientQueries {
    // 5 gas
    function selfBalance() external view returns (uint256) {
        return address(this).balance;
    }

    // 2602+ gas (pre-warm)
    function otherBalance(address addr) external view returns (uint256) {
        return addr.balance;
    }
}

Benchmarks

Performance:
  • Balance lookup: O(1) from state
  • Stack push: O(1)
Gas efficiency:
  • 5 gas per query
  • ~200,000 queries per million gas
  • 520x cheaper than ADDRESS + BALANCE (cold)

References