Solidity Basics
The last lesson described the bicycle escrow in words. On Ethereum and the other EVM chains, contracts like it are most often written in Solidity, a typed language with curly braces that looks familiar if you know JavaScript, Java or C. (Vyper, a Python-like language, compiles to the same EVM, and Rust is common on Solana.) Here is the escrow in full.
The rule
State stores it, functions change it,
requireguards it.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract BikeEscrow {
address public buyer;
address payable public seller;
bool public released;
constructor(address payable _seller) payable {
require(msg.value > 0, "send the payment");
buyer = msg.sender;
seller = _seller;
}
function release() external {
require(msg.sender == buyer, "only the buyer");
require(!released, "already released");
released = true;
(bool ok, ) = seller.call{value: address(this).balance}("");
require(ok, "payment failed");
}
}Read it from the top:
pragmapins the compiler version, because the language's rules change between versions.- State variables (
buyer,seller,released) live in the contract's storage on the chain. Marking thempublicmakes Solidity generate a getter function that anyone can call to read each one. - The constructor runs once, at deployment.
payablelets it receive coins.msg.valueis the amount sent with the call, andmsg.senderis the address that sent it, which here is Asha. releaseisexternal, so transactions from outside can call it. Itsrequirelines are guards: if a condition is false, the whole call reverts with that message.seller.call{value: …}("")sends the contract's entire balance to Ravi, and the lastrequirereverts everything if that transfer fails.
Before any of this runs on a chain, the Solidity source is compiled into EVM bytecode, and it is that bytecode which is deployed and executed by every node. Development tools such as Remix, Hardhat and Foundry compile, test and deploy contracts, usually on a local or test network first, where mistakes cost nothing.
Notice that released = true comes before the payment. Update your own state first, then interact with other addresses. This ordering closes a well-known class of bugs in which the receiving address calls back into the contract before its state has been updated. This escrow sends its whole balance in one go, so little could go wrong here, but in contracts that pay out from a shared pool, the wrong order has let attackers withdraw again and again. Make the habit automatic.
The shortcut
Read every function as "who, what, then what": who may call it (the msg.sender checks), what must be true (the require lines), then what state changes and what gets sent.
Who is calling?
msg.senderis Ravi's address, not the buyer's.
Drills
Answer fast — trust the rule.
Inside the constructor, what is msg.sender?
What does require(!released, "already released") prevent?
What does marking buyer as public do?
Why does release set released = true before sending the coins?
Which keyword lets the constructor receive coins when the contract is deployed?
Remember
- State stores it, functions change it,
requireguards it. - Shortcut: read each function as who, what, then what: caller checks, conditions, then state changes and payments.
- Trap: update state before sending coins, and remember that a reverted call still costs gas.
