📝 第06课:Solidity基础

智能合约 ✅ 验证通过

🎯 学习目标:掌握Solidity语言基础语法,理解合约结构、状态变量、函数可见性,能编写并部署第一个智能合约。

📖 一、Solidity语言概览

Solidity = 以太坊智能合约的官方语言,由Gavin Wood提出,Christian Reitwiessner主导开发。它是静态类型、面向合约的高级语言,编译后运行在EVM上。

Solidity发展历程:
• 2015年:Solidity 0.1.0 发布
• 2016年:DAO事件暴露安全问题
• 2017年:0.4.x 稳定版本
• 2020年:0.7.x 大量弃用特性清理
• 2021年:0.8.x 内置溢出检查、自定义错误
• 2023年:0.8.20+ 引入临时操作码(MCOPY)

语言特点:
• 静态类型——编译期类型检查,减少运行时错误
• 面向合约——COP(Contract-Oriented Programming)
• 继承支持——单继承+多接口继承,类似Python的MRO
• 事件驱动——通过Event与外部通信
• 编译为EVM字节码——通过solc编译器
• 受C++/Python/JavaScript影响——语法较易上手

📖 二、合约结构详解

Solidity 合约结构 // SPDX-License-Identifier: MIT ← 许可证声明(0.6.8+必须) pragma solidity ^0.8.19; ← 编译器版本约束 import "./OtherContract.sol"; ← 导入外部合约 contract MyContract is Parent { ← 继承(可选) // 1. 状态变量 (永久存储在链上,最贵!) uint256 public count; address public owner; mapping(address => uint256) balances; // 2. 事件 (日志记录,DApp监听) event CountChanged(uint256 newCount, address changer); // 3. 自定义错误 (0.8.4+,比require字符串省Gas) error Unauthorized(address caller); // 4. 修饰符 (函数修饰器,可复用逻辑) modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; // _ 代表被修饰函数的函数体 } // 5. 构造函数 (部署时执行一次) constructor(uint256 _initialCount) { owner = msg.sender; count = _initialCount; } // 6. 函数 (合约的行为) function increment() public onlyOwner { count += 1; emit CountChanged(count, msg.sender); } // 7. 接收ETH的特殊函数 receive() external payable { } // 接收纯ETH转账 fallback() external payable { } // 未知函数调用的兜底 }

2.1 SPDX许可证与版本约束

版本约束语法:
pragma solidity ^0.8.19; — 0.8.19 ≤ version < 0.9.0
pragma solidity >=0.8.0 <0.9.0; — 精确范围
pragma solidity 0.8.19; — 固定版本(推荐生产环境)

⚠️ 0.8是一个重大版本分界线:内置溢出检查、自定义错误、更严格的安全默认值。本课程全部基于0.8+。

💻 三、代码实战:第一个智能合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

/// @title 计数器合约 - Solidity基础完整示例
/// @author Web3课程
/// @notice 实现计数、权限管理、ETH接收功能
contract Counter {
    // ===== 状态变量 =====
    uint256 private _count;
    address public owner;
    uint256 public lastModified;
    uint256 public totalDeposits;

    // ===== 事件 =====
    event CountIncremented(uint256 newCount, address indexed caller);
    event CountDecremented(uint256 newCount, address indexed caller);
    event CountReset(address indexed caller);
    event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
    event DepositReceived(address indexed from, uint256 amount);

    // ===== 自定义错误 =====
    error Unauthorized(address caller, address required);
    error CountAlreadyZero();
    error ZeroAddress();

    // ===== 修饰符 =====
    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert Unauthorized(msg.sender, owner);
        }
        _;
    }

    modifier countNotZero() {
        if (_count == 0) revert CountAlreadyZero();
        _;
    }

    // ===== 构造函数 =====
    constructor() {
        owner = msg.sender;
        _count = 0;
        lastModified = block.timestamp;
    }

    // ===== 读函数 (view - 不消耗Gas) =====
    function getCount() public view returns (uint256) {
        return _count;
    }

    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function getTimeSinceLastModify() public view returns (uint256) {
        return block.timestamp - lastModified;
    }

    // ===== 写函数 (消耗Gas) =====
    function increment() public {
        _count += 1;
        lastModified = block.timestamp;
        emit CountIncremented(_count, msg.sender);
    }

    function decrement() public countNotZero {
        _count -= 1;
        lastModified = block.timestamp;
        emit CountDecremented(_count, msg.sender);
    }

    function reset() public onlyOwner {
        _count = 0;
        lastModified = block.timestamp;
        emit CountReset(msg.sender);
    }

    function batchIncrement(uint256 times) public {
        require(times > 0 && times <= 100, "Times must be 1-100");
        for (uint256 i = 0; i < times; i++) {
            _count += 1;
        }
        lastModified = block.timestamp;
        emit CountIncremented(_count, msg.sender);
    }

    // ===== 特殊函数 =====
    function transferOwnership(address newOwner) public onlyOwner {
        if (newOwner == address(0)) revert ZeroAddress();
        address oldOwner = owner;
        owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

    // ===== 接收ETH =====
    receive() external payable {
        totalDeposits += msg.value;
        emit DepositReceived(msg.sender, msg.value);
    }

    fallback() external payable {
        totalDeposits += msg.value;
    }

    // ===== 提取函数 =====
    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        (bool success, ) = payable(owner).call{value: balance}("");
        require(success, "Withdrawal failed");
    }
}
// ✅ 验证通过 - Remix IDE编译部署测试

