Solidity 学习总结:核心特性与实战指南

1. Solidity 概览

Solidity 是 Ethereum 及 EVM 兼容链上编写智能合约的主力语言。它是一种静态类型、面向合约的语言,语法借鉴了 JavaScript、Python 和 C++,但运行模型与传统编程有本质差异------每一次状态变更都需要消耗 Gas,且合约一旦部署便不可修改

理解 Solidity 的关键心智模型转变:

  • 一切皆交易 :合约函数调用本质上是一笔链上交易(view/pure 除外)
  • 状态是昂贵的:读写 storage 是 Gas 消耗的最大头
  • 信任边界不同:合约的调用者可能是任何人,甚至是恶意合约
  • 不可变性:部署后代码无法修改,需要通过代理模式等架构手段解决

版本与编译

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

Solidity 0.8.0 是一个重要分界线:内置了算术溢出检查(之前需要 SafeMath 库)。当前(2024-2025)生产环境推荐使用 ^0.8.20 或更高版本。

2. 开发环境

推荐工具链

工具 定位 适用场景
Hardhat JS/TS 生态的全栈框架 前端集成、快速原型
Foundry Rust 编写的高性能框架 专业合约开发、 fuzz 测试
Remix IDE 浏览器 IDE 学习、快速实验
OpenZeppelin 合约标准库 生产级安全组件

Hardhat 快速上手

bash 复制代码
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npx hardhat --init

项目结构:

bash 复制代码
├── contracts/     # Solidity 源码
├── scripts/       # 部署脚本
├── test/          # 测试文件
├── hardhat.config.js
└── package.json

Foundry 快速上手

bash 复制代码
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge init my-project

Foundry 的核心优势在于极快的编译和测试速度,以及内置的 fuzz testing 能力。

3. 数据类型与变量

值类型(Value Types)

值类型在赋值时总是复制一份,独立于原始值。

solidity 复制代码
// 整数:支持多种位宽
uint256 public balance;    // 无符号 256 位,默认类型
int128 public delta;       // 有符号 128 位
uint8 public counter;      // 0 ~ 255

// 布尔
bool public active = true;

// 地址
address public owner;
address payable public recipient; // 可接收 ETH 的地址

// 定长字节数组
bytes32 public hash;       // 32 字节,常用于哈希值
bytes20 public addr;       // 20 字节,与 address 同大小,但需显式转换

// 枚举(底层是 uint,从 0 开始)
enum Status { Pending, Active, Completed, Cancelled }
Status public currentStatus = Status.Pending;

关键点uint256 是 EVM 的原生字长,使用更小的类型(如 uint8)在 storage 打包时有优化效果,但在函数参数和局部变量中反而更贵(需要额外的清理操作)。

引用类型(Reference Types)

引用类型赋值时传递引用,修改会影响原始数据。必须明确标注数据位置。

solidity 复制代码
// 动态数组
uint256[] public numbers;
address[] public participants;

// 定长数组
uint256[10] public fixedNumbers;

// 结构体
struct User {
    address addr;
    uint256 balance;
    Status status;
}

// 映射(不能遍历,不能有 key 列表)
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;

// 字符串与字节
string public name;        // 动态 UTF-8 字符串
bytes public data;         // 动态字节数组

数据位置:storage / memory / calldata

这是 Solidity 独有的概念,直接影响 Gas 消耗和性能。

solidity 复制代码
contract DataLocation {
    uint256[] public storageArray; // state 变量永远是 storage

    function example(uint256[] calldata input) external {
        // calldata: 只读,不复制,Gas 最低 ------ 用于外部函数参数
        uint256 first = input[0];

        // memory: 复制到内存,可修改,函数结束后销毁
        uint256[] memory copy = input;
        copy[0] = 999;

        // storage 引用:指向 state 变量,修改会持久化
        uint256[] storage ref = storageArray;
        ref.push(42); // 直接修改 storageArray
    }
}

