Blockchain Development Reference
This guide is designed to help you navigate the blockchain development ecosystem used in the AVAX Lotto project. It is organized from language fundamentals to tooling and workflows.
Stack Overview
- Language: Solidity (Smart Contracts) & TypeScript (Tests/Scripts)
- Framework: Hardhat (Development Environment)
- Interaction: Viem (Lightweight library to talk to the blockchain)
- Deployment: Hardhat Ignition
Part 1: Solidity Essentials (The Language)
Solidity is the object-oriented language for writing smart contracts.
1.1. Core Data Types
bool:trueorfalse.uint/int: Unsigned (positive only) and signed integers.uint256is the standard (aliasuint).- Note: No decimals/floats! Use "Wei" (18 decimals) for currency.
address: Holds a 20-byte Ethereum address.address payable: Can receive Ether via.transfer()or.send().
bytes/string: Arbitrary data or text.- Data Structures:
struct: Custom objects (e.g.,struct User { uint id; uint balance; }).enum: Restricted set of values (e.g.,enum Status { Pending, Active, Done }).
1.2. Storage vs. Memory vs. Calldata (Important!)
Unlike standard programming, you must specify where data is stored.
storage: Permanent data on the blockchain. State variables arestorageby default. Very expensive (Gas cost).memory: Temporary data during function execution. Erased after function ends. Cheaper.calldata: Read-only location for function arguments. Cheapest.- Tip: Use
calldatafor external function arguments (e.g.,function verify(string calldata _proof)).
- Tip: Use
1.3. Mappings vs. Arrays
mapping(address => uint): Key-value store. O(1) access.- Pros: Fast, cheap.
- Cons: Cannot iterate (loop) through keys. You can't say "get all users".
address[]: Array.- Pros: Can iterate. Has
.length. - Cons: Expensive to resize or search if large. Avoid large loops in transactions!
- Pros: Can iterate. Has
1.4. Global Variables (The Blockchain Context)
Variables automatically available in every function.
msg.sender: The address calling the function. Crucial for access control.- In Constructor: It's the deployer (owner).
msg.value: Amount of Wei (Ether) sent with the transaction.block.timestamp: Current block time (in seconds).tx.origin: The original sender. Avoid using this for security checks; usemsg.sender.
1.5. Functions & Visibility
Control who can call your functions.
public: Callable by anyone (inside or outside).external: Callable only from outside. More gas-efficient for large arguments.internal: Callable only by this contract or inheriting contracts.private: Callable only by this contract (not even inheritors).
1.6. Modifiers
Keywords that change function behavior.
- Behavioral:
view: Reads state but doesn't change it. Free to call off-chain.pure: Doesn't even read state (e.g., math helpers). Free to call off-chain.payable: Allows function to accept Ether.
- Custom Modifiers: Reusable checks (e.g.,
onlyOwner).- The
_;Wildcard: Represents the body of the function being modified.
soliditymodifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; // <--- The function code executes here! } - The
1.7. Events (indexed)
Logs stored on the blockchain, cheaper than storage. Used by frontends to update UIs.
event WinnerPicked(address indexed winner, uint amount);indexed: Adds the parameter to a special search structure (Bloom filter), allowing efficient filtering (e.g., "Find all wins by User X"). Limit: 3 indexed parameters.
1.8. Error Handling
require(condition, "Message"): If false, reverts transaction and refunds gas. Used for inputs/validation.revert CustomError(): Gas-efficient way to stop execution (Solidity 0.8+).assert(condition): For internal errors that "should never happen". Consumes all gas. Avoid.
1.9. Special Functions
receive() external payable: Runs when contract receives ETH with no data.fallback() external payable: Runs when contract receives ETH with data (or function doesn't exist).
Part 2: Working with Tokens & Libraries
2.1. Native Currency vs. ERC-20
- Native Currency (AVAX/ETH): Built into the protocol.
- Sent via:
msg.value(in) andaddr.call{value: x}("")(out). - No imports required.
- Sent via:
- ERC-20 Tokens (USDC, LINK, UNI): These are smart contracts themselves, not protocol-level currencies.
- To interact with them, your contract needs to "speak" their language.
- The Interface (
IERC20): Defines the standard functions (transfer,balanceOf,approve). Without this, Solidity doesn't know these functions exist on the target address. - Why use it? If you wanted your Lotto to accept USDC instead of AVAX, you would need
IERC20to tell the USDC contract to move funds.
2.2. OpenZeppelin (The Industry Standard)
OpenZeppelin provides a library of secure, audited, and battle-tested smart contract components.
- Why use it?
- Security: Avoids common bugs (e.g., reentrancy, ownership flaws).
- Standardization: Ensures your tokens are compatible with wallets/exchanges.
- Common Modules:
Ownable: secure access control (better than writingonlyOwneryourself).ReentrancyGuard: Provides thenonReentrantmodifier.ERC20: Base implementation for creating your own tokens.IERC20: Interface for interacting with other people's tokens.
2.3. How to Use OpenZeppelin
- Install:
npm install @openzeppelin/contracts - Import & Inherit:solidity
import "@openzeppelin/contracts/access/Ownable.sol"; contract MyContract is Ownable { constructor() Ownable(msg.sender) {} // Initialize Ownable function restricted() external onlyOwner { // ... } }
Part 3: Security Best Practices
3.1. Reentrancy
- Concept: Attacker calls your function, and before you update their balance, they call it again recursively to drain funds.
- Fix 1 (Checks-Effects-Interactions): Update internal state (balances) before sending ETH.
- Fix 2 (Reentrancy Guard): Use a
nonReentrantmodifier to lock the function.
3.2. Isolating External Calls
- Pull over Push: Don't send money to 100 people in a loop. If one fails, everyone fails. Instead, record that they are owed money and let them "withdraw" (pull) it themselves.
- Untrusted Contracts: Always assume the address you are calling is malicious.
Part 4: Tooling & Environment (Hardhat)
4.1. Useful Commands
npx hardhat compile: Check for syntax errors.npx hardhat test: Run your test suite.npx hardhat node: Start a local blockchain (simulates Avalanche).
4.2. Configuration (hardhat.config.ts)
Where you define Solidity versions and network connections.
- Networks: To deploy to Fuji (Testnet), you'll add the URL and Private Key (via
vars) here.
Part 5: Testing & Interaction (Viem)
We use Viem (via hardhat-toolbox-viem) to interact with our contracts in tests and scripts.
5.1. Setup (in test/*.ts)
import { network } from "hardhat";
// ... inside a test block
const { viem } = await network.connect();
const myContract = await viem.deployContract("Lotto", [arg1]);5.2. Reading & Writing
- Write (Changes State):
await myContract.write.functionName([args]) - Read (View Only):
await myContract.read.functionName([args])
5.3. Utilities
parseEther("1.0"): Converts "1.0" to1000000000000000000(Wei).formatEther(bigInt): The reverse.getAddress("0x..."): Fixes letter casing (Checksum).
Part 6: Deployment (Ignition)
Deployment scripts are "Modules" in ignition/modules/.
// ignition/modules/Lotto.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("LottoModule", (m) => {
// Deploy dependencies first
const strategy = m.contract("MockYieldStrategy");
// Pass dependency as argument
const lotto = m.contract("Lotto", [strategy]);
return { lotto };
});Run it: npx hardhat ignition deploy ignition/modules/Lotto.ts --network localhost