3、Solidity快速入门:开发环境搭建(Remix + Hardhat)、合约结构、数据类型、函数与修饰符
好,咱们正式开始写代码了。这一章我带你搭好环境,再快速过一遍 Solidity 的核心语法。别怕,这些东西你每天都会用,熟得很。
3.1 开发环境:Remix 和 Hardhat
写 Solidity 有两种主流方式。我个人习惯分场景用:
- Remix:浏览器 IDE,零配置。适合快速验证想法、写小合约、教学演示。
- Hardhat:本地开发框架。适合正式项目、团队协作、自动化测试和部署。
3.1.1 Remix 上手
打开 remix.ethereum.org,你就能看到编辑器、文件管理器、编译器和部署面板。我建议你先把左侧的「Solidity 编译器」版本选成 0.8.20 或以上——现在新项目基本都用这个。
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
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 标准。到时候你会看到,刚才学的 mapping、modifier、require 全都会用上。
公众号:蓝海资料掘金营,微信deep3321