CAUtils Library
The CAUtils library provides utilities for detecting whether an address is a smart contract (CA - Contract Address) or an Externally Owned Account (EOA). This is essential for Core.sol functions that should only be callable by contracts.
Overview
Package: @evvm/testnet-contracts
Import Path: @evvm/testnet-contracts/library/utils/CAUtils.sol
Solidity Version: ^0.8.0
Purpose: Distinguish between smart contracts and EOAs using the extcodesize opcode.
Functions
verifyIfCA
Function Type: internal view
Function Signature: verifyIfCA(address)
Checks if an address is a smart contract by examining its code size using the extcodesize opcode.
Parameters
| Parameter | Type | Description |
|---|---|---|
from | address | Address to check |
Return Value
| Type | Description |
|---|---|
bool | true if contract (codesize > 0), false if EOA |
Implementation
function verifyIfCA(address from) internal view returns (bool) {
uint256 size;
assembly {
/// @dev check the size of the opcode of the address
size := extcodesize(from)
}
return (size != 0);
}
How It Works
The function uses the extcodesize EVM opcode which:
- Returns
> 0for addresses containing contract bytecode - Returns
0for EOA addresses (no code) - Returns
0during contract construction (before code is stored) - Returns
0for addresses that had contracts which self-destructed
Usage in Core.sol
Core.sol uses CAUtils to restrict certain functions to contract-only execution:
caPay Function
function caPay(address to, address token, uint256 amount) external {
address from = msg.sender;
if (!CAUtils.verifyIfCA(from)) revert Error.NotAnCA();
_updateBalance(from, to, token, amount);
if (isAddressStaker(msg.sender)) _giveReward(msg.sender, 1);
}
Purpose: Only smart contracts can call caPay, preventing EOAs from bypassing signature requirements.
validateAndConsumeNonce Function
function validateAndConsumeNonce(
address user,
bytes32 hashPayload,
address originExecutor,
uint256 nonce,
bool isAsyncExec,
bytes memory signature
) external {
address servicePointer = msg.sender;
if (!CAUtils.verifyIfCA(servicePointer))
revert Error.MsgSenderIsNotAContract();
// ... signature verification and nonce consumption
}
Purpose: Only EVVM service contracts can validate and consume nonces for users.
Security Considerations
Edge Cases
1. Contract Construction Phase
contract Example {
constructor() {
// During construction, CAUtils.verifyIfCA(address(this)) returns FALSE
// The contract code hasn't been stored yet
}
}
Impact: Constructors cannot reliably use CAUtils for self-checks.
2. Self-Destruct
contract Destructible {
function destroy() external {
selfdestruct(payable(msg.sender));
}
}
// After selfdestruct, CAUtils.verifyIfCA(address) returns FALSE
Impact: Self-destructed contract addresses appear as EOAs.
3. Create2 Pre-Deployment
// Address can be computed before deployment
address futureContract = computeCreate2Address(...);
// CAUtils.verifyIfCA(futureContract) returns FALSE until deployed
Impact: Pre-computed addresses cannot be verified until deployment.
Not a Complete Security Solution
CAUtils.verifyIfCA() should not be the sole security mechanism:
- Construction Bypass: Contracts under construction appear as EOAs
- Proxy Confusion: Proxies may not have code themselves
- Delegatecall Issues: Calls through proxies have different
msg.sender
For critical security, combine with:
- Whitelist/blacklist systems
- Role-based access control
- Signature verification
- Multi-factor authorization
Best Practices
✅ Good Use Cases
Access Control for System Functions:
function systemOperation() external {
require(CAUtils.verifyIfCA(msg.sender), "Contracts only");
// ... system logic
}
Preventing EOA Bypass:
function privilegedFunction() external {
if (!CAUtils.verifyIfCA(msg.sender)) revert NotAContract();
// Ensures EOAs can't call directly
}
❌ Avoid These Patterns
Don't Use as Sole Security:
// ❌ BAD: Too permissive
function dangerousFunction() external {
if (CAUtils.verifyIfCA(msg.sender)) {
// ANY contract can call this!
}
}
Don't Use for Reentrancy Protection:
// ❌ BAD: Doesn't prevent reentrancy
function withdrawAll() external {
require(!CAUtils.verifyIfCA(msg.sender), "EOAs only");
// An EOA can still trigger reentrancy via malicious contract
}
Usage Examples
Basic Contract Detection
import {CAUtils} from "@evvm/testnet-contracts/library/utils/CAUtils.sol";
contract MyContract {
function requireContract() external view {
require(CAUtils.verifyIfCA(msg.sender), "Must be contract");
}
function requireEOA() external view {
require(!CAUtils.verifyIfCA(msg.sender), "Must be EOA");
}
}
Combined with Access Control
import {CAUtils} from "@evvm/testnet-contracts/library/utils/CAUtils.sol";
contract SecureSystem {
mapping(address => bool) public authorizedContracts;
modifier onlyAuthorizedContract() {
require(CAUtils.verifyIfCA(msg.sender), "Not a contract");
require(authorizedContracts[msg.sender], "Not authorized");
_;
}
function systemFunction() external onlyAuthorizedContract {
// Secured by both CA check AND whitelist
}
}
Check External Address
function validateRecipient(address recipient) internal view returns (bool) {
if (CAUtils.verifyIfCA(recipient)) {
// It's a contract - might need special handling
return isApprovedContract(recipient);
} else {
// It's an EOA - standard handling
return true;
}
}
Gas Costs
The extcodesize opcode has the following gas costs:
- Warm address (already accessed): ~100 gas
- Cold address (first access): ~2600 gas
Optimization Tip: If checking the same address multiple times, cache the result:
bool isSenderContract = CAUtils.verifyIfCA(msg.sender);
// Use cached value multiple times
if (isSenderContract) { /* ... */ }
if (isSenderContract) { /* ... */ }
Related Documentation
- Core.sol caPay Function - Contract payment function
- Core.sol validateAndConsumeNonce - Nonce validation function
- disperseCaPay Function - Multi-recipient contract payment
Alternative Approaches
For more robust contract detection, consider:
1. Interface Detection (ERC-165):
interface IMyContract {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
2. Whitelisting:
mapping(address => bool) public approvedContracts;
3. Role-Based Access Control:
bytes32 public constant CONTRACT_ROLE = keccak256("CONTRACT_ROLE");
hasRole(CONTRACT_ROLE, msg.sender);
Summary
CAUtils provides simple contract detection but should be part of a defense-in-depth strategy. For EVVM, it effectively restricts service functions to contract execution while preventing EOAs from bypassing signature requirements.