Skip to content

Remove EntityRegistry Smart-Contract #93

@arrivets

Description

@arrivets

Motivation

The EntityRegistry contract is a thin Solidity shim — it validates
ownership / liveness / attribute charset, emits the EntityOperation event,
then forwards an OpRecord[] batch to the precompile. Profiling against the
direct-precompile baseline (just profile-create-compare) shows the
contract adds substantial wall-clock per tx for behavior the precompile is
already in the position to do natively. See commit 9670727
("feat(profiling): direct-precompile baseline + percentile timing") for
methodology.

Scope

  • Reduce contracts/src/EntityRegistry.sol to an interface:
    • interface IEntityRegistry { function execute(Operation[]) external; }
    • keep the Operation / Attribute / Mime128 / Ident32 /
      BlockNumber32 structs, op-type constants, and the
      EntityOperation event signature — these are the SDK ABI surface.
    • delete all implementation (per-op handlers, ownership / liveness
      validation, internal mappings, precompile CALL).
  • Drop the bytecode bake in crates/arkiv-genesis
    (entity_registry_runtime_code(), entity_registry_account(), the
    predeploy entry in genesis_alloc()).
  • Move the deleted Solidity logic into the precompile
    (crates/arkiv-node/src/precompile.rs):
    • per-op validation (ownership, liveness, attribute charset)
    • EntityOperation event emission
    • OpRecord accumulation → direct dispatch into arkiv-entitydb
  • Register the precompile at ENTITY_REGISTRY_ADDRESS (today
    ARKIV_PRECOMPILE_ADDRESS). Drop ARKIV_PRECOMPILE_ADDRESS. Callers'
    txs continue to target the same address with the same calldata.
  • Replace the precompile's input.caller == ENTITY_REGISTRY_ADDRESS
    check with per-op authorization on input.caller directly:
    • CREATE: open to any EOA
    • SET_ATTRIBUTE / REMOVE_ATTRIBUTE / EXTEND_LIFETIME / CHANGE_OWNER /
      DELETE: input.caller must equal the entity's stored owner
      (DELETE additionally allowed for anyone after expiresAt, per Permissionless DELETE on expiry #90)
  • Keep the DELEGATECALL / CALLCODE / STATICCALL guards — they're
    properties of how the call is made, not who makes it.

API compatibility

External ABI preserved — same execute(Operation[]) selector, same
EntityOperation event signature, same destination address. SDKs
(arkiv-bindings, arkiv-sdk-js) keep codegenning from
EntityRegistry.sol; nothing changes for callers.

Security posture

Identical. Database chains forbid arbitrary user-deployed contracts and
disable EIP-7702 (no delegated EOAs), so input.caller is by construction
always an EOA — the same value EntityRegistry's msg.sender resolves to,
one hop earlier. Trust chain becomes:

signed tx → EVM signature validation → input.caller → precompile

instead of:

signed tx → EVM signature validation → EntityRegistry's msg.sender
          → OpRecord.sender → precompile

One fewer trusted component, same guarantees.

Touch points

  • crates/arkiv-node/src/precompile.rs (main locus — gains validation,
    per-op auth, event emission; registered at ENTITY_REGISTRY_ADDRESS)
  • crates/arkiv-node/src/evm.rs (precompile registration address)
  • contracts/src/EntityRegistry.sol (gutted to interface-only)
  • crates/arkiv-genesis/src/lib.rs (drop runtime-code bake and the
    entity_registry_account predeploy; ENTITY_REGISTRY_ADDRESS constant
    kept and now refers to the precompile slot)
  • e2e/ (existing tests should pass unchanged)
  • justfile (contracts-build recipe can drop the artifact step if
    nothing else needs compiled bytecode)

Acceptance criteria

  • All existing e2e tests pass without modification (ABI parity).
  • just profile-create-compare shows the contract-routed and direct-
    precompile paths converge — they're now the same path.
  • Per-op authorization tests: CREATE from any EOA succeeds; non-owner
    SET_ATTRIBUTE / DELETE / EXTEND_LIFETIME / CHANGE_OWNER reverts with
    the same error shape as today.
  • DELEGATECALL / STATICCALL / value-bearing calls remain rejected.

Relation to #88 (fee system)

Independent but adjacent. With the contract gone, #87 ("Implement Fee
Function in EntityRegistry Precompile") lives in a precompile that's
already the single source of truth — no contract-side fee logic to
coordinate, no duplicate authorization layer to update. Best landed
before #87 to avoid churning the precompile while #87 is in flight.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions