白皮书 · 05
技术规范
A-REX Core 的规范性摘要:一致性、接口、操作语义、钩子约定、证明、存储与升级规则。权威文本是 standard/AREX-CORE.md;本章为白皮书读者重述其内容。
一致性
符合一致性要求的代币必须实现 ERC-1155、ERC-165、IERC7943MultiToken(0x41c4fbad)、IARexBatch(0xe22639e0),并通过 IARexHookManager.getHooks()(0x964f4861)公开其管线。它应当直接或通过配套合约实现 IARexAttest(0xad603a6c)。符合一致性要求的钩子必须实现 IARexHook(0x332afcbb)和 ERC-165。所有 id 均可由 npm run interface-ids 重现。
固定常量
| 常量 | 取值 | 含义 |
|---|---|---|
OP_MINT | 1 | 新增单位(createBatch) |
OP_TRANSFER | 2 | 持有人发起的转移 |
OP_BURN | 3 | 赎回、交付、核销 |
OP_FORCED_TRANSFER | 4 | 强制执行 |
OP_TRANSFORM | 5 | transform 的输入(to = 0)和输出(from = 0) |
HOOK_CHECK | 1 | 钩子需要 check |
HOOK_EXECUTED | 2 | 钩子需要 onExecuted |
HOOK_ELIGIBILITY | 4 | 钩子回答 canSend / canReceive |
STATUS_ACTIVE | 0 | 默认 |
STATUS_RETIRED | 255 | 终态 |
其余一切——商品代码、单位代码、状态 1..254、拒绝代码、证明主题——都是开放的。推荐的约定见 ARexCodes。
第 1 层:IARexBatch
interface IARexBatch is IERC165 {
struct BatchInit { bytes32 commodity; bytes32 unit; uint64 producedAt; uint64 expiresAt; bytes32 originRef; bytes32 metadataHash; string uri; }
struct Batch { address issuer; uint64 createdAt; uint8 status; bytes32 commodity; bytes32 unit; uint64 producedAt; uint64 expiresAt; bytes32 originRef; bytes32 metadataHash; string uri; }
event BatchCreated(uint256 indexed id, address indexed issuer, bytes32 indexed commodity, bytes32 unit, uint256 quantity, address to);
event BatchStatusChanged(uint256 indexed id, uint8 previousStatus, uint8 newStatus, bytes32 reason);
event BatchMetadataUpdated(uint256 indexed id, bytes32 metadataHash, string uri);
event BatchTransformed(address indexed operator, uint256[] inputIds, uint256[] inputAmounts, uint256[] outputIds, uint256[] outputAmounts, bytes32 processRef);
error ARexBatchNotFound(uint256 id);
error ARexBatchRetired(uint256 id);
error ARexInvalidBatch(bytes32 reason);
error ARexInvalidStatusTransition(uint256 id, uint8 from, uint8 to);
error ARexLengthMismatch();
function createBatch(BatchInit calldata init, uint256 quantity, address to, bytes calldata data) external returns (uint256 id);
function batchOf(uint256 id) external view returns (Batch memory);
function batchExists(uint256 id) external view returns (bool);
function totalSupply(uint256 id) external view returns (uint256);
function setBatchStatus(uint256 id, uint8 status, bytes32 reason) external;
function setBatchMetadata(uint256 id, bytes32 metadataHash, string calldata uri) external;
function transform(address from, address to, uint256[] calldata inputIds, uint256[] calldata inputAmounts, BatchInit[] calldata outputs, uint256[] calldata outputAmounts, bytes32 processRef) external returns (uint256[] memory outputIds);
function parentsOf(uint256 id) external view returns (uint256[] memory);
}
语义
createBatch
- 必须限于获授权的账户;授权不得源自代币余额。
- 当
quantity == 0、commodity == 0、unit == 0,或expiresAt != 0 && producedAt != 0 && expiresAt <= producedAt时,必须以ARexInvalidBatch回滚。 producedAt == 0必须被接受(收获前)。- 必须以
OP_MINT运行管线;必须先发出BatchCreated,再发出TransferSingle。
setBatchStatus
- 必须限于特定角色。
255是终态。已注销的批次:不可铸造,不可转移(ARexBatchRetired),允许销毁。
transform
msg.sender必须是from,或已获from的全部授权。- 长度不一致时,必须以
ARexLengthMismatch回滚;输入列表或输出列表为空时,必须回滚。 - 必须销毁输入、铸造输出、设置
parentsOf(output) = inputIds,并在输出上设置issuer = msg.sender。 - 对每一次销毁和每一次铸造,必须以
OP_TRANSFORM运行管线,且data = abi.encode(processRef)。 - 必须为每个输出发出
BatchCreated、发出 ERC-1155 事件,并发出一个BatchTransformed。 - 并不要求数量守恒。参考实现区分重新分批(商品和单位相同,
Σin == Σout;任何持有人均可)与加工(TRANSFORMER_ROLE)。
metadata
metadataHash必须是位于uri的规范文档的keccak256。该文档应当遵循standard/schema/batch-metadata.schema.json。originRef不得包含原始坐标或自然人的标识符。
第 0 层:IERC7943MultiToken
原样取自 ERC-7943(forcedTransfer、setFrozenTokens、canSend、canReceive、getFrozenTokens、canTransfer;事件 ForcedTransfer、Frozen;错误 ERC7943CannotSend、ERC7943CannotReceive、ERC7943CannotTransfer、ERC7943InsufficientUnfrozenBalance)。A-REX 增加了以下澄清:
- 在已暂停、批次不存在或已注销、资格检查失败、
amount > balance − frozen,或任何 CHECK 钩子拒绝OP_TRANSFER时,canTransfer必须为 false。 - 在
canTransfer为 false 的情形下,公开转移不得成功。 forcedTransfer必须限于特定角色,必须绕过资格检查,必须下调冻结数量使其绝不超过剩余余额(并发出Frozen),必须以OP_FORCED_TRANSFER运行钩子,必须发出ForcedTransfer,并且应当在暂停期间也能工作。canSend/canReceive是所有HOOK_ELIGIBILITY钩子的合取。- 代币应当公开
transferCheck(from, to, id, amount) → bytes32。
第 2 层:钩子
interface IARexHook is IERC165 {
error ARexHookRejected(address hook, bytes32 code);
function hookFlags() external view returns (uint8);
function check(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external view returns (bytes32 code);
function onExecuted(uint8 op, address operator, address from, address to, uint256 id, uint256 amount, bytes calldata data) external;
function canSend(address account) external view returns (bool);
function canReceive(address account) external view returns (bool);
}
interface IARexHookManager {
event HookRegistered(address indexed hook, uint8 flags, uint256 position);
event HookRemoved(address indexed hook);
error ARexHookAlreadyRegistered(address hook);
error ARexHookNotRegistered(address hook);
error ARexInvalidHook(address hook);
function getHooks() external view returns (address[] memory);
function hookFlagsOf(address hook) external view returns (uint8);
function registerHook(address hook) external;
function removeHook(address hook) external;
}
代币的义务:维护有序列表;在注册时验证 supportsInterface(0x332afcbb) 并缓存非零标志;对每个 id、每个 CHECK 钩子调用 check,并在任何余额变动之前,于第一个非零代码处以 ARexHookRejected(hook, code) 回滚;在余额更新之后,对每个 id、每个 EXECUTED 钩子调用 onExecuted;在整个序列期间防止重入;转发 data;保持列表和标志公开。
钩子的义务:标志为常量;check 是返回代码的视图函数,不会因政策原因而回滚;调用方代币即 msg.sender;对 OP_FORCED_TRANSFER 返回 0,除非该规则本身就规范强制执行;onExecuted 绝不调用会改变余额的函数。
第 3 层:IARexAttest
interface IARexAttest is IERC165 {
struct Attestation { address issuer; bytes32 ref; uint64 issuedAt; uint64 expiresAt; bool revoked; }
event Attested(uint256 indexed id, bytes32 indexed topic, address indexed issuer, bytes32 ref, uint64 expiresAt);
event AttestationRevoked(uint256 indexed id, bytes32 indexed topic, address indexed issuer, bytes32 reason);
event TrustedIssuerUpdated(bytes32 indexed topic, address indexed issuer, bool trusted);
error ARexUntrustedIssuer(bytes32 topic, address issuer);
error ARexAttestationNotFound(uint256 id, bytes32 topic);
function attest(uint256 id, bytes32 topic, bytes32 ref, uint64 expiresAt) external;
function revokeAttestation(uint256 id, bytes32 topic, bytes32 reason) external;
function getAttestation(uint256 id, bytes32 topic) external view returns (Attestation memory);
function isAttested(uint256 id, bytes32 topic) external view returns (bool);
function isTrustedIssuer(bytes32 topic, address issuer) external view returns (bool);
}
attest 要求调用方是该主题的可信签发方,并且批次已存在。证明不存在、已被撤销、已到期,或签发方不再受信任时,isAttested 为 false。签发方撤销自己的证明;管理员可以撤销任何证明。
拒绝代码(推荐)
keccak256("AREX:" + NAME),适用于 PAUSED、FROZEN、SENDER_INELIGIBLE、RECEIVER_INELIGIBLE、SANCTIONED、BATCH_NOT_FOUND、BATCH_RETIRED、BATCH_INACTIVE、BATCH_EXPIRED、BATCH_NOT_PRODUCED、LIMIT_EXCEEDED、DOCUMENT_MISSING、SEASON_CLOSED、IN_TRANSIT、ATTESTATION_MISSING。自定义代码:keccak256("NAMESPACE:NAME")。
参考实现概况
| 属性 | 取值 |
|---|---|
| 基础 | OpenZeppelin 5.3.0 可升级版:ERC1155、ERC1155Supply、AccessControl、Pausable、UUPS |
| 存储 | ERC-7201 命名空间 arex.storage.token,槽位 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900 |
| 编译器 | solc 0.8.22、viaIR、EVM paris;ARexToken 使用 optimizer.runs = 1(部署后 24,136 字节),其他合约为 200 |
| 角色 | ISSUER、TRANSFORMER、STATUS、ENFORCER、HOOK_ADMIN、PAUSER、UPGRADER、DEFAULT_ADMIN;ARexAttestations 上另有 ATTEST_ADMIN |
| 重入 | 围绕整个 _update 序列的存储锁;由 ReentrantHook 测试覆盖 |
| 数据转发 | safeTransferFrom / safeBatchTransferFrom / createBatch 为钩子暂存 data;transform 传递 abi.encode(processRef) |
| 元数据 | 设置了批次 URI 时,uri(id) 返回该 URI,否则返回基础 URI |
| 测试 | test/standard 中有 46 项:生命周期、谱系、冻结与强制转移、钩子顺序、拒绝、数据与重入、证明、接口 id、升级授权、领域钩子、扩展、随机化不变量,以及端到端执行的实施指南 |
标准的版本管理
对签名、事件或任何“必须”要求的更改都属于主版本,并附迁移说明。增量性的建议(代码、模式字段)属于次版本。只有在两个独立的实现者确认采用之后,更改才会被接受。在一次生产部署运行满六个月、出现三个独立的钩子实现并公布第三方审计之前,这些接口不会被作为 ERC 提交。