A complete guide to smart contract development
Smart ContractsIt is an auto-executor deployed on the blockchain that irreversibly executes the operation according to the preset logic once the conditions are met. It is the technical cornerstone of all Web3 applications such as DeFi, NFT, DAO, etc. This article will comprehensively analyze the development technology of smart contracts from zero to one, from Solidity language syntax, development toolchain, design pattern to security audit and mainnet deployment, to provide developers with industrial-grade practical references.
What is a smart contract?
A smart contract is a piece of computer code stored on a blockchain that defines a set of rules (contract terms) that automatically execute actions when preset conditions are triggered – without human intervention and without trusting third-party intermediaries.
Core features of smart contracts
⚡ Automated execution
Once the contract code is deployed on-chain, it automatically runs according to the predetermined logic. Execute immediately after the trigger conditions are met, without relying on any centralized authority approval.
🔒 It cannot be tampered with
The deployed contract code cannot be modified or deleted (unless using the upgradeable agent mode). Anyone can verify the execution logic of the contract.
🌐 Trustless
The two parties do not need to trust each other, just the code logic. Contracts act as impartial "digital arbiters," automatically enforcing agreements.
💎 Certainty
The same input will always produce the same output. The contract execution result is entirely determined by input parameters and on-chain state, eliminating the space for human manipulation.
Smart contracts vs. traditional contracts
| Contrast dimensions | Traditional contracts | Smart Contract |
|---|---|---|
| Execution method | Manual enforcement, relying on legal constraints | Code execution is automated, no manual labor required |
| Trust foundation | Legal system + third-party notarization | Cryptography + blockchain consensus |
| Modify the method | Negotiate and revise, re-sign | Not modifiable (or upgraded through governance) |
| Execution costs | Attorney's fees + notary fees + arbitration fees | Gas fees (usually a few dollars) |
| Execution speed | Days to months | Seconds to minutes |
| Transparency | It is visible only when the contract is signed | Everyone can review and verify |
| Cross-border capabilities | Subject to jurisdiction | Execution without borders worldwide |
Mainstream smart contract platform
| platform | Programming language | Virtual machines | Features: |
|---|---|---|---|
| Ethereum | Solidity / Vyper | EVM | The largest ecosystem, the most developers, and the richest tool chain |
| Solana | Rust / Anchor | SVM (Sealevel) | High TPS (65000+), low cost, parallel execution |
| Polygon | Solidity | EVM compatible | L2 is low-cost and fully compatible with Ethereum |
| Arbitrum | Solidity | EVM compatible | Optimistic Rollup, high security |
| Base | Solidity | EVM compatible | Coinbase-endorsed, fastest-growing L2 |
| TON | FunC / Tact | TON VM | Telegram ecosystem, asynchronous messaging model |
Solidity language foundation
Solidity is the mainstream smart contract programming language for Ethereum and all EVM-compatible chains. It is a statically typed, contract-oriented, high-level language with a syntax similar to JavaScript and C++.
Basic data types
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract DataTypes {
// 值类型
bool public isActive = true;
uint256 public amount = 1000; // 无符号整数(0 ~ 2^256-1)
int256 public temperature = -10; // 有符号整数
address public owner = msg.sender; // 以太坊地址(20字节)
bytes32 public hash; // 固定大小字节数组
// 引用类型
string public name = "NovaLink"; // 动态字符串
uint256[] public numbers; // 动态数组
mapping(address => uint256) public balances; // 映射(键值对)
// 枚举
enum Status { Pending, Active, Closed }
Status public currentStatus = Status.Pending;
// 结构体
struct Order {
uint256 id;
address buyer;
uint256 amount;
Status status;
}
mapping(uint256 => Order) public orders;
}
Function modifiers and visibility
contract FunctionTypes {
address public owner;
uint256 public value;
// 构造函数(部署时执行一次)
constructor() {
owner = msg.sender;
}
// 修饰符:权限控制
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_; // 继续执行被修饰的函数
}
// 可见性:public(任何人可调用)
// 状态可变性:payable(可接收ETH)
function deposit() public payable {
value += msg.value;
}
// 可见性:external(仅外部调用)
// 状态可变性:view(只读状态)
function getBalance() external view returns (uint256) {
return address(this).balance;
}
// 可见性:internal(仅本合约和子合约)
function _validateAmount(uint256 amount) internal pure returns (bool) {
return amount > 0 && amount <= 1000 ether;
}
// 使用修饰符保护的函数
function withdraw() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
}
Events and logs
contract Events {
// 事件声明(indexed 参数可用于过滤)
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function transfer(address to, uint256 amount) external {
// ... 转账逻辑
emit Transfer(msg.sender, to, amount); // 触发事件
}
}
// 前端监听事件:
// contract.on("Transfer", (from, to, value) => { ... });
Error handling
- require(): Verify the input parameters and preconditions, and return the remaining gas if it fails
- revert(): Explicit rollback transactions, support custom errors (Custom Error, more gas-saving)
- assert(): Validate invariant, failure indicates a serious bug
- try/catch: Catches external call failures and prevents the entire transaction from being rolled back
Development environment construction
A professional smart contract development environment requires the cooperation of a compiler, test framework, native blockchain, and deployment tools. The following is a comparison of mainstream toolchains:
Comparison of development frameworks
| Frame | language | Features: | Applicable scenarios |
|---|---|---|---|
| Hardhat | JavaScript/TypeScript | Rich plug-in ecology, excellent debugging experience, and console.log support | Preferred for most projects |
| Foundry | Solidity | Extremely fast compilation, Solidity native testing, and built-in fuzzing | Pursuing performance and in-depth testing |
| Truffle | JavaScript | Established framework, Ganache integration | Legacy project maintenance |
| Brownie | Python (programming language) | Python ecosystem integration | Python team |
Hardhat project initialization
# 创建项目
mkdir my-contract && cd my-contract
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
# 初始化 Hardhat
npx hardhat init
# 选择: Create a TypeScript project
# 项目结构
├── contracts/ # Solidity 合约源码
│ └── Lock.sol
├── scripts/ # 部署脚本
│ └── deploy.ts
├── test/ # 测试文件
│ └── Lock.ts
├── hardhat.config.ts # 配置文件
└── package.json
Hardhat configuration example
// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "dotenv/config";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: { enabled: true, runs: 200 },
viaIR: true, // 启用 IR 编译优化
},
},
networks: {
hardhat: {
forking: {
url: process.env.MAINNET_RPC_URL!, // Fork主网状态
blockNumber: 19000000,
},
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
},
polygon: {
url: process.env.POLYGON_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
gasReporter: {
enabled: true,
currency: "USD",
},
};
export default config;
Foundry project initialization
# 安装 Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# 创建项目
forge init my-contract && cd my-contract
# 项目结构
├── src/ # 合约源码
├── test/ # Solidity 测试
├── script/ # 部署脚本
├── lib/ # 依赖库(git submodule)
└── foundry.toml # 配置文件
# 安装依赖
forge install OpenZeppelin/openzeppelin-contracts
# 编译
forge build
# 测试(含 Gas 报告)
forge test -vvv --gas-report
# 模糊测试 100万次
forge test --fuzz-runs 1000000
Contract structure and life cycle
Understanding the full lifecycle of a contract is fundamental to writing high-quality code. From writing, compiling, deploying, to interacting, each stage has its technical requirements.
Contract lifecycle
Writing and compiling
Write contract code using Solidity, which compiles into EVM bytecode and ABI (Application Binary Interface) via solc.
Deploy on-chain
Bytecode is sent to the blockchain through transactions, and miners/validators execute constructors and assign contract addresses. Deployment consumes gas.
Interactive
Users call contract functions through transactions. Read operations (view/pure) are free, and write operations consume gas.
Upgrade or destroy
Proxy mode allows logical upgrades; selfdestruct destroyable contract (soon to be scrapped).
Full contract template
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
/// @title 完整合约模板
/// @author NovaLinkR
/// @notice 演示一个具备标准安全功能的合约结构
contract ContractTemplate is Ownable, ReentrancyGuard, Pausable {
// ============ 状态变量 ============
uint256 public totalDeposits;
mapping(address => uint256) public userBalances;
// ============ 事件 ============
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
// ============ 自定义错误(比 require string 更省 Gas) ============
error InsufficientBalance(uint256 requested, uint256 available);
error ZeroAmount();
// ============ 构造函数 ============
constructor() Ownable(msg.sender) {}
// ============ 外部函数 ============
/// @notice 存款
function deposit() external payable whenNotPaused {
if (msg.value == 0) revert ZeroAmount();
userBalances[msg.sender] += msg.value;
totalDeposits += msg.value;
emit Deposited(msg.sender, msg.value);
}
/// @notice 提款
function withdraw(uint256 amount) external nonReentrant whenNotPaused {
uint256 balance = userBalances[msg.sender];
if (amount > balance) revert InsufficientBalance(amount, balance);
// Checks-Effects-Interactions 模式
userBalances[msg.sender] -= amount; // Effects(先修改状态)
totalDeposits -= amount;
(bool success, ) = msg.sender.call{value: amount}(""); // Interactions
require(success, "Transfer failed");
emit Withdrawn(msg.sender, amount);
}
// ============ 管理函数 ============
function pause() external onlyOwner { _pause(); }
function unpause() external onlyOwner { _unpause(); }
// ============ View 函数 ============
function getContractBalance() external view returns (uint256) {
return address(this).balance;
}
// ============ Receive/Fallback ============
receive() external payable {
userBalances[msg.sender] += msg.value;
totalDeposits += msg.value;
}
}
ERC Standard Contract
ERC (Ethereum Request for Comments) is a token interface standard developed by the Ethereum community. Following standards ensures that tokens are interoperable across different applications.
List of mainstream ERC standards
| Standard | Type | Uses: | Typical case |
|---|---|---|---|
| ERC-20 | Fungible tokens | Cryptocurrencies, stablecoins, governance tokens | USDT, UNI, LINK |
| ERC-721 | Non-fungible tokens | Digital art, game props, domain names | BAYC, CryptoPunks |
| ERC-1155 | Multi-token standard | Game assets (including both FT and NFT) | Enjin Ecosystem |
| ERC-4626 | Tokenized vaults | DeFi yield aggregator, staking pool | Yearn Vaults |
| ERC-2981 | NFT royalties | Secondary transaction automatic royalty distribution | OpenSea standard |
| ERC-4337 | Account abstraction | Smart contract wallet, gas-free transactions | Safe, ZeroDev |
ERC-20 token contract implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/// @title 带铸造、销毁、Permit功能的ERC-20代币
contract NovaToken is ERC20, ERC20Burnable, ERC20Permit, Ownable {
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
constructor() ERC20("Nova Token", "NOVA") ERC20Permit("Nova Token") Ownable(msg.sender) {
// 初始铸造 30% 给部署者
_mint(msg.sender, 300_000_000 * 1e18);
}
/// @notice 仅限owner铸造(用于后续释放)
function mint(address to, uint256 amount) external onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
_mint(to, amount);
}
}
ERC-4626 tokenized vaults
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
/// @title 收益金库(DeFi核心组件)
/// @notice 用户存入底层资产,获得份额代币,份额随收益增值
contract YieldVault is ERC4626 {
constructor(IERC20 asset_)
ERC4626(asset_)
ERC20("Nova Yield Vault", "nvUSDC")
{}
// 金库的总资产 = 合约持有的底层代币 + 外部协议中的收益
function totalAssets() public view override returns (uint256) {
return IERC20(asset()).balanceOf(address(this));
// 实际项目中还需加上在 Aave/Compound 等协议中的存款
}
}
Design patterns and architectures
Mature smart contract projects need to use proven design patterns to ensure the security, maintainability, and scalability of the code.
Core design patterns
🛡️ Checks-Effects-Interactions (CEI)
Check the conditions first, then modify the state, and finally execute the external call. The first line of defense against reentrancy attacks, all functions involving transfers must be followed.
🏭 Factory mode
Create subcontract instances in bulk through factory contracts. For example, Uniswap Factory creates trading pairs, NFT Launchpad creates collections.
🔄 Proxy mode
Separate the contract logic from the storage and call it through the delegatecall proxy. Supports upgrading contract logic without changing addresses.
📡 Oracle oracle pattern
Bringing off-chain data on-chain through oracles like Chainlink. Price feeds, random numbers, API data, etc.
🗳️ Governor governance model
OpenZeppelin Governor implements a complete DAO governance process for on-chain proposals, voting, and execution.
🌳 Merkle Tree validation mode
Compress large amounts of data onto a single root hash storage chain, providing Merkle Proof to verify individual data on demand. Standard scheme for whitelisting and airdrop.
Factory model implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/Clones.sol";
/// @title 最小代理工厂(EIP-1167 Clone)
/// @notice 以极低Gas创建合约实例(比常规部署节省90%+)
contract TokenFactory {
using Clones for address;
address public implementation; // 模板合约
address[] public deployedTokens;
event TokenCreated(address indexed token, address indexed creator, string name);
constructor(address _implementation) {
implementation = _implementation;
}
/// @notice 创建新代币(Clone方式,极低Gas)
function createToken(
string memory name,
string memory symbol,
uint256 initialSupply
) external returns (address) {
address clone = implementation.clone();
// 初始化 clone 实例
IToken(clone).initialize(name, symbol, initialSupply, msg.sender);
deployedTokens.push(clone);
emit TokenCreated(clone, msg.sender, name);
return clone;
}
function getDeployedCount() external view returns (uint256) {
return deployedTokens.length;
}
}
DeFi contract development
DeFi (decentralized finance) is the most complex and valuable application scenario for smart contracts. The following shows the implementation ideas of the core DeFi module.
DeFi Core Modules
| module | Function | Representative project | Core mechanism |
|---|---|---|---|
| DEX (Decentralized Exchange) | Token exchange | Uniswap, Curve | Constant product AMM (x*y=k) |
| Lending agreement | Deposit and borrow assets to earn interest | Aave, Compound | Overcollateralization + interest rate model |
| Staking agreement | Lock up to get rewards | Lido, Rocket Pool | Liquid Staking derivatives |
| Income aggregator | Automate your earnings strategy | Yearn, Beefy | ERC-4626 Treasury + Strategy Contract |
| Perpetual Contract | Leverage trading | GMX, dYdX | Virtual AMM + Funding Rate |
| Stablecoins | Price anchoring | MakerDAO, FRAX | CDP overcollateralization / algorithmic regulation |
Simple AMM DEX contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
/// @title 恒定乘积AMM(简化版 Uniswap V2)
contract SimpleAMM is ERC20 {
IERC20 public immutable tokenA;
IERC20 public immutable tokenB;
uint256 public reserveA;
uint256 public reserveB;
uint256 public constant FEE_BPS = 30; // 0.3% 手续费
event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB, uint256 liquidity);
event Swapped(address indexed user, address tokenIn, uint256 amountIn, uint256 amountOut);
constructor(address _tokenA, address _tokenB) ERC20("AMM-LP", "ALP") {
tokenA = IERC20(_tokenA);
tokenB = IERC20(_tokenB);
}
/// @notice 添加流动性
function addLiquidity(uint256 amountA, uint256 amountB) external returns (uint256 liquidity) {
tokenA.transferFrom(msg.sender, address(this), amountA);
tokenB.transferFrom(msg.sender, address(this), amountB);
if (totalSupply() == 0) {
liquidity = sqrt(amountA * amountB);
} else {
liquidity = min(
(amountA * totalSupply()) / reserveA,
(amountB * totalSupply()) / reserveB
);
}
require(liquidity > 0, "Insufficient liquidity");
_mint(msg.sender, liquidity);
reserveA += amountA;
reserveB += amountB;
emit LiquidityAdded(msg.sender, amountA, amountB, liquidity);
}
/// @notice Token A → Token B 兑换
function swapAForB(uint256 amountIn) external returns (uint256 amountOut) {
require(amountIn > 0, "Zero input");
uint256 amountInWithFee = amountIn * (10000 - FEE_BPS) / 10000;
// 恒定乘积公式:x * y = k
amountOut = (reserveB * amountInWithFee) / (reserveA + amountInWithFee);
tokenA.transferFrom(msg.sender, address(this), amountIn);
tokenB.transfer(msg.sender, amountOut);
reserveA += amountIn;
reserveB -= amountOut;
emit Swapped(msg.sender, address(tokenA), amountIn, amountOut);
}
function sqrt(uint256 x) internal pure returns (uint256) {
uint256 z = (x + 1) / 2;
uint256 y = x;
while (z < y) { y = z; z = (x / z + z) / 2; }
return y;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
Staking mining contracts
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/// @title 单币质押挖矿(Synthetix StakingRewards 模型)
contract StakingRewards is ReentrancyGuard {
IERC20 public immutable stakingToken;
IERC20 public immutable rewardToken;
uint256 public rewardRate; // 每秒奖励数量
uint256 public lastUpdateTime;
uint256 public rewardPerTokenStored;
uint256 public totalStaked;
mapping(address => uint256) public userStaked;
mapping(address => uint256) public userRewardPerTokenPaid;
mapping(address => uint256) public rewards;
constructor(address _stakingToken, address _rewardToken, uint256 _rewardRate) {
stakingToken = IERC20(_stakingToken);
rewardToken = IERC20(_rewardToken);
rewardRate = _rewardRate;
}
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
lastUpdateTime = block.timestamp;
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) return rewardPerTokenStored;
return rewardPerTokenStored +
((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / totalStaked;
}
function earned(address account) public view returns (uint256) {
return (userStaked[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18
+ rewards[account];
}
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "Cannot stake 0");
stakingToken.transferFrom(msg.sender, address(this), amount);
userStaked[msg.sender] += amount;
totalStaked += amount;
}
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount <= userStaked[msg.sender], "Exceeds staked");
userStaked[msg.sender] -= amount;
totalStaked -= amount;
stakingToken.transfer(msg.sender, amount);
}
function claimReward() external nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardToken.transfer(msg.sender, reward);
}
}
}
Gas optimization tips
Gas fees directly impact user experience and contract competitiveness. Here are proven optimization strategies that can significantly reduce interaction costs.
Overview of optimization strategies
| Optimize the direction | Tricks | Savings |
|---|---|---|
| Storage optimization | Struct packing: Continuously declare small types | 30-50% |
| Storage optimization | Use mapping instead of array (avoid traversal) | Significant |
| Computational optimization | Custom Error replaces require string | ~200 gas/call |
| Computational optimization | Use unchecked{} to prevent packages from overflowing | ~80 Gas/operation |
| Call optimization | external alternative to public(calldata vs memory) | ~600 Gas |
| Call optimization | Batch operations (multiple transactions processed at a time) | 50-90% |
| Deployment optimization | EIP-1167 Minimal Proxy (Clone) | 90%+ deployment cost |
| Deployment optimization | Enable Optimizer + viaIR compilation | 10-30% |
Variable packaging example
// ❌ 差:每个变量占一个32字节存储槽(3个槽 = 60000 Gas写入)
contract Unoptimized {
uint256 public amount; // Slot 0
bool public isActive; // Slot 1(浪费31字节)
address public owner; // Slot 2
}
// ✅ 好:紧凑打包到更少的存储槽(2个槽 = 40000 Gas写入)
contract Optimized {
uint256 public amount; // Slot 0(占满32字节)
address public owner; // Slot 1(20字节)
bool public isActive; // Slot 1(+1字节,同一槽)
}
// ✅ 极致优化:位操作打包多个小变量
contract UltraOptimized {
// 将多个状态打包到一个 uint256
// bits 0-7: status (uint8)
// bits 8-39: timestamp (uint32, 可到2106年)
// bits 40-199: address (uint160)
// bits 200-255: amount (uint56, 足够大多数场景)
uint256 private _packedData;
}
Batch operation optimization
// ❌ 差:逐笔空投(N次独立交易,每次21000+ Gas基础费)
// 100人空投 ≈ 100 * 65000 = 6,500,000 Gas
// ✅ 好:批量空投(1次交易,共享基础费)
// 100人空投 ≈ 21000 + 100 * 35000 = 3,521,000 Gas(节省46%)
function batchTransfer(
address[] calldata recipients, // calldata 比 memory 省 Gas
uint256[] calldata amounts
) external {
require(recipients.length == amounts.length, "Length mismatch");
for (uint256 i; i < recipients.length; ) {
_transfer(msg.sender, recipients[i], amounts[i]);
unchecked { ++i; } // 不可能溢出,省检查
}
}
Upgradeable contracts
Once a smart contract is deployed, it cannot be modified. But with the Proxy Pattern, we can upgrade the contract logic while keeping the address and state the same.
Proxy model comparison
| mode | Principle | Pros: | Cons: |
|---|---|---|---|
| Transparent Proxy | Admin calls use proxy logic, and user calls go through implementation | Simple, clear and widely used | Address check more than once per call |
| UUPS | The upgrade function is written in the implementation contract | Save gas and be lighter | If you forget to write the upgrade function, it will be permanently locked |
| Beacon Proxy | Multiple agents share a Beacon to obtain the implementation address | A single upgrade affects all instances | The architecture is more complex |
| Diamond (EIP-2535) | Multiple logical contracts (Facets) are routed through function selectors | Break through contract size limitations and modularity | Extremely complex |
UUPS can be upgraded to contract implementation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/// @title V1 版本(初始部署)
contract MyContractV1 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint256 public value;
/// @notice 替代构造函数(代理模式不使用 constructor)
function initialize() public initializer {
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
}
function setValue(uint256 _value) external {
value = _value;
}
/// @notice 授权升级(仅 owner 可升级)
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}
/// @title V2 版本(升级后)
contract MyContractV2 is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint256 public value;
uint256 public newFeature; // 新增状态变量(只能追加,不能修改已有布局)
function initialize() public initializer {
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
}
function setValue(uint256 _value) external {
value = _value;
}
// V2 新增功能
function setNewFeature(uint256 _feature) external {
newFeature = _feature;
}
function getVersion() external pure returns (string memory) {
return "v2.0.0";
}
function _authorizeUpgrade(address) internal override onlyOwner {}
}
Security and vulnerability protection
Smart contracts directly manage funds, and security breaches can lead to millions of dollars in irreversible losses. Here are the most common types of attacks and their protections.
Top 10 Contract Security Risks
🔁 Reentrancy
The attacker recursively reentrants the contract in an external call. Protection: ReentrancyGuard + CEI mode + restrict gas with transfer/send.
⚡ Flash loan manipulation
Use flash loans to borrow large amounts of funds to manipulate price oracles. Protection: Use TWAP oracle, multi-source price validation.
🔢 Integer overflow/underflow
Solidity 0.8+ has built-in checks. Lower versions require SafeMath. Note manual validation in the unchecked block.
🎭 tx.origin fishing
The malicious contract tricks tx.origin authentication through an intermediate call. Protection: Always use msg.sender instead of tx.origin.
🕳️ Delegatecall injection
delegatecall to a malicious contract modifies the caller's storage. Protection: DelegateCall only to trusted audited contracts.
⏰ Timestamp dependencies
Miners can manipulate block.timestamp slightly. Protection: No timestamps for key randomness sources, using Chainlink VRF.
Safety checklist
- External calls: Check the return value after all external calls; Use SafeERC20 to handle non-standard tokens
- Permission control: Add onlyOwner/AccessControl to the key function; The initializer modifier is added to the initializer modifier
- Enter verification: Validate all parameter ranges; Check that the address is non-zero; Verify array length
- State consistency: Use invariant checks to ensure that the contract state is always legitimate
- Emergency mechanisms: Implement Pausable pause; Set up an emergency withdrawal function
- Front-end protection: EIP-712 Structured Signatures; Clearly display the signature content to prevent blind signing
Test & Debug
The unmodifiable nature of contract code determines that testing is far more important than traditional software. 100% test coverage is the industry minimum.
Test type
| Type | Tools | Purpose | Coverage requirements |
|---|---|---|---|
| Unit Testing | Hardhat/Foundry | The boundary conditions of each function vs. the normal path | 100% |
| Integration testing | Hardhat Fork | Contract-to-contract interaction and integration with deployed protocols | Core path |
| Fuzzing | Foundry Fuzz | Random input detects unexpected behavior | 1 million+ times |
| Invariant testing | Foundry Invariant | Validation system invariants hold forever | Key attributes |
| Formal verification | Certora / Halmos | Mathematical proof of contract correctness | High-value logic |
| Gas benchmarking | Foundry --gas-report | Monitor gas changes to prevent performance degradation | All public functions |
Hardhat unit test example
// test/StakingRewards.test.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers";
describe("StakingRewards", function () {
async function deployFixture() {
const [owner, user1, user2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MockERC20");
const stakingToken = await Token.deploy("Staking", "STK", ethers.parseEther("1000000"));
const rewardToken = await Token.deploy("Reward", "RWD", ethers.parseEther("1000000"));
const Staking = await ethers.getContractFactory("StakingRewards");
const staking = await Staking.deploy(
await stakingToken.getAddress(),
await rewardToken.getAddress(),
ethers.parseEther("1") // 1 token/sec
);
// 分发代币
await stakingToken.transfer(user1.address, ethers.parseEther("10000"));
await rewardToken.transfer(await staking.getAddress(), ethers.parseEther("100000"));
return { staking, stakingToken, rewardToken, owner, user1, user2 };
}
it("should accumulate rewards over time", async function () {
const { staking, stakingToken, user1 } = await loadFixture(deployFixture);
// 质押
await stakingToken.connect(user1).approve(await staking.getAddress(), ethers.parseEther("1000"));
await staking.connect(user1).stake(ethers.parseEther("1000"));
// 前进 100 秒
await time.increase(100);
// 验证奖励累积
const earned = await staking.earned(user1.address);
expect(earned).to.be.closeTo(ethers.parseEther("100"), ethers.parseEther("1"));
});
it("should revert on zero stake", async function () {
const { staking, user1 } = await loadFixture(deployFixture);
await expect(staking.connect(user1).stake(0)).to.be.revertedWith("Cannot stake 0");
});
});
Foundry fuzzing example
// test/StakingRewards.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/StakingRewards.sol";
contract StakingFuzzTest is Test {
StakingRewards staking;
function setUp() public {
// ... 部署合约
}
/// @notice 模糊测试:任意金额质押后,提取不能超过质押量
function testFuzz_WithdrawCannotExceedStaked(uint256 stakeAmount, uint256 withdrawAmount) public {
// 限制输入范围
stakeAmount = bound(stakeAmount, 1, 1_000_000 ether);
withdrawAmount = bound(withdrawAmount, stakeAmount + 1, type(uint256).max);
// 质押
deal(address(stakingToken), address(this), stakeAmount);
stakingToken.approve(address(staking), stakeAmount);
staking.stake(stakeAmount);
// 超额提取应该失败
vm.expectRevert("Exceeds staked");
staking.withdraw(withdrawAmount);
}
}
Deployment and validation
Contract deployment is a critical step in permanently writing code to the blockchain, requiring careful execution and ensuring that the source code is publicly verifiable.
Deploy checklists
- Compiler version: Locks the exact version (e.g. 0.8.20) to avoid the ^0.8.0 range
- Optimize settings: Records the optimizer runs parameter, which must be exactly the same when verifying
- Constructor parameters: Carefully check the initialization parameters (address, value)
- Network confirmation: Quadratic Confirmation Target Network (mainnet vs testnet)
- Gas estimates: Ensure that the deployment wallet has enough native tokens
- Sign more for approval: Important contracts are deployed through Safe multi-signature
Multi-chain deployment scripts
// scripts/deploy.ts
import { ethers, network, run } from "hardhat";
async function main() {
const [deployer] = await ethers.getSigners();
console.log(`Deploying on ${network.name} with ${deployer.address}`);
console.log(`Balance: ${ethers.formatEther(await ethers.provider.getBalance(deployer.address))} ETH`);
// 部署合约
const Contract = await ethers.getContractFactory("MyContract");
const contract = await Contract.deploy(/* constructor args */);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log(`Contract deployed: ${address}`);
// 等待区块确认(Etherscan验证需要)
console.log("Waiting for confirmations...");
await contract.deploymentTransaction()?.wait(5);
// 自动验证源码到 Etherscan
console.log("Verifying on Etherscan...");
await run("verify:verify", {
address,
constructorArguments: [/* same args */],
});
console.log("✅ Deployment & verification complete!");
}
main().catch(console.error);
Post-deployment validation
- Confirm that the contract is validated in Etherscan (green tick)
- Confirm that the contract owner is the expected multisig address
- Execute a test transaction to confirm that the function is normal
- Set up transaction monitoring and alerts on Tenderly
- Update the contract address and ABI in the front-end configuration
Contract audit process
Professional security audits are a necessary step before a contract goes live. The audit not only finds vulnerabilities, but also provides users with confidence guarantees.
Audit process timeline
Code Freeze & Document Preparation (1 week)
Freeze code versions, write technical documentation, describe business logic, and label known risks. Test coverage reports and architecture diagrams are available.
Automated scanning (2-3 days)
Static analysis and symbolic execution using tools like Slither, Mythril, Aderyn, and more to discover known vulnerability patterns.
Manual audit (2-4 weeks)
Senior auditors review code line by line to analyze issues that cannot be discovered by automation tools, such as vulnerabilities in business logic, economic attack vectors, and privilege escalation paths.
Repair & Review (1-2 weeks)
The team fixes the problems found according to the audit report, and the auditor reviews to confirm the effectiveness of the repair. Iterate to all high-risk problem resolutions.
Report Release & Bug Bounty
Publicly audit reports enhance user trust. The Immunefi Bug Bounty program will continue to be tested by the community after launch.
Well-known audit institutions
| institutions | Features: | Audit cycle | Fee range |
|---|---|---|---|
| Trail of Bits | Top security research, Slither developer | 4-8 weeks | 00K - $500K+ |
| OpenZeppelin | Standard library maintainer, deepest EVM understanding | 4-6 weeks | $80K - $300K |
| CertiK | Formal verification and wide coverage | 2-4 weeks | $30K - 50K |
| Spearbit | Elite Auditor DAO, team by project | 2-4 weeks | $50K - 00K |
| Code4rena | Competitive audit and crowdsourcing model | 1-2 weeks | $30K - 00K |
Development process and quotation
Based on the delivery experience of the NovaLinkR team in dozens of smart contract projects, the typical contract development process is as follows:
Requirements sorting and program design (1-2 weeks)
Deeply understand business requirements, design contract architecture, and select technical solutions (chain, standard, proxy model). Output: Technical solution documentation.
Contract Development & Unit Testing (2-6 weeks)
Solidity contract coding, OpenZeppelin integration, 100% unit test coverage, gas optimization.
Integration Testing and Security Hardening (1-2 weeks)
Inter-contract integration testing, fork testing, fuzz testing, internal security review, and slither scanning.
Third-party audit (2-4 weeks)
Submit code to the audit institution, cooperate with the audit Q&A, fix the problems found, and get the final report.
Testnet deployment and co-commissioning (1-2 weeks)
Deploy to the testnet, co-debug with the front-end/back-end, invite beta users to test, and fix integration issues.
Mainnet Launch & Monitoring (1 week)
Mainnet deployment, Etherscan verification, monitoring alarm configuration, bug bounty activation, and O&M handover.
Project quotation reference
| Project type | Complexity | Cycle | Fee range |
|---|---|---|---|
| ERC-20 tokens + lock-up release | low | 2-3 weeks | $8,000 - 5,000 |
| NFT collection + minting + marketplace | Medium | 4-6 weeks | 0,000 - $50,000 |
| DeFi protocols (DEX/lending/staking) | High | 8-12 weeks | $50,000 - 50,000 |
| DAO governance + token economy | High | 6-10 weeks | $40,000 - 00,000 |
| Cross-chain bridge / omnichain protocol | Extremely high | 12-20 weeks | 00,000 - $300,000 |
The NovaLinkR team has extensive experience in contract development and auditing, providing contract development services throughout the lifecycle, from ERC standard tokens to complex DeFi protocols.Contact us todayGet a free technical consultation and project quote.