Solidity Smart Contract Development

FREE
intermediatev1.0.0tokenshrink-v2
# Solidity SC Development

## Solidity Language Fundamentals

Solidity is a statically-typed, contract-oriented language targeting the EVM. Every SC is deployed at an address and contains state variables (stored on-chain), functions (logic), events (indexed logs), and MRs (reusable preconditions).

Value types: `uint256` (most common, default for integers — always use explicitly sized types), `int256`, `address` (20 bytes, use `address payable` for ETH transfers), `bool`, `bytes32` (cheaper than `string` for fixed-length data). Reference types: arrays, mappings, structs. Mappings cannot be iterated — if you need iteration, maintain a parallel array.

Visibility: `public` (external + internal, auto-generates getter), `external` (only callable from outside — cheaper than public for large calldata), `internal` (this contract + derived), `private` (this contract only — NOT truly private, data is readable on-chain).

State mutability: `view` (reads state, no gas when called externally), `pure` (no state access), `payable` (can receive ETH). Unmarked functions can read and write state.

Key patterns:
```solidity
// MR pattern — reusable preconditions
modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;
}

// Event emission — always emit after state changes
event Transfer(address indexed from, address indexed to, uint256 value);

// Error handling — custom errors save gas vs require strings
error InsufficientBalance(uint256 requested, uint256 available);
if (balance < amount) revert InsufficientBalance(amount, balance);
```

Custom errors (Solidity 0.8.4+) save significant gas compared to `require` with string messages. A `require` with a 32-character string costs ~500 more gas than a custom error `revert`.

## OZ Patterns & Libraries

OZ provides audited, battle-tested SC components. Always inherit rather than writing from scratch.

**AC patterns**: `Ownable` for single-owner (simple but centralized). `AccessControl` for role-based (define roles like MINTER_ROLE, PAUSER_ROLE). `AccessControlDefaultAdminRules` adds two-step admin transfer with delay — use this for production.

