Solidity 智能合约基础

FreeGuideOnline 最新 2026-07-08

Solidity 简介与环境搭建

什么是 Solidity

Solidity 是一种面向合约的高级编程语言,主要用于在以太坊虚拟机(EVM)上编写智能合约。它的语法受到 JavaScript、C++ 和 Python 的影响,设计目标是让开发者能够编写去中心化应用(DApp)的逻辑。

智能合约是运行在区块链上的自执行程序,一旦部署,其代码和状态就不可篡改,这赋予了它无需可信第三方的自治能力。Solidity 是目前以太坊生态中使用最广泛的智能合约语言。

开发环境准备

初学者无需搭建本地区块链环境,可以直接使用在线集成开发环境 Remix IDE(https://remix.ethereum.org)。Remix 提供了代码编辑、编译、部署和调试的全套功能,内置一个 JavaScript 虚拟机模拟以太坊网络,可以零成本开始学习。

如果你希望本地开发,常用工具链包括:

  • Node.js + npm:管理项目依赖
  • Hardhat 或 Truffle:本地开发框架,提供编译、测试、部署脚本
  • Ganache:本地个人区块链,用于快速测试

本教程将以 Remix 为主要演示环境。

第一个合约:Hello World

在 Remix 中创建一个新文件 HelloWorld.sol,输入以下代码:

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

contract HelloWorld {
    string public greeting = "Hello, Solidty!";

    function getGreeting() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _newGreeting) public {
        greeting = _newGreeting;
    }
}
  • SPDX 许可证标识:第一行注释声明许可证,是 Solidity 社区的规范。
  • 版本声明pragma solidity ^0.8.0; 表示使用 0.8.0 及以上版本编译器,避免语法兼容问题。
  • contract 关键字:类似面向对象中的类,一个合约可以包含状态变量和函数。
  • 状态变量 greeting:永久存储在区块链上,public 修饰符自动生成一个同名的 getter 函数。
  • 函数getGreeting 读取数据(view 表示不修改状态),setGreeting 修改数据。

部署到 Remix 的虚拟机后,点击按钮就可以读写问候语,这让你直观感受到合约的存储与交互。

数据类型与结构

基本值类型

Solidity 提供丰富的值类型,它们是按值传递的。

类型 说明 示例
bool 布尔值,取 truefalse bool public isReady = true;
int / uint 有符号/无符号整数,从 8 位到 256 位,以 8 位为步长 uint256 public count = 100;
address 20 字节的以太坊地址,拥有余额和转账相关成员 address public owner = 0xAb8...;
bytes1bytes32 固定长度字节数组 bytes32 public hash = "0x12...";
string 动态长度 UTF-8 字符串 string public name = "Alice";

注意:在 Solidity 0.8.0 之后,整数运算默认带有溢出检查,安全度大幅提升。

引用类型与数据位置

引用类型包括数组、结构体和映射。它们的使用方式依赖于 数据位置(data location)

  • storage:状态变量的默认位置,数据永久存储在链上,读写开销大。
  • memory:临时变量,函数调用期间存在,调用后释放,用于局部变量。
  • calldata:函数参数的特殊数据位置,不可修改,直接来自交易输入数据,成本低。

代码示例:

uint[] public storageArray; // storage 数组

function testMemory(uint[] memory _input) public pure returns (uint) {
    uint[] memory memArray = new uint[](3); // memory 数组
    return memArray.length;
}

结构体与映射

结构体(struct) 允许自定义组合类型:

struct Person {
    string name;
    uint age;
    address wallet;
}
Person public person;

映射(mapping) 是一种键值对存储结构,类似于哈希表,语法为 mapping(keyType => valueType)。映射只能存在于 storage 中,不能作为函数参数的 memory 类型。

mapping(address => uint) public balances;

操作映射:

balances[msg.sender] = 100;
uint userBalance = balances[msg.sender];

函数与修饰器

函数基本语法

function 函数名(参数列表) 可见性修饰符 状态修饰符 returns (返回类型) {
    // 逻辑
}

常见可见性修饰符:

  • public:内部和外部均可访问
  • private:仅当前合约内部可见
  • internal:当前合约和派生合约可见
  • external:仅外部调用(不能通过 this 调用,但可被外部交易触发)

状态修饰符:

  • view:不修改状态,可读取状态
  • pure:既不读取也不修改状态,纯计算
  • payable:允许函数接收以太币

函数返回与解构

Solidity 支持多返回值:

function getInfo() public view returns (string memory, uint) {
    return ("Alice", 30);
}

function useInfo() public view {
    (string memory name, uint age) = getInfo(); // 元组解构
}

修饰器(modifier)

修饰器用于在函数执行前后进行条件检查,增强可读性和复用性。常用场景:权限控制、重入锁等。

modifier onlyOwner() {
    require(msg.sender == owner, "Not the contract owner");
    _; // 代表函数体执行位置
}

function changeOwner(address _newOwner) public onlyOwner {
    owner = _newOwner;
}

修饰器可以带参数,实现更灵活的校验。

合约继承与接口

继承

Solidity 支持多重继承,使用 is 关键字。子合约可以重写父合约函数,必须在函数上标记 virtualoverride