经验法则

  • 外部函数的只读参数用 calldata
  • 需要修改的临时数据用 memory
  • 需要持久化的数据用 storage(注意避免不必要的 storage 读写)

4. 函数系统

可见性(Visibility)

solidity 复制代码
function externalFunc() external { } // 只能从合约外部调用
function publicFunc() public { }     // 内外皆可
function internalFunc() internal { } // 仅内部和子合约
function privateFunc() private { }   // 仅当前合约

external 函数接收参数通过 calldata,比 publicmemory 复制更省 Gas。因此如果一个函数只预期被外部调用,优先使用 external

特殊函数修饰

solidity 复制代码
// view: 读取状态但不修改,不消耗 Gas(外部调用时)
function getBalance() external view returns (uint256) {
    return address(this).balance;
}

// pure: 不读不写状态,纯计算
function add(uint256 a, uint256 b) external pure returns (uint256) {
    return a + b;
}

// payable: 允许接收 ETH
function deposit() external payable {
    balances[msg.sender] += msg.value;
}

函数修改器(Modifiers)

修改器是 Solidity 的 AOP(面向切面编程)机制,常用于权限控制和前置条件校验。

solidity 复制代码
modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _; // 占位符,表示被修饰函数的执行位置
}

modifier validAmount(uint256 amount) {
    require(amount > 0, "Amount must be > 0");
    _;
}

function withdraw(uint256 amount) external onlyOwner validAmount(amount) {
    payable(msg.sender).transfer(amount);
}

返回值

solidity 复制代码
// 命名返回值
function getInfo() external view returns (address addr, uint256 bal) {
    addr = msg.sender;
    bal = balances[msg.sender];
}

// 多返回值与解构
function swap(uint256 a, uint256 b) external pure returns (uint256, uint256) {
    return (b, a);
}

5. 合约结构与面向对象

继承

Solidity 支持多重继承,使用 C3 线性化解决菱形继承问题。

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Ownable {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
}

contract Pausable {
    bool public paused;

    modifier whenNotPaused() {
        require(!paused, "Paused");
        _;
    }
}

// 多重继承: Ownable + Pausable
contract Token is Ownable, Pausable {
    mapping(address => uint256) public balances;

    function mint(address to, uint256 amount) external onlyOwner whenNotPaused {
        balances[to] += amount;
    }

    // 继承顺序决定了修饰器优先级,也可以显式指定
    // Ownable.owner 是 public state 变量,自动生成同名 getter,无需手动 override
}

抽象合约与接口

solidity 复制代码
// 抽象合约:至少有一个未实现的函数
abstract contract ERC20Base {
    function totalSupply() public view virtual returns (uint256);
    function balanceOf(address) public view virtual returns (uint256);
}

// 接口:所有函数都是外部且未实现的,不能有状态变量
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
}

实践建议:优先使用接口而非抽象合约,接口不占用合约大小限制(24KB),且解耦更彻底。

库(Libraries)

库是部署在链上的可复用代码,使用 delegatecall 执行(修改调用合约的状态),或对于 internal/private 函数直接内联编译进调用合约。

solidity 复制代码
library SafeTransfer {
    function safeTransfer(address token, address to, uint256 amount) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSignature("transfer(address,uint256)", to, amount)
        );
        require(success && (data.length == 0 || abi.decode(data, (bool))), "Transfer failed");
    }
}

contract Vault {
    using SafeTransfer for address;

    function rescueToken(address token, uint256 amount) external {
        token.safeTransfer(msg.sender, amount);
    }
}

6. 高级特性

事件(Events)

事件是合约向链外(前端、索引服务)通信的唯一低成本方式。数据存储在日志中,合约自身无法读取。

