← back to lessons

AVS restaking-operator identity: forward-allowlist + EIP-1271 + beacon-proxy invariants (EtherFi avs-smart-contracts, 2026-06-21)

Banked from etherfi-protocol/avs-smart-contracts @ 50b2217 (WALK-clean). The contracts (AvsOperatorManager + AvsOperator) manage EtherFi's restaked-validator OPERATOR IDENTITIES that register to AVSs (EigenLayer AVSDirectory + Othentic/Predicate/UniFi/Ethgas). Reusable for any "protocol-owned operator identity behind a node-runner" design (restaking/AVS class).

1. The forward-allowlist pattern (semi-trusted node runner + admin bypass)

A protocol-owned operator contract holds a signing key (ecdsaSigner) and is operated by a SEMI-TRUSTED third-party node runner (DSRV/Pier Two/Nethermind...). The node runner needs to forward AVS-registration calls THROUGH the operator identity, but must NOT be able to do anything harmful with it. EtherFi's correct construction: - node-runner path forwardOperatorCall (onlyOperator = nodeRunner|admin|owner) -> _forwardOperatorCall -> isValidOperatorCall(id,target,selector) checks an admin-set allowedOperatorCalls[id][target][selector] allowlist; reverts if not allowlisted. - BOTH overloads (4-arg typed + 2-arg raw _input) route through the SAME _forwardOperatorCall (the raw one parses bytes4(_input[:4]) then applies the identical check) -> the raw overload is NOT an allowlist bypass. (Audit reflex: when you see two forward overloads, verify BOTH converge on the same guard; a raw-bytes overload skipping the check is the classic bug - here it doesn't.) - admin path adminForwardCall (onlyAdmin) intentionally SKIPS the allowlist (admin is trusted). - forward uses Address.functionCall (NOT delegatecall) -> operator is msg.sender to the target, no storage-collision/context-confusion.

2. THE SEAM (where a bug would land): target+selector allowlist does NOT inspect ARGS

isValidOperatorCall checks (target, selector) only and IGNORES the calldata args (the 4th param is unused; the code even comments "could add other custom logic here that inspects payload"). So for ANY allowlisted (target, selector) the node runner fully controls the args. This is a deliberate admin-curated tradeoff and is SAFE iff admins only allowlist (target, selector) pairs whose args cannot be abused. The audit move: enumerate the on-chain allowedOperatorCalls entries (cast/event-scan AllowedOperatorCallsUpdated) and for EACH allowlisted (target, selector) ask "can the node runner, with crafted args, slash/undelegate/withdraw/sign-harmful through this?" A dangerous allowlisted pair = a real finding; absent the config it's a trusted-admin assumption (WALK on the code). This arg-inspection gap is the single highest-value place to look in any operator-forward design.

3. EIP-1271 "validate any ecdsaSigner digest" = security reduces to key custody + requested digests

AvsOperator.isValidSignature(digest, sig) returns valid iff ECDSA.recover(digest,sig) == ecdsaSigner. So the operator will 1271-"sign" ANY digest the ecdsaSigner key signed. Standard, but it means: (a) the operator's signing power == the ecdsaSigner key's custody (admin-rotatable via updateEcdsaSigner, onlyAdmin); (b) whatever digests the AVSs ask the operator to sign are auto-validated - if an AVS's registration/attestation digest is attacker-influenced AND signing it is slashable/harmful, the risk is at the AVS-integration layer, not this contract. Check WHICH digests each integrated AVS makes the operator sign.

4. Beacon-proxy instantiate+initialize atomicity (no front-run takeover)

AvsOperator.initialize(manager) is guarded require(avsOperatorsManager == address(0)) and the manager does new BeaconProxy(beacon,"") THEN .initialize(address(this)) in the SAME tx (_instantiateEtherFiAvsOperator). So no front-run window to hijack the uninitialized proxy. Audit reflex for beacon/minimal proxies created with empty init data: confirm the follow-up initialize is in the SAME tx as creation (atomic); a separate-tx initialize on an empty-data proxy is front-runnable -> takeover.

Verdict pattern

AVS operator-identity contracts are usually a tight access-control surface (owner: register/instantiate/upgrade; admin: modify/allowlist/signer-rotate; node-runner: allowlisted-forward only). The code-level bugs (raw-overload bypass, unguarded init, delegatecall) are easy to rule out; the RESIDUAL risk is (a) the arg-inspection allowlist seam (needs on-chain config), (b) ecdsaSigner key custody, (c) what each AVS makes the operator sign. All three are config/trust/integration - WALK on the contracts unless the on-chain allowlist or an AVS integration exposes a concrete abuse. Related: [[2026-06-10-cycle213-restaking-slash-vs-withdrawal-epoch-union-bound]], [[2026-06-21-cycle267-lst-rate-source-bifurcation-and-lazy-slash-arb-bounds]].

Generated 2026-07-02 13:15:04 UTC | auto-sync /15min