3、Solidity快速入门:开发环境搭建(Remix + Hardhat)、合约结构、数据类型、函数与修饰符

好,咱们正式开始写代码了。这一章我带你搭好环境,再快速过一遍 Solidity 的核心语法。别怕,这些东西你每天都会用,熟得很。

3.1 开发环境:Remix 和 Hardhat

写 Solidity 有两种主流方式。我个人习惯分场景用:

  • Remix:浏览器 IDE,零配置。适合快速验证想法、写小合约、教学演示。
  • Hardhat:本地开发框架。适合正式项目、团队协作、自动化测试和部署。

3.1.1 Remix 上手

打开 remix.ethereum.org,你就能看到编辑器、文件管理器、编译器和部署面板。我建议你先把左侧的「Solidity 编译器」版本选成 0.8.20 或以上——现在新项目基本都用这个。

小技巧: Remix 的「Auto compile」建议勾上。每次保存文件自动编译,省得你手动点。

3.1.2 Hardhat 初始化

本地开发我几乎只用 Hardhat。它比 Truffle 轻量,而且内置了网络模拟和测试框架。初始化命令很简单:

mkdir my-nft-project
cd my-nft-project
npm init -y
npm install --save-dev hardhat
npx hardhat init

选「Create an empty hardhat.config.js」。嗯,这里要注意,如果你用 TypeScript,可以选 TS 模板,但我个人觉得 JS 起步更快。

装完后再加几个常用插件:

npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install --save-dev dotenv

hardhat-toolbox 集成了测试、部署、验证等常用功能。我在项目中一直这么配,省心。

3.2 合约结构:一个 Solidity 文件长什么样

写合约就像写一个类。我带你拆开看:

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

contract MyNFT {
    // 状态变量
    string public name;
    uint256 public totalSupply;

    // 构造函数
    constructor(string memory _name) {
        name = _name;
    }

    // 函数
    function mint() public {
        totalSupply += 1;
    }
}

从上到下依次是:

  • SPDX 许可证声明:必须写,否则编译器会警告。我一般用 MIT。
  • Pragma 版本:告诉编译器用哪个 Solidity 版本。^0.8.20 表示兼容 0.8.20 到 0.8.x。
  • contract 关键字:定义合约,类似 class。
  • 状态变量:存在链上的数据,永久保存。
  • 构造函数:部署时执行一次,用来初始化。
  • 函数:合约的行为逻辑。
注意: 状态变量默认是 internal 可见性。如果你想让外部读取,记得加 public。我曾经因为忘加 public,调试了半天才发现前端读不到数据。

3.3 数据类型:Solidity 的变量家族

Solidity 是静态类型语言,每个变量都得声明类型。我按使用频率给你列一下:

3.3.1 值类型

类型 说明 示例
bool 布尔值 bool public isActive = true;
uint 无符号整数(默认 uint256) uint256 public count = 100;
int 有符号整数 int256 public balance = -50;
address 以太坊地址(20字节) address public owner = msg.sender;
bytes32 固定大小字节数组 bytes32 public hash;

你想想看,为什么 uint 用得最多?因为以太坊上的数值几乎都是非负的——代币数量、余额、ID,全是正数。

3.3.2 引用类型

类型 说明 示例
string 动态字符串 string public name = "MyNFT";
bytes 动态字节数组 bytes public data;
array 数组 uint256[] public ids;
mapping 键值映射 mapping(address => uint256) public balances;
struct 自定义结构体 struct User { string name; uint age; }
重点: mapping 是 Solidity 的灵魂。NFT 合约里,我们用它来记录「谁拥有哪个 Token」——mapping(uint256 => address)。这个模式你会在后面反复见到。

3.4 函数与修饰符

函数是合约的「动作」。修饰符则是给函数加「前置条件」的利器。

3.4.1 函数的基本写法

function transfer(address to, uint256 amount) public returns (bool) {
    // 逻辑
    return true;
}

函数结构:function 函数名(参数列表) 可见性 状态可变性 returns (返回值类型)

可见性有四种:

  • public:内外都能调
  • internal:本合约和子合约能调
  • external:只能外部调(省 gas)
  • private:仅本合约能调

状态可变性:

  • view:只读,不修改状态
  • pure:既不读也不写状态
  • payable:可以接收 ETH
省 gas 小技巧: 如果函数不需要在合约内部调用,用 external 代替 public。参数用 calldata 代替 memory。我在优化 gas 时经常这么改,效果很明显。

3.4.2 修饰符(modifier)

修饰符用来封装「检查逻辑」。最常见的场景是权限控制:

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

function withdraw() public onlyOwner {
    // 只有 owner 能调用
    payable(owner).transfer(address(this).balance);
}

你看,onlyOwner 在函数执行前检查了调用者身份。那个 _ 是占位符,表示「继续执行原函数」。我刚开始写的时候老忘加 _,结果函数体永远不执行——嗯,踩过这个坑。

3.4.3 常用内置函数

函数 说明
require(条件, "错误信息") 条件不满足则回滚,退 gas
revert("错误信息") 主动回滚
assert(条件) 检查内部错误,不退 gas(少用)
msg.sender 当前调用者地址
block.timestamp 当前区块时间戳
避坑指南: 我曾经在项目里用 block.timestamp 做随机数,结果被矿工操纵了。记住,链上时间戳和区块哈希都不适合做随机源。后面讲 NFT 铸造时我会细说。

3.5 快速上手:写一个最简单的合约

来,咱们在 Remix 里跑一下。新建文件 SimpleStorage.sol

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

contract SimpleStorage {
    uint256 private data;

    function set(uint256 _data) public {
        data = _data;
    }

    function get() public view returns (uint256) {
        return data;
    }
}

编译 -> 部署 -> 调用 set(42) -> 再调用 get()。看到返回 42 了吗?恭喜,你的第一个智能合约跑通了。

这一章的内容够你起步了。环境搭好,语法过了一遍,还亲手部署了一个合约。下一章我们开始写真正的 NFT 合约——ERC-721 标准。到时候你会看到,刚才学的 mappingmodifierrequire 全都会用上。

公众号:蓝海资料掘金营,微信deep3321