solidity 复制代码
contract Auction {
    // indexed 参数可以被高效过滤(最多 3 个)
    event BidPlaced(address indexed bidder, uint256 amount, uint256 timestamp);
    event AuctionEnded(address indexed winner, uint256 highestBid);

    function bid() external payable {
        emit BidPlaced(msg.sender, msg.value, block.timestamp);
    }
}

自定义错误(Custom Errors)

0.8.4 引入,比 require(message) 更省 Gas。原因是:require 的字符串参数会完整编码进 revert 数据(越长的字符串消耗越多),而自定义错误只编码 4 字节的错误选择器加参数,大幅减少 revert 数据量。

solidity 复制代码
error InsufficientBalance(uint256 available, uint256 required);
error Unauthorized(address caller);

contract Vault {
    mapping(address => uint256) public balances;

    function withdraw(uint256 amount) external {
        if (balances[msg.sender] < amount) {
            revert InsufficientBalance(balances[msg.sender], amount);
        }
        balances[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
    }
}

低级调用与合约交互

solidity 复制代码
contract Caller {
    // call: 调用外部合约,最推荐的方式
    function callExternal(address target, uint256 value) external {
        (bool success, bytes memory returnData) = target.call{value: value}(
            abi.encodeWithSignature("deposit(uint256)", 100)
        );
        require(success, "External call failed");
    }

    // delegatecall: 在调用者的上下文中执行目标合约的代码
    // 用于代理模式(Proxy Pattern)
    function upgradeCall(address implementation, bytes memory data) external {
        (bool success, ) = implementation.delegatecall(data);
        require(success, "Delegatecall failed");
    }

    // staticcall: 只读调用,如果目标修改状态则会 revert
    function readOnly(address target) external view returns (bytes memory) {
        (bool success, bytes memory data) = target.staticcall(
            abi.encodeWithSignature("getInfo()")
        );
        require(success);
        return data;
    }
}

call vs delegatecall

特性 call delegatecall
代码执行位置 目标合约 调用合约
msg.sender 调用合约地址 原始调用者
storage 目标合约的 storage 调用合约的 storage
用途 普通合约交互 代理模式、库

delegatecall 安全警告delegatecall 在调用合约的 storage 上下文中执行,因此调用合约和逻辑合约的 storage 布局必须严格一致(变量声明顺序、类型完全相同),否则会导致状态变量被错误覆盖。此外,逻辑合约必须被初始化(通过 initialize() 而非构造函数),且需防止被他人抢先初始化。

try/catch 错误处理

对外部调用使用 try/catch 可以让合约优雅地处理失败,而非整体回滚。

solidity 复制代码
contract ResilientVault {
    IERC20 public token;

    constructor(address _token) {
        token = IERC20(_token);
    }

    function safeTransfer(address to, uint256 amount) external {
        try token.transfer(to, amount) returns (bool success) {
            if (!success) {
                // 处理 transfer 返回 false 的情况(如 USDT 等非标准实现)
                emit TransferFailed(to, amount);
            }
        } catch Error(string memory reason) {
            // 捕获 require/revert 带字符串的情况
            emit TransferReverted(reason);
        } catch (bytes memory) {
            // 捕获所有其他异常(如目标合约不存在、Gas 不足等)
            emit UnknownError();
        }
    }

    event TransferFailed(address to, uint256 amount);
    event TransferReverted(string reason);
    event UnknownError();
}

构造器与特殊函数

solidity 复制代码
contract MyContract {
    address public owner;                  // 普通 state 变量
    address public immutable deployer;     // immutable: 构造器中赋值一次,之后不可变
    uint256 public constant MAX_SUPPLY = 1_000_000; // constant: 编译时常量,直接内联

    constructor(address _initialOwner) {
        deployer = msg.sender;
        owner = _initialOwner;
    }

    // receive: 接收纯 ETH 转账(无 calldata)
    receive() external payable { }

    // fallback: 处理不匹配任何函数的调用
    fallback() external payable { }
}

两者的 Gas 优化机制略有不同:constant 在编译时直接将值替换到字节码中;immutable 在部署时写入合约的运行时代码(而非 storage),因此读取时通过 CODECOPY 而非 SLOAD,Gas 成本远低于普通 storage 变量。两者都不占用 storage slot。

7. 安全实战

这是智能合约开发中最关键的部分。合约一旦部署,漏洞可能直接导致资金损失且无法挽回。

重入攻击(Reentrancy)

攻击原理 :合约在更新余额前将 ETH 发送给攻击者,攻击者的 receive() 函数再次调用取款函数,形成递归。

solidity 复制代码
// ❌ 危险代码
contract VulnerableVault {
    mapping(address => uint256) public balances;

    function withdraw() external {
        uint256 amount = balances[msg.sender];
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok);
        balances[msg.sender] = 0; // 这行在重入时还未执行!
    }
}

