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:
| Role | What It Does | When It Happens |
|---|---|---|
| Storage | Acts as a database for address → role mappings | Always (persistent state) |
| Management | Provides Solidity functions to modify the database | When admins call the precompile |
| Enforcement | Rejects unauthorized transactions | Automatically, 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?
| Checkpoint | Purpose | Speed | Consensus |
|---|---|---|---|
| Mempool | Fast rejection, prevents spam | Immediate | Local only |
| Execution | Canonical, validators must agree | During block | Network-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
| Concept | What Happens |
|---|---|
| Storage vs Enforcement | Precompile stores data; EVM enforces it at hardcoded checkpoints |
| You Never Call It | Enforcement happens automatically—you can be blocked without any precompile interaction |
| Two-Layer Defense | Mempool check (fast, local) + Execution check (canonical, consensus) |
| Contract Deployment | Additional hook checks DeployerAllowList before any contract creation |
| State Reading | GetState(precompileAddr, keccak256(yourAddress)) returns your role |
Summary
The allowlist system is enforced through hardcoded checkpoints in the EVM:
- Mempool validation: Fast rejection before entering the transaction pool
- State transition: Consensus-critical check before execution
- 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?
