RWA 代币化 —— Token 合约解析

RWAToken 是平台的核心代币合约,基于 ERC-20 标准扩展,支持治理投票、链下签名授权、黑名单管理等功能,并采用 UUPS 可升级模式。

1. 概述

RWAToken 继承了多个 OpenZeppelin 合约,组合成功能完整的 RWA 代币:

继承合约 功能
ERC20Upgradeable 基础代币功能
ERC20PermitUpgradeable EIP-2612 链下签名授权
ERC20VotesUpgradeable 治理投票功能
ERC20BurnableUpgradeable 代币销毁
AccessControlUpgradeable 角色权限控制
PausableUpgradeable 紧急暂停
ReentrancyGuardUpgradeable 重入防护
UUPSUpgradeable 可升级代理
1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────────────────────┐
│ RWATokenUpgradeable │
├─────────────────────────────────────────────────────────┤
│ ERC20 基础 │ Permit │ Votes │ Burnable │
├─────────────────────────────────────────────────────────┤
│ AccessControl │ Pausable │ ReentrancyGuard │
├─────────────────────────────────────────────────────────┤
│ UUPSUpgradeable │
└─────────────────────────────────────────────────────────┘

2. 角色与权限

2.1 角色定义

1
2
3
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
角色 权限
DEFAULT_ADMIN_ROLE 管理所有角色、升级合约、设置黑名单、更新参数
MINTER_ROLE 铸造代币
PAUSER_ROLE 暂停/恢复转账
BURNER_ROLE 销毁代币

2.2 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function initialize(
string memory name,
string memory symbol,
uint256 _maxSupply,
address owner,
string memory tokenURI_
) public initializer {
require(owner != address(0), "RWAToken: invalid owner address");
require(_maxSupply > 0, "RWAToken: max supply must be greater than 0");

__ERC20_init(name, symbol);
__ERC20Permit_init(name);
__ERC20Votes_init();
__ERC20Burnable_init();
__AccessControl_init();
__Pausable_init();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();

maxSupply = _maxSupply;
_tokenURI = tokenURI_;

// 初始化时将所有角色授予 owner
_grantRole(DEFAULT_ADMIN_ROLE, owner);
_grantRole(MINTER_ROLE, owner);
_grantRole(PAUSER_ROLE, owner);
_grantRole(BURNER_ROLE, owner);

emit MaxSupplyUpdated(_maxSupply);
emit TokenURIUpdated(tokenURI_);
}

参数说明

参数 说明
name 代币名称,如 “Real Estate Token”
symbol 代币符号,如 “RET”
_maxSupply 最大供应量(含精度)
owner 初始管理员地址
tokenURI_ 代币元数据 URI

3. 核心功能

3.1 铸造代币

mint

铸造代币到指定地址。

1
2
3
4
5
6
7
8
9
10
function mint(address to, uint256 amount)
external
onlyRole(MINTER_ROLE)
validAddress(to)
notBlacklisted(to)
nonReentrant
{
require(totalSupply() + amount <= maxSupply, "RWAToken: exceeds max supply");
_mint(to, amount);
}

安全检查

  • 仅 MINTER_ROLE 可调用
  • 接收地址不能为零地址
  • 接收地址不能在黑名单中
  • 不能超过最大供应量
  • 防止重入攻击

batchMint

批量铸造到多个地址,适用于代币分发场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function batchMint(
address[] calldata recipients,
uint256[] calldata amounts
) external onlyRole(MINTER_ROLE) nonReentrant {
require(recipients.length == amounts.length, "RWAToken: arrays length mismatch");

// 先计算总量,检查是否超限
uint256 totalAmount = 0;
for (uint256 i = 0; i < amounts.length; i++) {
totalAmount += amounts[i];
}
require(totalSupply() + totalAmount <= maxSupply, "RWAToken: exceeds max supply");

// 执行批量铸造
for (uint256 i = 0; i < recipients.length; i++) {
require(recipients[i] != address(0), "RWAToken: invalid recipient address");
require(!_blacklisted[recipients[i]], "RWAToken: recipient is blacklisted");
_mint(recipients[i], amounts[i]);
}
}

使用场景

1
2
3
4
认购结算后分发代币:
recipients = [Alice, Bob, Charlie]
amounts = [1000e18, 500e18, 2000e18]
→ 一次交易完成所有分发

3.2 销毁代币

burn(继承自 ERC20Burnable)

用户销毁自己的代币。

1
2
3
4
// 继承自 ERC20BurnableUpgradeable
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}

burnFrom

销毁指定地址的代币(需要 BURNER_ROLE)。

1
2
3
4
5
6
7
8
function burnFrom(address account, uint256 amount)
public
override
onlyRole(BURNER_ROLE)
validAddress(account)
{
super.burnFrom(account, amount);
}

注意:需要被销毁方预先 approve 足够的额度。

3.3 黑名单管理

黑名单用于冻结可疑账户或满足监管要求。

setBlacklisted

添加或移除单个地址的黑名单状态。

1
2
3
4
5
6
7
8
9
10
mapping(address => bool) private _blacklisted;

function setBlacklisted(address account, bool blacklistStatus)
external
onlyRole(DEFAULT_ADMIN_ROLE)
validAddress(account)
{
_blacklisted[account] = blacklistStatus;
emit BlacklistUpdated(account, blacklistStatus);
}

batchSetBlacklisted

批量更新黑名单状态。

1
2
3
4
5
6
7
8
9
10
11
12
function batchSetBlacklisted(
address[] calldata accounts,
bool[] calldata blacklistStatusArray
) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(accounts.length == blacklistStatusArray.length, "RWAToken: arrays length mismatch");