**Token standards**: `ERC20` (fungible tokens — include `ERC20Permit` for gasless approvals via EIP-2612), `ERC721` (NFTs — use `ERC721Enumerable` only if on-chain enumeration needed, it's expensive), `ERC1155` (multi-token — single contract for fungible + non-fungible, batch transfers save gas).

**Security utilities**: `ReentrancyGuard` (add `nonReentrant` MR to all external functions that change state or transfer value), `Pausable` (emergency stop pattern — include in every production SC), `Address.sendValue` (safe ETH transfer avoiding the 2300 gas stipend issue).

**Governance**: `Governor` + `TimelockController` for DAO governance. `Governor` handles proposal creation and voting. `TimelockController` enforces delay between vote passage and execution. Always use timelock for treasury operations.

Import convention: `import "@openzeppelin/contracts/token/ERC20/ERC20.sol";` — pin OZ version in package.json. Never use `@latest`.

## GO Techniques

Gas is the execution cost on EVM. Every operation has a fixed gas cost. Optimization reduces TX cost for users.

**SLT optimization**: storage is the most expensive operation. `SSTORE` (write) costs 20,000 gas for new slot, 5,000 for update. `SLOAD` (read) costs 2,100 gas (cold) or 100 gas (warm). Pack variables into single 256-bit slots:
```solidity
// Bad: 3 storage slots (60,000 gas to initialize)
uint256 amount;    // slot 0
uint128 timestamp; // slot 1
bool active;       // slot 2

// Good: 2 storage slots (40,000 gas to initialize)
uint128 timestamp; // slot 0 (16 bytes)
bool active;       // slot 0 (1 byte, packed)
uint256 amount;    // slot 1
```

**Memory vs calldata**: use `calldata` for external function parameters that aren't modified — avoids copying to memory. Use `memory` only when you need to modify the data.

**Loop optimization**: cache array length (`uint256 len = arr.length`), use `unchecked { ++i }` for loop counters (safe in Solidity 0.8+ since counter can't realistically overflow), avoid storage reads inside loops (cache to memory variable).

**Short-circuiting**: in `require(a && b)`, put the cheaper check first. If `a` fails, `b` is never evaluated.

**Constants and immutables**: `constant` for compile-time values (costs 0 gas to read — inlined into bytecode). `immutable` for constructor-set values (much cheaper than regular storage reads).

**Mapping vs array**: mappings have O(1) lookup with no length tracking overhead. Use mappings as default DS. Only use arrays when you need enumeration or ordering.

## RE Prevention & Security

RE is the most dangerous SC vulnerability. It occurs when an external call re-enters the calling contract before state updates complete.

The CEI pattern is the primary defense:
```solidity
function withdraw(uint256 amount) external nonReentrant {
    // CHECKS — validate inputs and conditions
    require(balances[msg.sender] >= amount, "Insufficient");
    
    // EFFECTS — update state BEFORE external call
    balances[msg.sender] -= amount;
    
    // INTERACTIONS — external call LAST
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}
```

Always combine CEI with OZ `ReentrancyGuard` (`nonReentrant` MR) as defense in depth. Cross-contract RE (read-only RE) occurs when contract A calls contract B, which calls back into a view function on A that reads stale state. Guard against this with mutex locks or by ensuring view functions reflect pending state changes.

FL attack prevention: validate that critical price feeds or balance ratios haven't been manipulated within the same TX. Use time-weighted average prices (TWAPs) instead of spot prices. FL attacks exploit atomic composability — anything that reads instantaneous on-chain state is vulnerable.

MEV awareness: TX ordering is controlled by block builders. Front-running (seeing your TX and inserting one before it) and sandwich attacks (buying before your trade, selling after) are common. Mitigation: use commit-reveal schemes, private mempools (Flashbots Protect), or slippage limits.

## Proxy Patterns for UR SCs

SCs are immutable once deployed. Proxy patterns enable upgradability by separating logic from storage.

**TP (OZ TransparentUpgradeableProxy)**: admin calls go to proxy (upgrade functions), user calls are delegated to implementation via CAL. Simple but has gas overhead — every call checks if caller is admin. ProxyAdmin contract manages upgrades. Use for established projects with infrequent upgrades.

**UUPS (EIP-1822)**: upgrade logic lives in the implementation contract, not the proxy. Lighter proxy (cheaper deployment and per-call gas). The implementation must include `_authorizeUpgrade` function with AC. Risk: if you deploy an implementation without upgrade logic, it's permanently frozen.

SLT compatibility is critical for UR SCs: never change the order or type of existing state variables in upgrades. Only append new variables at the end. Never remove variables — add unused spacers if needed. OZ provides `@openzeppelin/contracts-upgradeable` with initializers instead of constructors.

**Initializer pattern**: UR SCs cannot use constructors (constructor runs in implementation context, not proxy). Use `initialize` function with OZ `initializer` MR that ensures it runs exactly once:
```solidity
function initialize(address admin) public initializer {
    __Ownable_init(admin);
    __ERC20_init("Token", "TKN");
    __UUPSUpgradeable_init();
}
```

Storage gaps: in base contracts of UR systems, reserve storage slots for future variables: `uint256[50] private __gap;` Each upgrade can use gap slots for new variables without breaking derived contract SLT.

## ERC Standards Implementation

**ERC-20 essentials**: implement `transfer`, `approve`, `transferFrom`, `balanceOf`, `totalSupply`, `allowance`. Add `ERC20Permit` for gasless approvals (users sign a message, anyone can submit the TX). Consider `ERC20Votes` if governance is planned — tracks historical balances for snapshot-based voting.

**ERC-721 (NFTs)**: `tokenURI` returns metadata JSON (typically IPFS URI). Use `_safeMint` (checks receiver can handle NFTs) not `_mint`. `ERC721URIStorage` for per-token URIs, `ERC721Royalty` (EIP-2981) for creator royalties. Batch minting: ERC721A from Azuki saves ~50% gas for bulk mints by lazy-initializing ownership data.

**ERC-1155 (multi-token)**: single contract manages multiple token types. `balanceOf(address, id)` replaces separate contracts. Batch operations (`safeBatchTransferFrom`) save gas. `uri(id)` returns metadata — use `{id}` substitution pattern per spec. Ideal for gaming (items, currencies, land in one contract).

## HH Testing & Deployment

HH is the standard SC development environment. Project structure: `contracts/`, `test/`, `scripts/`, `hardhat.config.ts`.

Testing essentials:
```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { loadFixture } = require("@nomicfoundation/hardhat-network-helpers");

async function deployFixture() {
    const [owner, user] = await ethers.getSigners();
    const Token = await ethers.getContractFactory("Token");
    const token = await Token.deploy();
    return { token, owner, user };
}

it("should revert on insufficient balance", async () => {
    const { token, user } = await loadFixture(deployFixture);
    await expect(token.connect(user).transfer(owner, 100))
        .to.be.revertedWithCustomError(token, "InsufficientBalance");
});
```

Use `loadFixture` for test isolation — snapshots EVM state and reverts between tests (faster than redeployment). Test RE explicitly: create an attacker contract that calls back during `receive()`. Test AC: verify that unauthorized callers get reverted with correct error. Test edge cases: zero amounts, max uint256, address(0).

Gas reporting: enable `hardhat-gas-reporter` plugin. Compare gas costs before and after GO changes. Set gas price assumptions in config for cost estimation.

Deployment: use HH Ignition (declarative deployment module) or custom scripts. Always verify on block explorer: `npx hardhat verify --network mainnet CONTRACT_ADDRESS constructor_args`. Deploy to testnet first (Sepolia), run full test suite against testnet deployment, then mainnet. Use multi-sig (Safe) as owner for mainnet deployments — never EOA.

## Security Audit Preparation

Pre-audit checklist: complete NM documentation for every public and external function. Run Slither (static analysis) and fix all high/medium findings before engaging auditors. Run Mythril (symbolic execution) for deeper vulnerability detection. Achieve 100% line coverage and 90%+ branch coverage in tests. Document all design decisions and known tradeoffs in a separate document for auditors.

Common vulnerability classes beyond RE:
- **Integer overflow/underflow**: Solidity 0.8+ has built-in overflow checks. But `unchecked` blocks bypass this — only use for provably safe arithmetic (loop counters, known-bounded calculations).
- **Access control**: every state-changing function must have explicit AC. Missing AC on `initialize` in UR SCs is a critical vulnerability — anyone can call it first.
- **Oracle manipulation**: price oracles using spot prices from DEXs are vulnerable to FL manipulation. Always use TWAPs (Uniswap V3 observation, Chainlink feeds). Chainlink feeds can go stale — check `updatedAt` timestamp and revert if data is older than the heartbeat.
- **Denial of service**: unbounded loops over user-controlled arrays can exceed block gas limit. Implement pagination or limit array sizes. `transfer` and `send` forward only 2300 gas — if the recipient has a complex `receive` function, the TX fails. Use `call` with explicit gas limits.
- **Front-running**: any TX that depends on ordering can be front-run. Use commit-reveal for auctions, Flashbots for sensitive TXs. Slippage limits on DEX trades protect against sandwich attacks.
- **Signature replay**: signatures used for meta-TXs must include nonce and chain ID to prevent replay across TXs or chains. EIP-712 provides typed structured data hashing that includes domain separator with chain ID and contract address.

Gas estimation: use HH gas reporter to benchmark every function. Compare against block gas limit (30M on Ethereum mainnet). Functions approaching 50% of block gas limit are risky — may fail during network congestion. Batch operations should have configurable batch sizes to stay within gas limits.

## DeFi SC Patterns

AMM (Automated Market Maker): constant product formula `x * y = k`. Adding liquidity: deposit both tokens in current ratio, receive LP tokens. Removing liquidity: burn LP tokens, receive proportional share. Impermanent loss occurs when token prices diverge — provide examples: 2x price change = 5.7% IL, 5x = 25.5% IL.

Lending protocol patterns: supply assets to earn yield, borrow against collateral. Health factor = (collateral value * liquidation threshold) / borrow value. When health factor drops below 1.0, position is liquidatable. Liquidation bonus (5-15%) incentivizes liquidators. Interest rate models: typically utilization-based — low utilization = low rates (encourage borrowing), high utilization = high rates (encourage repayment and deposits).

Vault patterns (ERC-4626): standardized tokenized vault interface. Deposit underlying token, receive share tokens. `convertToAssets` and `convertToShares` handle the exchange rate. `maxDeposit`, `maxWithdraw` enforce limits. Implements hooks for yield strategy integration. Always round in favor of the vault (against the user) to prevent rounding exploits on deposit/withdraw.

Staking patterns: users deposit tokens, earn rewards over time. Track rewards per share using the "masterchef" pattern: `accRewardPerShare += newRewards / totalStaked`. User pending rewards = `(userAmount * accRewardPerShare) - userRewardDebt`. Update `rewardDebt` on every deposit/withdraw. This avoids iterating over all stakers — O(1) per operation.

## Deployment & Operations

Multi-chain deployment: use `CREATE2` for deterministic addresses across chains (same address on Ethereum, Arbitrum, Base, etc.). Deploy factory contract first, then deploy through factory. HH Ignition supports multi-chain deployment workflows. Verify contracts on each chain's block explorer.

Monitoring post-deployment: use Tenderly or OpenZeppelin Defender for TX monitoring. Set alerts for: large token transfers, AC role changes, proxy upgrades, unusual gas consumption patterns, and contract balance changes. Monitor governance proposals and timelock queued TXs.

Emergency procedures: every production SC should have a circuit breaker (OZ Pausable). Define clear criteria for when to pause (exploit detected, oracle failure, governance attack). Multi-sig (Safe) should control pause function — require 2-of-3 minimum. Document the emergency response runbook: who can pause, who must be notified, what's the assessment process, and who authorizes unpause.

2.7K

tokens

13.0%

savings

Downloads0
Sign in to DownloadCompressed by TokenShrink