// ✅ 修复方案:Checks-Effects-Interactions 模式
contract SafeVault {
    mapping(address => uint256) public balances;

    function withdraw() external {
        uint256 amount = balances[msg.sender]; // Checks
        require(amount > 0, "No balance");

        balances[msg.sender] = 0; // Effects(先更新状态)

        (bool ok, ) = msg.sender.call{value: amount}(""); // Interactions
        require(ok, "Transfer failed");
    }
}

// ✅ 修复方案 2:重入锁
contract SafeVault2 {
    uint256 private locked = 1;

    modifier nonReentrant() {
        require(locked == 1, "Reentrant call");
        locked = 2;
        _;
        locked = 1;
    }

    function withdraw() external nonReentrant {
        // ...
    }
}

前端运行(Front-running)

问题:矿工/验证者可以看到 mempool 中的待处理交易,并据此插入自己的交易获利。

防御措施

  • 使用 commit-reveal 方案(先提交哈希,后揭示数据)
  • 设置滑点保护(DEX 中设置 minAmountOut
  • 使用 Flashbots 等私有交易池

访问控制缺失

solidity 复制代码
// ❌ 任何人都能调用
function setFee(uint256 newFee) external {
    fee = newFee;
}

// ✅ 正确的访问控制
function setFee(uint256 newFee) external onlyOwner {
    require(newFee <= MAX_FEE, "Fee too high");
    fee = newFee;
    emit FeeUpdated(newFee);
}

tx.origin 钓鱼

solidity 复制代码
// ❌ 危险:如果用户被诱导调用恶意合约,tx.origin 是用户地址
require(tx.origin == owner, "Not owner");

// ✅ 正确:msg.sender 是直接调用者
require(msg.sender == owner, "Not owner");

未检查的外部调用返回值

solidity 复制代码
// ❌ send 和 transfer 失败只返回 false 或抛少量 Gas
// 注意:transfer 固定 2300 gas 限制,在 Gas 成本变化后可能失败
(bool ok, ) = recipient.call{value: amount}("");
// 忘记检查 ok...

// ✅ 始终检查返回值
(bool ok, ) = recipient.call{value: amount}("");
require(ok, "Transfer failed");

整数溢出/下溢

Solidity 0.8+ 已内置检查,但使用 unchecked 块时需格外小心:

solidity 复制代码
// unchecked 块可节省 Gas,但必须先完成边界校验
function transfer(address to, uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient balance");
    unchecked {
        // 前置 require 已保证 amount <= balances[msg.sender],减法不会下溢
        // 同时 amount <= 2^256,加法也不会溢出(余额上限约束)
        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
}

8. Gas 优化

核心原则

Gas 消耗的主要来源按数量级排列:

  1. SSTORE(写入 storage):~20,000 gas(新 slot)/ ~5,000 gas(更新)
  2. SLOAD(读取 storage):~2,100 gas(冷访问)/ ~100 gas(热访问)
  3. CALL(外部调用):~2,600 gas + 子调用 Gas
  4. 内存操作和计算:通常可忽略

实用优化技巧

1. 变量打包(Struct Packing)

EVM storage slot 为 32 字节,连续的小变量可以打包进同一个 slot。

solidity 复制代码
// ❌ 浪费:每个变量占一个 slot(共 3 slots)
uint8 a;
uint256 b;
uint8 c;

// ✅ 打包:a 和 c 与 b 共享一个 slot(共 2 slots)
uint8 a;
uint8 c;
uint256 b;

// ✅ 使用 struct 显式打包
struct Packed {
    uint128 balance;   // 16 bytes
    uint64 timestamp;  // 8 bytes
    uint32 id;         // 4 bytes
    bool active;       // 1 byte
} // 总计 29 bytes,一个 slot 搞定

2. 用 calldata 代替 memory

solidity 复制代码
// ❌ 复制到 memory 需要额外 Gas
function process(uint256[] memory data) external { }

// ✅ calldata 直接读取,零拷贝
function process(uint256[] calldata data) external { }

3. 使用 constant / immutable

solidity 复制代码
// ❌ 每次读取消耗 storage gas
uint256 public maxSupply = 1000000;

// ✅ constant 编译时替换,零 Gas
uint256 public constant MAX_SUPPLY = 1000000;

// ✅ immutable 构造时确定,首次读取后缓存
address public immutable DEPLOYER = msg.sender;

4. 短路运算

solidity 复制代码
// 将更可能为 false 的条件放前面
require(cheapCheck() && expensiveCheck(), "Failed");

5. 合理使用 unchecked

solidity 复制代码
// 循环计数器用 unchecked 可以显著省 Gas
for (uint256 i = 0; i < array.length;) {
    // ... process array[i]
    unchecked { ++i; } // 不会溢出(除非数组长度达到 2^256)
}

9. ERC 代币标准

ERC(Ethereum Request for Comments)是以太坊生态的合约接口标准,定义了不同场景下合约必须实现的函数,确保互操作性。

ERC-20:同质化代币

最常用的代币标准,定义了 totalSupplybalanceOftransfertransferFromapproveallowance 六个核心函数。几乎所有 DeFi 协议都基于此标准交互。

solidity 复制代码
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor() ERC20("MyToken", "MTK") {
        _mint(msg.sender, 1_000_000 * 10 ** decimals());
    }
}

ERC-721:非同质化代币(NFT)

每个代币有唯一的 tokenId,适合数字艺术品、游戏资产、域名等场景。核心操作包括 ownerOfsafeTransferFromapprove

ERC-1155:多代币标准

一个合约中同时管理多种代币(同质化和非同质化混合),支持批量转账,大幅降低 Gas 成本。广泛用于游戏资产、批量空投。

ERC-4626:代币化金库

定义了 ERC-20 金库的标准接口,统一了存入、提取、份额计算的函数签名。Yearn、Aave 等协议的 Vault 正在向此标准迁移。

实践建议:生产环境中应始终使用 OpenZeppelin 的合约实现而非从零编写,并在其基础上通过继承添加自定义逻辑(如投票、白名单、版税等)。

10. 设计模式

代理模式(Proxy Pattern)

解决合约不可变性问题。用户与代理合约交互,代理通过 delegatecall 转发调用到逻辑合约。

solidity 复制代码
// 简化版透明代理
contract Proxy {
    address public implementation;
    address public admin;

    constructor(address _impl) {
        implementation = _impl;
        admin = msg.sender;
    }

    // 管理员升级
    function upgradeTo(address newImpl) external {
        require(msg.sender == admin, "Not admin");
        implementation = newImpl;
    }

    // 所有其他调用转发到逻辑合约
    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

生产环境请使用 OpenZeppelin 的 TransparentUpgradeableProxyUUPSUpgradeable,它们处理了 storage 冲突、初始化等复杂问题。

工厂模式(Factory Pattern)

solidity 复制代码
contract TokenFactory {
    event TokenCreated(address indexed token, string name, address creator);

    function createToken(string calldata name, string calldata symbol) external returns (address) {
        SimpleToken token = new SimpleToken(name, symbol, msg.sender);
        emit TokenCreated(address(token), name, msg.sender);
        return address(token);
    }
}

使用 CREATE2 可以预计算合约地址(常用于空投、Layer 2 钱包):

solidity 复制代码
function deployDeterministic(bytes32 salt, bytes memory bytecode) internal returns (address addr) {
    assembly {
        addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
    }
}

拉取支付模式(Pull Over Push)

避免在向多个接收方转账时因某个地址 revert 而导致全部失败。

solidity 复制代码
// ❌ Push 模式:一个失败全部失败
function distributeRewards() external {
    for (uint256 i = 0; i < recipients.length; i++) {
        payable(recipients[i]).transfer(rewards[recipients[i]]);
    }
}

// ✅ Pull 模式:用户自己来取
mapping(address => uint256) public pendingRewards;

function claimReward() external {
    uint256 reward = pendingRewards[msg.sender];
    require(reward > 0, "No reward");
    pendingRewards[msg.sender] = 0;
    payable(msg.sender).transfer(reward);
}

11. 测试与部署

Hardhat 测试示例

javascript 复制代码
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Vault", function () {
  let vault, owner, user;

  beforeEach(async function () {
    [owner, user] = await ethers.getSigners();
    const Vault = await ethers.getContractFactory("Vault");
    vault = await Vault.deploy();
    await vault.waitForDeployment();
  });

  it("should allow deposits and withdrawals", async function () {
    await vault.connect(user).deposit({ value: ethers.parseEther("1.0") });
    expect(await vault.balances(user.address)).to.equal(ethers.parseEther("1.0"));

    await vault.connect(user).withdraw(ethers.parseEther("0.5"));
    expect(await vault.balances(user.address)).to.equal(ethers.parseEther("0.5"));
  });

  it("should revert on insufficient balance", async function () {
    await expect(
      vault.connect(user).withdraw(ethers.parseEther("1.0"))
    ).to.be.revertedWithCustomError(vault, "InsufficientBalance");
  });
});