for (uint256 i = 0; i < accounts.length; i++) {
require(accounts[i] != address(0), "RWAToken: invalid address");
_blacklisted[accounts[i]] = blacklistStatusArray[i];
emit BlacklistUpdated(accounts[i], blacklistStatusArray[i]);
}
}

isBlacklisted

查询地址是否在黑名单中。

1
2
3
function isBlacklisted(address account) external view returns (bool) {
return _blacklisted[account];
}

3.4 转账限制

重写 _update 函数,在每次转账时检查:

1
2
3
4
5
6
7
8
9
function _update(address from, address to, uint256 value)
internal
override(ERC20Upgradeable, ERC20VotesUpgradeable)
whenNotPaused // 合约未暂停
notBlacklisted(from) // 发送方不在黑名单
notBlacklisted(to) // 接收方不在黑名单
{
super._update(from, to, value);
}

转账会被阻止的情况

  • 合约处于暂停状态
  • 发送方在黑名单中
  • 接收方在黑名单中

3.5 暂停功能

1
2
3
4
5
6
7
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}

function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}

使用场景

  • 发现安全漏洞时紧急暂停
  • 合约升级前暂停
  • 监管要求临时冻结

4. 治理投票(Votes)

RWAToken 继承 ERC20Votes,支持链上治理投票。

4.1 投票权委托

1
2
3
4
5
6
7
8
9
10
11
12
13
// 用户必须先委托投票权才能参与投票
// 可以委托给自己
function delegate(address delegatee) public virtual;

// 使用签名委托(无需发送交易)
function delegateBySig(
address delegatee,
uint256 nonce,
uint256 expiry,
uint8 v,
bytes32 r,
bytes32 s
) public virtual;

4.2 查询投票权

1
2
3
4
5
6
7
8
// 获取当前投票权
function getVotes(address account) public view returns (uint256);

// 获取历史某个时间点的投票权
function getPastVotes(address account, uint256 timepoint) public view returns (uint256);

// 获取历史某个时间点的总投票权
function getPastTotalSupply(uint256 timepoint) public view returns (uint256);

5. Permit 签名授权

支持 EIP-2612,允许通过链下签名完成 approve,节省 Gas。

1
2
3
4
5
6
7
8
9
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual;

使用场景

1
2
3
4
5
6
7
传统方式(两笔交易):
1. 用户调用 approve(spender, amount)
2. spender 调用 transferFrom(user, to, amount)

Permit 方式(一笔交易):
1. 用户链下签名生成 permit
2. spender 调用 permit(...) + transferFrom(...) 合并执行

6. 供应量管理

6.1 最大供应量

1
2
3
4
5
6
7
uint256 public maxSupply;

function updateMaxSupply(uint256 newMaxSupply) external onlyRole(DEFAULT_ADMIN_ROLE) {
require(newMaxSupply >= totalSupply(), "RWAToken: new max supply too low");
maxSupply = newMaxSupply;
emit MaxSupplyUpdated(newMaxSupply);
}

限制:新的 maxSupply 不能低于当前 totalSupply。

6.2 Token URI

1
2
3
4
5
6
7
8
9
10
string private _tokenURI;

function tokenURI() public view returns (string memory) {
return _tokenURI;
}

function setTokenURI(string memory newTokenURI) external onlyRole(DEFAULT_ADMIN_ROLE) {
_tokenURI = newTokenURI;
emit TokenURIUpdated(newTokenURI);
}

Token URI 指向代币元数据,通常是 IPFS 或链下服务器上的 JSON 文件。

7. 可升级性

7.1 UUPS 模式

RWAToken 使用 UUPS(Universal Upgradeable Proxy Standard)模式:

1
2
3
4
5
6
7
8
9
10
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers(); // 禁止实现合约被初始化
}

function _authorizeUpgrade(address newImplementation)
internal
override
onlyRole(DEFAULT_ADMIN_ROLE)
{}

7.2 版本管理

1
2
3
function version() external pure returns (string memory) {
return "1.0.0";
}

每次升级后应更新版本号,便于跟踪。

8. 事件

1
2
3
4
5
6
7
8
9
event BlacklistUpdated(address indexed account, bool isBlacklisted);
event MaxSupplyUpdated(uint256 newMaxSupply);
event TokenURIUpdated(string newTokenURI);

// 继承的事件
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Paused(address account);
event Unpaused(address account);

9. 安全考虑

9.1 重入防护

铸造函数使用 nonReentrant 修饰器,防止重入攻击。

9.2 供应量检查

所有铸造操作都检查 totalSupply() + amount <= maxSupply

9.3 零地址检查

1
2
3
4
modifier validAddress(address account) {
require(account != address(0), "RWAToken: invalid address");
_;
}

9.4 权限分离

不同操作需要不同角色,建议:

  • MINTER_ROLE:授予发行管理合约
  • PAUSER_ROLE:授予多签钱包
  • BURNER_ROLE:根据业务需要授予
  • DEFAULT_ADMIN_ROLE:使用多签钱包

10. 部署示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 部署实现合约
RWATokenUpgradeable implementation = new RWATokenUpgradeable();

// 2. 部署代理合约
ERC1967Proxy proxy = new ERC1967Proxy(
address(implementation),
abi.encodeWithSelector(
RWATokenUpgradeable.initialize.selector,
"Building A Token", // name
"BAT", // symbol
1000000 * 10**18, // maxSupply: 100万代币
adminAddress, // owner
"ipfs://Qm..." // tokenURI
)
);

// 3. 获取代理合约实例
RWATokenUpgradeable token = RWATokenUpgradeable(address(proxy));