Whitepaper · 05
Especificação Técnica
Resumo normativo do A-REX Core: conformidade, interfaces, semântica das operações, o contrato de hook, atestações, armazenamento e regras de atualização. O texto de referência é standard/AREX-CORE.md; este capítulo o reapresenta aos leitores do whitepaper.
Conformidade
Um token conforme DEVE implementar o ERC-1155, o ERC-165, IERC7943MultiToken (0x41c4fbad), IARexBatch (0xe22639e0) e expor seu pipeline por meio de IARexHookManager.getHooks() (0x964f4861). DEVERIA implementar IARexAttest (0xad603a6c) diretamente ou por meio de um contrato complementar. Um hook conforme DEVE implementar IARexHook (0x332afcbb) e o ERC-165. Todos os identificadores de interface são reproduzidos por npm run interface-ids.
Constantes fixas
| Constante | Valor | Significado |
|---|---|---|
OP_MINT | 1 | novas unidades (createBatch) |
OP_TRANSFER | 2 | transferência iniciada pelo titular |
OP_BURN | 3 | resgate, entrega, baixa por perda |
OP_FORCED_TRANSFER | 4 | enforcement |
OP_TRANSFORM | 5 | entradas (to = 0) e saídas (from = 0) de transform |
HOOK_CHECK | 1 | o hook solicita check |
HOOK_EXECUTED | 2 | o hook solicita onExecuted |
HOOK_ELIGIBILITY | 4 | o hook responde a canSend / canReceive |
STATUS_ACTIVE | 0 | padrão |
STATUS_RETIRED | 255 | terminal |
Todo o resto — códigos de commodity, códigos de unidade, status de 1..254, códigos de rejeição, tópicos de atestação — é aberto. As convenções recomendadas estão em ARexCodes.
Camada 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);
}
Semântica
createBatch
- DEVE ter acesso restrito a contas autorizadas; a autorização NÃO DEVE derivar do saldo de tokens.
- DEVE reverter com
ARexInvalidBatchquandoquantity == 0,commodity == 0,unit == 0ouexpiresAt != 0 && producedAt != 0 && expiresAt <= producedAt. producedAt == 0DEVE ser aceito (pré-colheita).- DEVE executar o pipeline com
OP_MINT; DEVE emitirBatchCreatede, em seguida,TransferSingle.
setBatchStatus
- DEVE ter acesso restrito por papel.
255é terminal. Lotes baixados: sem cunhagem, sem transferências (ARexBatchRetired), queimas permitidas.
transform
msg.senderDEVE serfromou ter sido aprovado para todos porfrom.- DEVE reverter com
ARexLengthMismatchem caso de comprimentos inconsistentes e DEVE reverter com listas de entradas ou de saídas vazias. - DEVE queimar as entradas, cunhar as saídas, definir
parentsOf(output) = inputIdse definirissuer = msg.sendernas saídas. - DEVE executar o pipeline com
OP_TRANSFORMpara cada queima e cada cunhagem, comdata = abi.encode(processRef). - DEVE emitir
BatchCreatedpor saída, os eventos do ERC-1155 e um únicoBatchTransformed. - A conservação de quantidade NÃO é exigida. A referência distingue o reagrupamento (mesma commodity e mesma unidade,
Σin == Σout; qualquer titular) do processamento (TRANSFORMER_ROLE).
metadata
metadataHashDEVE ser okeccak256do documento canônico emuri. O documento DEVERIA seguirstandard/schema/batch-metadata.schema.json.originRefNÃO DEVE conter coordenadas brutas nem identificadores de pessoas naturais.
Camada 0 — IERC7943MultiToken
Literal do ERC-7943 (forcedTransfer, setFrozenTokens, canSend, canReceive, getFrozenTokens, canTransfer; eventos ForcedTransfer, Frozen; erros ERC7943CannotSend, ERC7943CannotReceive, ERC7943CannotTransfer, ERC7943InsufficientUnfrozenBalance). O A-REX acrescenta os seguintes esclarecimentos:
canTransferDEVE ser falso quando pausado, quando o lote não existe ou foi baixado, quando a elegibilidade falha, quandoamount > balance − frozenou quando qualquer hook CHECK rejeitaOP_TRANSFER.- Transferências públicas NÃO DEVEM ser bem-sucedidas quando
canTransferé falso. forcedTransferDEVE ter acesso restrito por papel, DEVE ignorar a elegibilidade, DEVE reduzir o valor congelado de modo que nunca exceda o saldo restante (emitindoFrozen), DEVE executar os hooks comOP_FORCED_TRANSFER, DEVE emitirForcedTransfere DEVERIA funcionar durante a pausa.canSend/canReceivesão a conjunção de todos os hooksHOOK_ELIGIBILITY.- Um token DEVERIA expor
transferCheck(from, to, id, amount) → bytes32.
Camada 2 — hooks
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;
}
Obrigações do token: lista ordenada; verificar supportsInterface(0x332afcbb) e armazenar em cache as flags diferentes de zero no registro; chamar check por id e por hook CHECK e reverter com ARexHookRejected(hook, code) no primeiro código diferente de zero, antes de qualquer alteração de saldo; chamar onExecuted por id e por hook EXECUTED após os saldos; impedir reentrância durante a sequência; encaminhar data; manter a lista e as flags públicas.
Obrigações do hook: flags constantes; check é uma view que retorna códigos e nunca reverte por motivo de política; o token chamador é msg.sender; retornar 0 para OP_FORCED_TRANSFER, a menos que a regra trate de enforcement; onExecuted nunca chama funções que alterem saldos.
Camada 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 exige um emissor confiável para o tópico e um lote existente. isAttested é falso se a atestação estiver ausente, revogada, expirada ou se o emissor deixou de ser confiável. Os emissores revogam suas próprias atestações; um administrador PODE revogar qualquer uma.
Códigos de rejeição (recomendados)
keccak256("AREX:" + NAME) para 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. Códigos personalizados: keccak256("NAMESPACE:NAME").
Fatos da implementação de referência
| Propriedade | Valor |
|---|---|
| Base | OpenZeppelin 5.3.0 atualizável: ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS |
| Armazenamento | namespace ERC-7201 arex.storage.token, slot 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900 |
| Compilador | solc 0.8.22, viaIR, EVM paris; optimizer.runs = 1 para ARexToken (24,136 bytes implantados), 200 nos demais |
| Papéis | ISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN; ATTEST_ADMIN em ARexAttestations |
| Reentrância | trava de armazenamento em torno de toda a sequência de _update; exercitada pelo teste ReentrantHook |
| Encaminhamento de dados | safeTransferFrom / safeBatchTransferFrom / createBatch guardam data temporariamente para os hooks; transform repassa abi.encode(processRef) |
| Metadados | uri(id) retorna a URI do lote quando definida; caso contrário, a URI base |
| Testes | 46 em test/standard: ciclo de vida, linhagem, congelamento e transferência forçada, ordem dos hooks, rejeição, dados e reentrância, atestações, identificadores de interface, autorização de atualização, hooks de domínio, extensões, invariantes randomizadas e o Guia de Implementação executado de ponta a ponta |
Versionamento do padrão
Qualquer alteração em uma assinatura, em um evento ou em um requisito DEVE constitui uma versão maior, com nota de migração. Recomendações aditivas (códigos, campos do esquema) constituem versões menores. As alterações só são aceitas quando dois implementadores independentes confirmam a adoção. As interfaces não serão submetidas como ERC antes que uma implantação em produção tenha operado por seis meses, existam três implementações independentes de hooks e uma auditoria de terceiros seja publicada.