One rule
State is the data your contract keeps between calls. Access control is the rule for who can change that data.
Any function that changes important state must answer two questions: who is allowed, and what proof will show the change happened.
State needs guards
A contract is code plus data at an address. Storage stays after the transaction ends, so a bad state change stays too unless a later transaction fixes it.
Use public reads when useful, but keep writes narrow. Fees, roles, balances, owners, and status flags should not be changeable by any wallet.
Owner and events
This pattern is small enough to understand. It sets an owner once, restricts admin actions, and emits an event when state changes.
Use custom errors instead of long revert strings when the error is part of your contract design.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.37;
contract CampusClub {
address public immutable owner;
string public notice;
bool public paused;
error NotOwner(address caller);
error Paused();
error EmptyNotice();
event NoticeChanged(address indexed changedBy, string newNotice);
event PausedChanged(address indexed changedBy, bool paused);
modifier onlyOwner() {
if (msg.sender != owner) revert NotOwner(msg.sender);
_;
}
modifier whenNotPaused() {
if (paused) revert Paused();
_;
}
constructor(string memory firstNotice) {
owner = msg.sender;
notice = firstNotice;
}
function setNotice(string calldata newNotice) external onlyOwner whenNotPaused {
if (bytes(newNotice).length == 0) revert EmptyNotice();
notice = newNotice;
emit NoticeChanged(msg.sender, newNotice);
}
function setPaused(bool value) external onlyOwner {
paused = value;
emit PausedChanged(msg.sender, value);
}
}Read the pattern
Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.
0 of 5 done
Do not use tx.origin
tx.origin is the wallet that started the whole transaction. It can stay the same even when another contract calls your contract in the middle.
For authorization, use msg.sender. It tells you who called this function directly. Solidity security guidance says never use tx.origin for authorization.
Use OpenZeppelin when bigger
For a hackathon contract with simple ownership, writing the pattern yourself teaches you what is happening. For a larger repo, use audited building blocks instead of copy-pasting library code.
OpenZeppelin Contracts 5.x includes Ownable for onlyOwner, Ownable2Step for safer ownership transfer, and Pausable for emergency stops. Install the package instead of copying source files.
npm install @openzeppelin/contractsPrivate is not secret
private only stops other Solidity contracts from reading the variable directly. It does not hide the value from the chain.
Do not store secrets like OTPs, private keys, exam answers, or hidden auction bids in contract state. Everything used in a smart contract is publicly visible.
Do this now
Do each one yourself, then tap it to tick it off. The ticks are only a checklist for you: they are not marked or scored.
0 of 5 done