Skip to main contentA-REX Core

白皮书 · 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_MINT1新增单位(createBatch)
OP_TRANSFER2持有人发起的转移
OP_BURN3赎回、交付、核销
OP_FORCED_TRANSFER4强制执行
OP_TRANSFORM5transform 的输入(to = 0)和输出(from = 0)
HOOK_CHECK1钩子需要 check
HOOK_EXECUTED2钩子需要 onExecuted
HOOK_ELIGIBILITY4钩子回答 canSend / canReceive
STATUS_ACTIVE0默认
STATUS_RETIRED255终态

其余一切——商品代码、单位代码、状态 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 提交。