Foundry 测试示例

solidity 复制代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/Vault.sol";

contract VaultTest is Test {
    Vault public vault;
    address public user = address(0x1);

    function setUp() public {
        vault = new Vault();
        vm.deal(user, 10 ether);
    }

    function test_DepositAndWithdraw() public {
        vm.prank(user);
        vault.deposit{value: 1 ether}();
        assertEq(vault.balances(user), 1 ether);

        vm.prank(user);
        vault.withdraw(0.5 ether);
        assertEq(vault.balances(user), 0.5 ether);
    }

    // Fuzz testing: 自动随机生成输入
    function testFuzz_Deposit(uint256 amount) public {
        amount = bound(amount, 0, 10 ether);
        vm.deal(user, amount);
        vm.prank(user);
        vault.deposit{value: amount}();
        assertEq(vault.balances(user), amount);
    }
}

部署脚本

javascript 复制代码
// scripts/deploy.js (Hardhat)
async function main() {
  const Vault = await ethers.getContractFactory("Vault");
  const vault = await Vault.deploy();
  await vault.waitForDeployment();
  console.log("Vault deployed to:", await vault.getAddress());
}

main().catch(console.error).finally(() => process.exit());

部署命令:

bash 复制代码
# Hardhat - 部署到测试网
npx hardhat run scripts/deploy.js --network sepolia

