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));
|