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 } }
| Scenario | Function called |
|---|---|
msg.data == "" and receive exists | receive() |
msg.data == "" and no receive | fallback() |
msg.data != "" and no match | fallback() |
| Neither exists | revert |
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.callinstead.
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
.callforwards all gas by default (or the specifiedgas:amount). Enables reentrancy — pair withnonReentrantor 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.valueis only non-zero inpayablefunctions.- Accessing
msg.valuein 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
| Method | Gas forwarded | Recommended |
|---|---|---|
.transfer() | 2300 (fixed) | no |
.send() | 2300 (fixed) | no |
.call{value: v}("") | all (or specified) | yes |
| Internal call | all | yes |