基本定义
本节是我们合约的开头,我们首先来介绍下合约的文件结构。一个单个合约文件的结构需要清晰、有条理,便于阅读、理解和维护。
文件头部声明
SPDX-License 标识符
- 用于声明合约的许可证类型(MIT、Apache-2.0 等)。
- 非强制,但强烈推荐,尤其在开源项目中。在 Remix 中如果不声明这个标识符会报警告
- 格式:
// SPDX-License-Identifier: <license-name>
。
示例:
arduino
// SPDX-License-Identifier: MIT
编译器版本声明
- 指定 Solidity 的编译器版本范围。
- 推荐使用
^
或>= <
,避免版本不兼容问题。
示例:
ini
pragma solidity ^0.8.0;
随着 solidity 编译器版本的升级,一些较早时间写的合约使用新版本的编译器编译会出现报错的情况,因此,合约头部声明需要指定版本范围。
导入依赖
- 文件依赖 :使用
import
语句导入其他合约、库或接口。 - 路径选择:
-
- 使用相对路径导入项目内文件:
./
。 - 使用绝对路径导入第三方库:
@openzeppelin/contracts/...
。
- 使用相对路径导入项目内文件:
示例:
arduino
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "./libraries/SafeMath.sol";
import "./interfaces/IUniswapV2Router.sol";
合约定义
在文件中声明主要合约,以下是常见的内容组织方式:
状态变量
- 描述合约的全局状态。
- 一般按以下顺序排列:
-
- 公共变量 :使用 public 修饰,如
address public owner
。 - 私有变量 :使用 private 修饰 如
uint256 private _balance
。 - 常量 :使用
constant
或immutable
声明。 - 映射或复杂结构 :如
mapping
或struct
。
- 公共变量 :使用 public 修饰,如
示例:
java
contract MyContract {
address public owner; // 合约的管理员
uint256 public totalSupply; // 总供应量
mapping(address => uint256) private balances; // 用户余额
uint256 public constant FEE_RATE = 2; // 手续费率
bytes32 public immutable DOMAIN_SEPARATOR; // EIP-712 域分隔符
}
事件声明
- 用于定义日志信息。
- 一般用于记录合约中的关键操作(如转账、状态变更)。
- 使用
indexed
关键字支持事件过滤。
示例:
css
event Transfer(address indexed from, address indexed to, uint256 amount);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
构造函数
- 用于初始化合约状态变量。
- 仅在合约部署时调用一次。
- 通常在构造函数中设置管理员(
owner
)。
示例:
ini
constructor(uint256 _initialSupply) {
owner = msg.sender; // 部署者为管理员
totalSupply = _initialSupply;
}
修饰符
- 修饰符是对函数调用的访问控制或预检查。
- 使用
modifier
定义,_
表示修饰符内插入函数代码。
示例:
javascript
modifier onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}
modifier validAddress(address _addr) {
require(_addr != address(0), "Invalid address");
_;
}
函数定义
函数分为以下几类,按顺序排列更清晰:
- 公共函数(
external
或public
) :
-
- 直接与外部交互的接口。
- 通常是合约的主要功能。
- 内部函数(
internal
或private
) :
-
- 用于辅助逻辑,其他函数可调用。
- 视图函数(
view
或pure
) :
-
- 不修改状态,用于查询。
- 支付函数(
payable
) :
-
- 允许接收
ETH
。
- 允许接收
- 回退函数(
fallback
和receive
) :
-
- 用于处理直接发送
ETH
的情况。
- 用于处理直接发送
示例:
scss
function transfer(address to, uint256 amount) external onlyOwner {
require(to != address(0), "Invalid address");
require(amount <= totalSupply, "Insufficient balance");
totalSupply -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
function balanceOf(address account) external view returns (uint256) {
return balances[account];
}
fallback() external payable {
// 回退函数逻辑
}
可复用逻辑
- 使用库或内部函数封装逻辑。
- 尽量避免代码重复。
示例:
css
function _safeTransfer(address to, uint256 amount) internal {
require(to != address(0), "Invalid address");
balances[to] += amount;
}
文件末尾
确保文件末尾有明确结束:
- 如果合约较长,可以加注释标记结束。
示例:
arduino
// End of MyContract