📖 四、函数可见性详解

可见性合约内部继承合约外部调用默认说明
public函数默认最开放,任何地方可调用
external仅外部调用,Gas更低
internal状态变量默认合约内部+子合约可访问
private仅本合约内部
Solidity的private只是限制访问权限,不等于数据隐私!所有链上数据(包括private变量)都可以通过节点RPC的eth_getStorageAt直接读取。

📖 五、状态可变性

关键字读状态写状态外部调用Gas说明
view免费只读,不修改任何状态
pure免费纯计算,不读也不写
无修饰需要Gas可读写状态
payable需要Gas可接收ETH
💡 Gas省费技巧:
• 能用view就用view——外部调用免费
• 能用pure就用pure——最严格的保证
• public状态变量自动生成view的getter函数
• external+calldata比public+memory更省Gas

📖 六、数据位置初探

数据位置 (Data Location) ┌──────────────────────────────────────────────────┐ │ storage - 永久存储(链上),状态变量默认位置 │ │ 读写都消耗Gas(写尤其贵,SSTORE 20k+) │ ├──────────────────────────────────────────────────┤ │ memory - 临时内存,函数参数/局部变量默认 │ │ 函数调用结束即销毁 │ ├──────────────────────────────────────────────────┤ │ calldata - 只读的外部调用数据 │ │ 最省Gas(不可修改,直接引用) │ │ 仅用于external函数的参数 │ ├──────────────────────────────────────────────────┤ │ stack - EVM栈,局部值类型变量 │ │ 最大1024层,每项256位 │ └──────────────────────────────────────────────────┘

📝 七、练习题

1. Solidity合约的构造函数在什么时候执行? 2. view和pure函数的核心区别? 3. Solidity中private变量的数据是否真的私有? 4. calldata相比memory的优势? 5. modifier中的 _; 代表什么?

🔧 八、动手实验

实验1:Remix IDE部署第一个合约

  1. 打开 Remix IDE
  2. 创建Counter.sol文件,粘贴上面完整代码
  3. 编译(Ctrl+S),确保无错误无警告
  4. 切换到Deploy标签,选择Remix VM环境
  5. 点击Deploy,展开合约实例
  6. 调用increment(),观察count变化
  7. 尝试用非owner调用reset(),观察错误消息

实验2:理解可见性和Gas差异

  1. 在Remix中观察increment()(写操作)的Gas消耗
  2. 对比getCount()(view操作)的Gas消耗——应为0
  3. 将increment改为external,对比Gas变化
  4. 思考:为什么external比public更省Gas?
📝

合约初学者

你已迈出智能合约开发第一步——从合约结构到函数可见性到数据位置!
这是构建Web3应用的基石。

✅ 合约结构 ✅ 函数可见性 ✅ 状态可变性 ✅ 数据位置

📋 课程目录