By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Tokenomics is the economic design behind a blockchain token—how many tokens exist, who gets them, when they become spendable, and how holders influence the protocol. By encoding supply rules, distribution schedules, and governance rights directly into smart contracts, projects can align incentives, fund development, and let the community run the system without a central authority. Real?world example: The UNI token that powers Uniswap’s DAO; holders receive a share of protocol fees and can vote on fee?structure changes, while a portion of UNI was locked in a 4?year vesting curve for the team and early contributors.
Total Supply (uint256 public constant MAX_SUPPLY) – The hard cap of tokens that can ever exist. solidity uint256 public constant MAX_SUPPLY = 1_000_000 * 1e18; // 1?M tokens with 18 decimals
uint256 public constant MAX_SUPPLY
solidity uint256 public constant MAX_SUPPLY = 1_000_000 * 1e18; // 1?M tokens with 18 decimals
Initial Mint (_mint(msg.sender, amount)) – Tokens created at deployment for founders, treasury, or a liquidity bootstrap. solidity constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 200_000 * 1e18); // 20% to deployer }
_mint(msg.sender, amount)
solidity constructor() ERC20("MyToken", "MTK") { _mint(msg.sender, 200_000 * 1e18); // 20% to deployer }
Vesting Schedule (LinearVesting) – A contract that releases tokens over time to prevent dump?and?burn attacks. ```solidity contract LinearVesting { IERC20 public immutable token; uint256 public immutable start; uint256 public immutable cliff; // earliest unlock uint256 public immutable duration; uint256 public totalAllocated; mapping(address => uint256) public released;
LinearVesting
function claim(address beneficiary) external { uint256 vested = _vestedAmount(beneficiary); uint256 claimable = vested - released[beneficiary]; released[beneficiary] = vested; token.transfer(beneficiary, claimable); } // _vestedAmount() calculates linear proportion based on block.timestamp } ```
Distribution Model – The blueprint for who gets what (e.g., 40?% community, 20?% team, 15?% investors, 25?% ecosystem). Usually encoded as a series of grant calls in the deployment script.
grant
Governance Token (ERC20Votes) – An ERC?20 that tracks voting power via snapshots, enabling on?chain proposals. solidity contract GovToken is ERC20, ERC20Permit, ERC20Votes { constructor() ERC20("GovToken", "GOV") ERC20Permit("GovToken") {} function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { super._afterTokenTransfer(from, to, amount); } }
ERC20Votes
solidity contract GovToken is ERC20, ERC20Permit, ERC20Votes { constructor() ERC20("GovToken", "GOV") ERC20Permit("GovToken") {} function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { super._afterTokenTransfer(from, to, amount); } }
Snapshot (_snapshot()) – Takes a block?level picture of token balances so votes are based on holdings at proposal creation, not on?the?fly transfers.
_snapshot()
Emission Rate (uint256 public rewardPerBlock) – For tokens that are minted as block rewards (e.g., staking or liquidity mining).
uint256 public rewardPerBlock
Burn Mechanism (_burn(address from, uint256 amount)) – Reduces circulating supply, often used to create deflationary pressure.
_burn(address from, uint256 amount)
Treasury Wallet (address public treasury) – Holds a pool of tokens for future grants, community incentives, or protocol upgrades.
address public treasury
Delegation (delegate(address delegatee)) – Allows a token holder to assign voting power to another address without transferring tokens.
delegate(address delegatee)
Anti?Whale Transfer Limit (require(amount <= maxTx, "Too big")) – Caps single?transaction size to curb market manipulation.
require(amount <= maxTx, "Too big")
ERC20
Vesting
bash npx hardhat init npm install @openzeppelin/contracts
npx hardhat compile
bash npx hardhat run scripts/deploy.js --network goerli
GovToken
vestedAmount
rewardPerBlock
Governor
delegate
castVote
Mistake: Hard?coding the total supply in multiple places (e.g., both in the contract and the deployment script). Correction: Keep a single source of truth (a constant in the Solidity file) and reference it everywhere; mismatched caps cause supply bugs and audit red flags.
Mistake: Forgetting to call _snapshot() after each token transfer when using ERC20Votes. Correction: Inherit ERC20Votes and let OpenZeppelin’s _afterTokenTransfer hook handle snapshots automatically.
_afterTokenTransfer
Mistake: Using block.timestamp for vesting cliffs without a safety buffer. Correction: Add a small “grace period” (e.g., + 1 days) to avoid edge?case failures caused by miners manipulating timestamps.
block.timestamp
+ 1 days
Mistake: Allowing the treasury to transfer tokens without a timelock. Correction: Wrap treasury actions in a TimelockController so any large move must be announced and can be vetoed by governance.
TimelockController
Mistake: Setting the vesting duration to 0 (instant unlock) for team allocations. Correction: Enforce a minimum multi?year duration (2?years) to align incentives and satisfy investors.
duration
0
delegatecall
require(msg.sender == admin)
tokensReceived
Scenario: A token contract mints new tokens each block based on rewardPerBlock. If the block time halves after a network upgrade, what happens to the annual inflation rate? Answer: It doubles, because the same per?block reward is emitted twice as often.
Scenario: A DAO proposal tries to change the treasury address, but the call is made through a delegatecall from a malicious contract. Why is this risky? Answer: delegatecall runs the malicious code in the context of the DAO, allowing it to overwrite storage (e.g., treasury address) without proper permission checks.
Scenario: A vesting contract uses require(block.timestamp >= start + cliff) but the cliff is set to 0. What problem arises? Answer: The cliff is effectively disabled, letting beneficiaries claim immediately and defeating the purpose of a delayed release.
require(block.timestamp >= start + cliff)
cliff
tx.origin
unchecked {}
i++
vested = totalAllocated * (now - start) / duration
[0,totalAllocated]
blockNumber = block.number
pragma solidity ^0.8.20
nonReentrant
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.