contract Ownable {
    address public owner;
    modifier onlyOwner() { ... }
}

contract Pausable is Ownable {
    bool public paused;
    function pause() public onlyOwner { ... }
}

抽象合约与接口

抽象合约 包含至少一个未实现的函数(没有函数体的 virtual 函数),不能直接部署,必须被继承并实现。

abstract contract Animal {
    function makeSound() public virtual returns (string memory);
}

接口(interface) 是完全抽象的合约,不能包含任何实现,也不能定义状态变量和构造函数。接口是与其他合约交互的标准方式。

interface IERC20 {
    function transfer(address to, uint amount) external returns (bool);
}

接口中的函数必须是 external,所有声明自动为 virtual

事件、回退与接收器

事件(Events)

事件允许将数据记录到区块链的交易日志中,成本低于存储,前端可以监听这些日志。语法:

event Transfer(address indexed from, address indexed to, uint value);

function transfer(address to, uint value) public {
    emit Transfer(msg.sender, to, value);
}

indexed 参数最多三个,可作为过滤条件索引。

错误处理与 require

除了传统的 requireassertrevert,Solidity 0.8.4 后支持自定义错误,可以节省 gas。

error InsufficientBalance(uint requested, uint available);

function withdraw(uint amount) public {
    if (balances[msg.sender] < amount)
        revert InsufficientBalance(amount, balances[msg.sender]);
    // 转账逻辑
}

回退函数与接收器

合约可以定义 fallbackreceive 来处理直接转账或调用不存在的函数。

  • receive():合约接收纯以太币转帐时触发,必须声明为 external payable
  • fallback():当调用的函数签名不匹配,或转账数据非空时触发。
event Received(address sender, uint amount);

receive() external payable {
    emit Received(msg.sender, msg.value);
}

fallback() external payable { }

实战:发行简单代币(ERC-20 标准)

标准接口概览

ERC-20 是以太坊同质化代币的技术标准,规定了必须实现的事件和函数:

totalSupply()
balanceOf(address)
transfer(address,uint)
transferFrom(address,address,uint)
approve(address,uint)
allowance(address,address)

事件:Transfer, Approval

完整实现示例

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

contract SimpleToken {
    mapping(address => uint) public balanceOf;
    mapping(address => mapping(address => uint)) public allowance;

    uint public totalSupply;
    string public name = "My Token";
    string public symbol = "MTK";
    uint8 public decimals = 18;

    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);

    constructor(uint _initialSupply) {
        totalSupply = _initialSupply * (10 ** uint(decimals));
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }

    function transfer(address to, uint value) external returns (bool) {
        require(balanceOf[msg.sender] >= value, "Insufficient balance");
        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;
        emit Transfer(msg.sender, to, value);
        return true;
    }

    function approve(address spender, uint value) external returns (bool) {
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function transferFrom(address from, address to, uint value) external returns (bool) {
        require(balanceOf[from] >= value, "Insufficient balance");
        require(allowance[from][msg.sender] >= value, "Not allowed");
        allowance[from][msg.sender] -= value;
        balanceOf[from] -= value;
        balanceOf[to] += value;
        emit Transfer(from, to, value);
        return true;
    }
}

部署后,你可以使用 Remix 的交互面板查看总供应、转账、授权等操作。这是理解 DeFi 应用的基础。

安全注意事项与最佳实践

常见漏洞与防范

  1. 重入攻击:攻击者在转账回调中重复调用合约。解决:使用“检查-效果-交互”模式,或添加重入锁修饰器。
  2. 整数溢出/下溢:0.8.0 之后编译器默认检查,但使用 unchecked 块时需谨慎。
  3. 访问控制缺陷:检查 onlyOwner 等修饰器是否正确,避免误用 tx.origin 做鉴权。
  4. 前端运行与抢先交易:依赖精确时间戳或区块高度可能导致问题,应使用随机性更强的方案或预言机。
  5. 未初始化存储指针:结构体、数组在 storage 中声明时需显式初始化,否则可能覆盖意外位置的存储。

开发习惯建议

  • 使用最新稳定版编译器,并采用 0.8.x 以获得默认安全检查。
  • 状态变量声明时一律赋予初始值。
  • 充分利用自定义错误节省 gas。
  • 编写完备的测试用例(Hardhat + Mocha 或 Truffle 测试)。
  • 主网部署前,务必在测试网(如 Sepolia)进行充分测试并经过安全审计。

学习路径与后续进阶

掌握本教程内容后,你已经具备编写基本智能合约的能力。下一步可以深入以下方向:

  • OpenZeppelin 合约库:使用经过审计的安全合约模板。
  • NFT 标准 (ERC-721/1155):学习非同质化代币的实现。
  • DeFi 协议:研究 Uniswap V2/V3 的代码结构,理解流动性池和 AMM。
  • 升级合约模式:代理模式(Proxy)与钻石标准(EIP-2535)。
  • Gas 优化:深入理解 EVM 操作码,优化存储和循环。
  • 预言机(Chainlink):将链下数据安全引入合约。

Solidity 的学习需要大量的编码实践,建议持续在 Remix 或本地框架中尝试编写、测试和部署合约,逐步积累经验。