# Foundry - 部署并验证
forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify

12. 实用速查表

全局变量

变量 说明
msg.sender 当前调用者地址
msg.value 本次调用携带的 ETH(wei)
msg.data 完整 calldata
block.timestamp 当前区块时间戳(可被矿工小幅操纵)
block.number 当前区块号
block.chainid 链 ID
tx.origin 交易发起者(勿用于鉴权)
gasleft() 剩余 Gas
address(this) 当前合约地址

ETH 转账方式对比

方式 Gas 限制 失败处理 推荐程度
transfer() 2300 固定 revert 不推荐
send() 2300 固定 返回 bool 不推荐
call{value: x}("") 全部可用 Gas 返回 bool 推荐

注意 :使用 call{value:}("") 时务必配合重入锁(见第 7 节),因为它会将所有剩余 Gas 转发给接收方,接收方可能借此执行回调代码发起重入攻击。

常用内建函数

solidity 复制代码
// 地址
addr.balance;                    // 查询 ETH 余额
addr.call{value: 1 ether}("");   // 低级调用
addr.delegatecall(data);         // 代理调用

// ABI 编码
abi.encode(a, b);                // 标准编码
abi.encodePacked(a, b);          // 紧凑编码(注意碰撞风险)
abi.encodeWithSignature("func(uint256)", x);
abi.encodeWithSelector(IERC20.transfer.selector, to, amount);

