Solidity Cheatsheet

Ether and payable

Use this Solidity reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Ether Units

uint256 a = 1 wei;    // 1
uint256 b = 1 gwei;   // 1_000_000_000
uint256 c = 1 ether;  // 1_000_000_000_000_000_000  (1e18)

// All values are in wei internally
uint256 fee = 0.01 ether; // 10_000_000_000_000_000

// No floating-point — use fixed-point math
uint256 half = 1 ether / 2; // 5e17

payable Keyword

Functions

// Without payable — reverts if msg.value > 0
function doWork() external { }

// With payable — accepts ether
function deposit() external payable {
    require(msg.value > 0, "No ether sent");
    balances[msg.sender] += msg.value;
}

// Constructor can be payable
constructor() payable {
    // Contract funded at deployment
}

Addresses

address      a = 0x123...;    // can receive ether via .call but NOT .transfer/.send
address payable p = payable(a); // can use .transfer, .send, .call

// Conversion
address payable p2 = payable(msg.sender);
address plain    = address(p2); // strip payable

// address(this) is address, NOT payable by default:
address payable self = payable(address(this));

Receiving Ether

A contract must have at least one of these to accept plain ether transfers:

contract Wallet {
    // Called when msg.data is empty + ether sent
    receive() external payable {
        emit Received(msg.sender, msg.value);
    }

    // Called when no function matches (or receive absent)
    fallback() external payable {
        // inspect msg.data
    }
}
ScenarioFunction called
msg.data == "" and receive existsreceive()
msg.data == "" and no receivefallback()
msg.data != "" and no matchfallback()
Neither existsrevert

Sending Ether

transfer (2300 gas, reverts on failure)

address payable recipient = payable(msg.sender);
recipient.transfer(1 ether); // reverts on failure, 2300 gas stipend

Avoid transfer — the 2300 gas stipend breaks with smart contract wallets and EIP-2929 cold slot reads. Use .call instead.

send (2300 gas, returns bool)

bool success = payable(msg.sender).send(1 ether);
require(success, "Send failed");

Same issues as transfer. Avoid in modern code.

.call (recommended)

(bool ok, ) = payable(recipient).call{value: 1 ether}("");
require(ok, "Transfer failed");

// With custom gas
(bool ok2, bytes memory data) = recipient.call{value: 0.1 ether, gas: 10_000}("");

// Always check return value — .call does NOT revert on failure

.call forwards all gas by default (or the specified gas: amount). Enables reentrancy — pair with nonReentrant or checks-effects-interactions.

msg.value

function buy(uint256 quantity) external payable {
    uint256 cost = quantity * PRICE_PER_UNIT;
    require(msg.value >= cost, "Underpaid");

    _mint(msg.sender, quantity);

    // Refund excess
    uint256 excess = msg.value - cost;
    if (excess > 0) {
        (bool ok, ) = payable(msg.sender).call{value: excess}("");
        require(ok, "Refund failed");
    }
}
  • msg.value is only non-zero in payable functions.
  • Accessing msg.value in a non-payable function is a compile error (0.5+).

Checking Balances

address(this).balance;         // contract's own ether balance
address(someAddr).balance;     // any address's balance

Forwarding Ether in Internal Calls

contract Router {
    function route(address target) external payable {
        // Forward all received ether to target
        (bool ok, ) = target.call{value: msg.value}("");
        require(ok, "Routing failed");
    }
}

selfdestruct (Deprecated)

// Forces all remaining ether to recipient — bypasses receive/fallback
// EIP-6049: deprecated. Post-Cancun: no longer destroys code/storage.
selfdestruct(payable(owner));

Do not rely on selfdestruct. It is deprecated and its behavior changed in the Cancun upgrade (EIP-6780).

Checks-Effects-Interactions Pattern

Always update state before sending ether to prevent reentrancy.

// WRONG — reentrancy vulnerable
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool ok, ) = msg.sender.call{value: amount}(""); // external call FIRST
    require(ok);
    balances[msg.sender] -= amount; // state updated AFTER — too late!
}

// CORRECT — checks-effects-interactions
function withdraw(uint256 amount) external nonReentrant {
    require(balances[msg.sender] >= amount, "Insufficient");
    balances[msg.sender] -= amount; // EFFECT first
    (bool ok, ) = msg.sender.call{value: amount}(""); // INTERACTION last
    require(ok, "Transfer failed");
}

Pull Payment Pattern

Prefer "pull" (user withdraws) over "push" (contract sends) to avoid forcing ether on recipients.

mapping(address => uint256) public pendingWithdrawals;

function claimRefund() external {
    uint256 amount = pendingWithdrawals[msg.sender];
    require(amount > 0, "Nothing to claim");
    pendingWithdrawals[msg.sender] = 0; // zero before transfer
    (bool ok, ) = payable(msg.sender).call{value: amount}("");
    require(ok, "Withdrawal failed");
}

Payable in Constructors and Create2

// Fund contract at deployment
contract FundedVault {
    uint256 public immutable initialFund;

    constructor() payable {
        initialFund = msg.value;
    }
}

// Create2 with ether
contract Factory {
    function deploy(bytes32 salt) external payable returns (address) {
        FundedVault v = new FundedVault{salt: salt, value: msg.value}();
        return address(v);
    }
}

Gas Stipend Reference

MethodGas forwardedRecommended
.transfer()2300 (fixed)no
.send()2300 (fixed)no
.call{value: v}("")all (or specified)yes
Internal callallyes