Skip to main contentA-REX Core

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

ConstanteValorSignificado
OP_MINT1novas unidades (createBatch)
OP_TRANSFER2transferência iniciada pelo titular
OP_BURN3resgate, entrega, baixa por perda
OP_FORCED_TRANSFER4enforcement
OP_TRANSFORM5entradas (to = 0) e saídas (from = 0) de transform
HOOK_CHECK1o hook solicita check
HOOK_EXECUTED2o hook solicita onExecuted
HOOK_ELIGIBILITY4o hook responde a canSend / canReceive
STATUS_ACTIVE0padrão
STATUS_RETIRED255terminal

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 ARexInvalidBatch quando quantity == 0, commodity == 0, unit == 0 ou expiresAt != 0 && producedAt != 0 && expiresAt <= producedAt.
  • producedAt == 0 DEVE ser aceito (pré-colheita).
  • DEVE executar o pipeline com OP_MINT; DEVE emitir BatchCreated e, em seguida, TransferSingle.

setBatchStatus

  • DEVE ter acesso restrito por papel. 255 é terminal. Lotes baixados: sem cunhagem, sem transferências (ARexBatchRetired), queimas permitidas.

transform

  • msg.sender DEVE ser from ou ter sido aprovado para todos por from.
  • DEVE reverter com ARexLengthMismatch em 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) = inputIds e definir issuer = msg.sender nas saídas.
  • DEVE executar o pipeline com OP_TRANSFORM para cada queima e cada cunhagem, com data = abi.encode(processRef).
  • DEVE emitir BatchCreated por saída, os eventos do ERC-1155 e um único BatchTransformed.
  • 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

  • metadataHash DEVE ser o keccak256 do documento canônico em uri. O documento DEVERIA seguir standard/schema/batch-metadata.schema.json.
  • originRef NÃ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:

  • canTransfer DEVE ser falso quando pausado, quando o lote não existe ou foi baixado, quando a elegibilidade falha, quando amount > balance − frozen ou quando qualquer hook CHECK rejeita OP_TRANSFER.
  • Transferências públicas NÃO DEVEM ser bem-sucedidas quando canTransfer é falso.
  • forcedTransfer DEVE ter acesso restrito por papel, DEVE ignorar a elegibilidade, DEVE reduzir o valor congelado de modo que nunca exceda o saldo restante (emitindo Frozen), DEVE executar os hooks com OP_FORCED_TRANSFER, DEVE emitir ForcedTransfer e DEVERIA funcionar durante a pausa.
  • canSend / canReceive são a conjunção de todos os hooks HOOK_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

PropriedadeValor
BaseOpenZeppelin 5.3.0 atualizável: ERC1155, ERC1155Supply, AccessControl, Pausable, UUPS
Armazenamentonamespace ERC-7201 arex.storage.token, slot 0x8ed599b6be05dec05297c009a1279a71df771ad996fe924d0b5a47326ff60900
Compiladorsolc 0.8.22, viaIR, EVM paris; optimizer.runs = 1 para ARexToken (24,136 bytes implantados), 200 nos demais
PapéisISSUER, TRANSFORMER, STATUS, ENFORCER, HOOK_ADMIN, PAUSER, UPGRADER, DEFAULT_ADMIN; ATTEST_ADMIN em ARexAttestations
Reentrânciatrava de armazenamento em torno de toda a sequência de _update; exercitada pelo teste ReentrantHook
Encaminhamento de dadossafeTransferFrom / safeBatchTransferFrom / createBatch guardam data temporariamente para os hooks; transform repassa abi.encode(processRef)
Metadadosuri(id) retorna a URI do lote quando definida; caso contrário, a URI base
Testes46 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.