// 哈希
keccak256(abi.encodePacked(a, b)); // Keccak-256 哈希

// 错误处理
require(condition, "message");     // 条件校验,失败时回滚并退还剩余 Gas
revert CustomError(params);        // 自定义错误(revert 数据更短,比 require 省 Gas)
assert(condition);                 // 不变量检查(正常逻辑不应失败)

13. 推荐学习路径

第一阶段:语言基础(1-2 周)

掌握数据类型、函数、继承、事件等核心语法。在 Remix IDE 中编写简单合约(投票、存储、ERC20 代币)建立直觉。

第二阶段:安全与模式(2-3 周)

系统学习常见漏洞(重入、前端运行、访问控制),精读 Ethernaut 和 Damn Vulnerable DeFi 等 CTF 挑战。这是从"能写"到"写得好"的关键跨越。

第三阶段:工程实践(2-4 周)

掌握 Hardhat 或 Foundry 的完整开发流程,学习编写全面的测试(单元测试 + fuzz 测试),理解 Gas 优化策略,精读 OpenZeppelin 合约库的实现。

第四阶段:深入 EVM(持续)

理解 Yul/内联汇编、storage 布局、合约大小限制、EIP 标准(ERC-20/721/1155/4626 等),阅读 DeFi 头部项目的源码(Uniswap V3、Aave、Compound)。

推荐资源


本文档基于 Solidity 0.8.x 版本编写,最后更新于 2026 年 6 月。

相关推荐
Rockbean21 小时前
10分钟智能合约:进阶实战-6.4 使合约拒绝服务
web3·智能合约·solidity
Rockbean21 小时前
10分钟智能合约:进阶实战-6.3 重入攻击提取资金
web3·智能合约·solidity
木西3 天前
实战:基于 Solidity 0.8.27 与 OpenZeppelin V5 构建多链恶搞代币(以 SPX6900 为例)
web3·智能合约·solidity
Maimai108088 天前
Web3 前端交易系统如何落地:从下单 UI 到 Operation 编码、签名与实时状态更新
前端·react.js·ui·架构·前端框架·web3
Maimai108088 天前
Web3 前端实时通信如何落地:从 SSE 订阅到行情、订单与账户状态更新
前端·javascript·react.js·前端框架·web3·状态模式
用户887665426638 天前
Web3 前端实时通信如何落地:从 SSE 订阅到行情、订单与账户状态更新
前端·react.js·web3
Rockbean8 天前
10分钟智能合约:进阶实战-4.3 Delegatecall漏洞
web3·智能合约·solidity
Man on the moon12 天前
Solidity 零基础入门:从语法到实战,快速掌握智能合约开发
web3·区块链·智能合约
JAMSAN093012 天前
通信权力的去中心化重构:Web3通讯工具市场深度分析
重构·web3·去中心化·交互