Automatic Enforcement

How the blockchain rejects transactions from non-allowlisted addresses without any precompile call.

In the previous lesson, you learned how admins call precompiles to manage the allowlist. But here's the key question: How does the blockchain reject a normal transaction if you never call the precompile?

The answer: enforcement happens automatically at hardcoded checkpoints in the EVM code.

The Two Roles of Allowlist Precompiles

Precompiles serve two distinct purposes:

RoleWhat It DoesWhen It Happens
StorageActs as a database for address → role mappingsAlways (persistent state)
ManagementProvides Solidity functions to modify the databaseWhen admins call the precompile
EnforcementRejects unauthorized transactionsAutomatically, before every transaction

Key Insight: You can be blocked from sending ANY transaction without ever interacting with the precompile directly. The EVM checks the precompile's storage at hardcoded enforcement points.

The Enforcement Architecture

Transaction AllowList Enforcement

When you send ANY transaction (transfer, contract call, etc.), the TxAllowList is checked at two critical points:

Why Two Checkpoints?

CheckpointPurposeSpeedConsensus
MempoolFast rejection, prevents spamImmediateLocal only
ExecutionCanonical, validators must agreeDuring blockNetwork-wide

State can change between checks. An admin could revoke your permission after your transaction enters the mempool but before it executes.

Contract Deployer AllowList Enforcement

For contract deployments, there's an additional enforcement point via the CanCreateContract hook:

The Hook Implementation

func (r RulesExtra) CanCreateContract(
    ac *libevm.AddressContext,
    gas uint64,
    state libevm.StateReader,
) (uint64, error) {
    
    if r.IsPrecompileEnabled(deployerallowlist.ContractAddress) {
        // Read deployer role from precompile storage
        allowListRole := deployerallowlist.GetContractDeployerAllowListStatus(
            state,
            ac.Origin, // tx.origin (the original sender)
        )
        
        if !allowListRole.IsEnabled() {
            // REJECT: Return error
            return 0, fmt.Errorf(
                "tx.origin %s is not authorized to deploy a contract",
                ac.Origin,
            )
        }
    }
    
    return gas, nil // Authorized, continue
}

Where is the Allowlist Data Stored?

The enforcement code reads from the precompile's storage trie:

Complete Transaction Lifecycle

Key Takeaways

ConceptWhat Happens
Storage vs EnforcementPrecompile stores data; EVM enforces it at hardcoded checkpoints
You Never Call ItEnforcement happens automatically—you can be blocked without any precompile interaction
Two-Layer DefenseMempool check (fast, local) + Execution check (canonical, consensus)
Contract DeploymentAdditional hook checks DeployerAllowList before any contract creation
State ReadingGetState(precompileAddr, keccak256(yourAddress)) returns your role

Summary

The allowlist system is enforced through hardcoded checkpoints in the EVM:

  1. Mempool validation: Fast rejection before entering the transaction pool
  2. State transition: Consensus-critical check before execution
  3. Contract creation hook: Additional check for deployments

This multi-layered approach ensures that permission restrictions are enforced consistently throughout the transaction lifecycle—even when you're not directly calling the precompile.

Is this guide helpful?