You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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
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.
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.
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 thecontract 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
interface IEntityRegistry { function execute(Operation[]) external; }Operation/Attribute/Mime128/Ident32/BlockNumber32structs, op-type constants, and theEntityOperationevent signature — these are the SDK ABI surface.validation, internal mappings, precompile CALL).
(
entity_registry_runtime_code(),entity_registry_account(), thepredeploy entry in
genesis_alloc()).(crates/arkiv-node/src/precompile.rs):
ENTITY_REGISTRY_ADDRESS(todayARKIV_PRECOMPILE_ADDRESS). DropARKIV_PRECOMPILE_ADDRESS. Callers'txs continue to target the same address with the same calldata.
input.caller == ENTITY_REGISTRY_ADDRESScheck with per-op authorization on
input.callerdirectly:DELETE:
input.callermust equal the entity's stored owner(DELETE additionally allowed for anyone after
expiresAt, per Permissionless DELETE on expiry #90)properties of how the call is made, not who makes it.
API compatibility
External ABI preserved — same
execute(Operation[])selector, sameEntityOperationevent signature, same destination address. SDKs(
arkiv-bindings,arkiv-sdk-js) keep codegenning fromEntityRegistry.sol; nothing changes for callers.
Security posture
Identical. Database chains forbid arbitrary user-deployed contracts and
disable EIP-7702 (no delegated EOAs), so
input.calleris by constructionalways an EOA — the same value EntityRegistry's
msg.senderresolves to,one hop earlier. Trust chain becomes:
instead of:
One fewer trusted component, same guarantees.
Touch points
per-op auth, event emission; registered at ENTITY_REGISTRY_ADDRESS)
entity_registry_account predeploy; ENTITY_REGISTRY_ADDRESS constant
kept and now refers to the precompile slot)
contracts-buildrecipe can drop the artifact step ifnothing else needs compiled bytecode)
Acceptance criteria
just profile-create-compareshows the contract-routed and direct-precompile paths converge — they're now the same path.
SET_ATTRIBUTE / DELETE / EXTEND_LIFETIME / CHANGE_OWNER reverts with
the same error shape as today.
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.