Security Patterns
Comprehensive security patterns implemented across all Integra V7 smart contracts.
Overview
The Integra V7 smart contract system implements defense-in-depth security with multiple independent layers of protection that work together to prevent exploits and maintain system integrity even under attack. This comprehensive security architecture combines battle-tested patterns from OpenZeppelin with custom protections designed specifically for real-world contract tokenization, creating a robust defense against the full spectrum of smart contract vulnerabilities including reentrancy, access control bypass, front-running, and denial-of-service attacks.
Each security layer operates independently to provide redundant protection, ensuring that the failure of any single mechanism does not compromise the entire system. All state-changing functions use reentrancy guards to prevent recursive call attacks, while multi-layer access control combines role-based permissions, attestation-based capabilities, and document-level authorization. The pausability pattern enables emergency shutdowns across all contracts, and the checks-effects-interactions model ensures state updates occur before external calls. Code hash verification in the registry prevents malicious contract upgrades, front-running protection validates attestation recipients, and graceful degradation returns safe default values instead of reverting to prevent denial-of-service. Finally, time-limited emergency controls with progressive expiry provide rapid incident response capability while ensuring eventual complete decentralization.
Pattern 1: Reentrancy Protection
Description
All state-changing functions use OpenZeppelin’s ReentrancyGuard to prevent reentrancy attacks. The nonReentrant modifier ensures no function can be called recursively, protecting against malicious contracts attempting to re-enter during external calls.
Implementation
// All contracts inherit ReentrancyGuard
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
abstract contract AttestationAccessControlV7 is
UUPSUpgradeable,
AccessControlUpgradeable,
ReentrancyGuardUpgradeable, // ✅ Reentrancy protection
PausableUpgradeable
{
// Standard pattern for all state-changing functions
function _verifyCapability(
address user,
bytes32 documentHash,
uint256 requiredCapability,
bytes calldata attestationProof
) internal nonReentrant whenNotPaused { // ✅ nonReentrant modifier
// ... state changes
// SECURITY: External calls to attestation providers
(bool verified, uint256 grantedCapabilities) = IAttestationProvider(provider)
.verifyCapabilities(attestationProof, user, documentHash, requiredCapability);
// ... validation
}
}Benefits
- Complete Protection: Prevents all reentrancy attack vectors
- Zero Trust: No assumptions about external contract behavior
- Gas Efficient: Minimal overhead (~2,600 gas)
- Battle Tested: OpenZeppelin’s proven implementation
Contracts Using Pattern
AttestationAccessControlV7(Foundation) - All verification functionsIntegraDocumentRegistryV7(Document Management) - Registration, transfers, executor authorizationBaseTokenizerV7(Tokenization) - Token reserve, claim, cancel operationsIntegraMessageV7(Communication) - Message sendingIntegraSignalV7(Communication) - Payment request operationsIntegraExecutorV7(Execution) - Meta-transaction execution
Testing Strategy
-
Reentrancy Attack Tests:
- Deploy malicious contract that attempts reentrancy
- Verify all state-changing functions revert with “ReentrancyGuard: reentrant call”
- Test both direct and cross-contract reentrancy
-
Gas Cost Analysis:
- Measure gas overhead of
nonReentrantmodifier - Verify acceptable performance impact
- Measure gas overhead of
Pattern 2: Checks-Effects-Interactions
Description
The Checks-Effects-Interactions pattern ensures state changes occur before external calls, preventing reentrancy and race conditions. Even with nonReentrant protection, this pattern provides defense-in-depth.
Implementation
// TrustGraphIntegration.sol - Example from trust credential issuance
function _issueCredentialsToAllParties(bytes32 integraHash) internal {
// CHECKS: Validate preconditions
if (credentialsIssued[integraHash]) {
return; // Already issued
}
// EFFECTS: Update state BEFORE external calls
credentialsIssued[integraHash] = true; // ✅ State update first
// INTERACTIONS: External calls last (wrapped in try/catch)
address[] memory parties = _getDocumentParties(integraHash);
for (uint256 i = 0; i < parties.length;) {
// Try/catch prevents one failure from blocking others
try this._issueCredentialToParty(parties[i], integraHash) {
emit TrustCredentialIssued(integraHash, parties[i]);
} catch Error(string memory reason) {
emit TrustCredentialFailed(integraHash, parties[i], reason);
} catch {
emit TrustCredentialFailed(integraHash, parties[i], "Unknown error");
}
unchecked { ++i; }
}
}Benefits
- Reentrancy Safe: State updated before external calls
- Non-blocking: Try/catch prevents cascade failures
- Transparent: Events track both success and failure
- Idempotent: Double-issuance prevented by state flag
Critical Locations
- Trust Credential Issuance (
TrustGraphIntegration.sol:182-198) - Resolver Calls (
IntegraDocumentRegistryV7.sol:1004-1071) - Fee Collection (
IntegraDocumentRegistryV7.sol)
Pattern 3: Access Control Layers
Description
Multi-layer access control with three security levels:
- Foundation: Attestation-based capabilities (fine-grained permissions)
- Document Management: Document ownership (coarse-grained permissions)
- Tokenization: Per-document executor authorization (delegated permissions)
Foundation: Attestation-Based Access Control
/**
* @notice Verify caller has required capability via attestation
* @dev 13-step verification process with front-running protection
*/
modifier requiresCapability(
bytes32 documentHash,
uint256 requiredCapability,
bytes calldata attestationProof
) {
_verifyCapability(msg.sender, documentHash, requiredCapability, attestationProof);
_;
}
// 13-Step Verification (EASAttestationProviderV7.sol:279-402)
function verifyCapabilities(...) external view returns (bool, uint256) {
// 1. ✅ Fetch attestation from EAS
// 2. ✅ Verify attestation exists
// 3. ✅ Verify not revoked
// 4. ✅ Verify not expired
// 5. ✅ Verify schema matches
// 6. ✅ Verify recipient matches (FRONT-RUNNING PROTECTION)
// 7. ✅ Verify attester is authorized issuer
// 8. ✅ Verify source chain ID (cross-chain replay prevention)
// 9. ✅ Verify source EAS contract (EAS spoofing prevention)
// 10. ✅ Verify document contract (contract spoofing prevention)
// 11. ✅ Verify schema version
// 12. ✅ Verify document hash matches
// 13. ✅ Verify attestation age (optional time limits)
}Document Management: Document Ownership
// IntegraDocumentRegistryV7.sol - Three access paths
function getDocumentOwner(bytes32 integraHash) public view returns (address) {
DocumentRecord storage doc = documents[integraHash];
if (!doc.exists) revert DocumentNotRegistered(integraHash);
return doc.owner;
}
// Owner validation in critical operations
function transferDocumentOwnership(
bytes32 integraHash,
address newOwner,
string calldata reason
) external nonReentrant whenNotPaused {
DocumentRecord storage doc = documents[integraHash];
// Only current owner can transfer
if (msg.sender != doc.owner) {
revert Unauthorized(msg.sender, integraHash);
}
// ... transfer logic
}Tokenization: Per-Document Executor Authorization
// BaseTokenizerV7.sol - Zero-trust executor model
modifier requireOwnerOrExecutor(bytes32 integraHash) {
// VALIDATION: Ensure document uses THIS tokenizer
address documentTokenizer = documentRegistry.getTokenizer(integraHash);
if (documentTokenizer == address(0)) {
revert TokenizerNotSet(integraHash);
}
if (documentTokenizer != address(this)) {
revert WrongTokenizer(integraHash, documentTokenizer, address(this));
}
// PATH 1: Document owner (highest priority)
address owner = documentRegistry.getDocumentOwner(integraHash);
if (msg.sender == owner) {
_;
return;
}
// PATH 2: Per-document authorized executor (opt-in)
address authorizedExecutor = documentRegistry.getDocumentExecutor(integraHash);
if (authorizedExecutor != address(0) && msg.sender == authorizedExecutor) {
_;
return;
}
// PATH 3: Unauthorized - revert
revert Unauthorized(msg.sender, integraHash);
}Security Benefits
- Defense in Depth: Multiple independent validation layers
- Zero Trust: No global privileges, all access explicit
- Front-Running Protection: Recipient validation in attestations
- Owner Sovereignty: Document owner always maintains control
- Flexible Delegation: Opt-in executor authorization
Testing Strategy
-
Attestation Tests:
- Test all 13 verification steps individually
- Test front-running scenarios (wrong recipient)
- Test expired/revoked attestations
- Test cross-chain replay attacks
-
Ownership Tests:
- Test ownership transfer scenarios
- Test unauthorized access attempts
- Test edge cases (zero address, self-transfer)
-
Executor Tests:
- Test opt-in authorization
- Test revocation
- Test wrong tokenizer attacks
- Test EOA vs contract executors
Pattern 4: Pausability
Description
Emergency circuit breaker pattern using OpenZeppelin’s Pausable. All state-changing functions can be paused by governance in case of security issues.
Implementation
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
abstract contract AttestationAccessControlV7 is
UUPSUpgradeable,
AccessControlUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable // ✅ Pausability
{
// Pause functions (governance only)
function pause() external onlyRole(GOVERNOR_ROLE) {
_pause();
}
function unpause() external onlyRole(GOVERNOR_ROLE) {
_unpause();
}
// All state-changing functions use whenNotPaused
function criticalFunction(...)
external
nonReentrant
whenNotPaused // ✅ Can be paused
onlyRole(GOVERNOR_ROLE)
{
// ... implementation
}
}Benefits
- Emergency Stop: Immediately halt all operations
- Incident Response: Time to investigate and fix issues
- Reversible: Can be unpaused after resolution
- Governance Controlled: Only authorized roles can pause
Contracts with Pausability
All contracts support pausability:
- Foundation:
AttestationAccessControlV7,EASAttestationProviderV7 - Document management:
IntegraDocumentRegistryV7_Immutable - Tokenization: All tokenizers (via
BaseTokenizerV7) - Communication:
IntegraMessageV7,IntegraSignalV7 - Execution:
IntegraExecutorV7
Pattern 5: Code Hash Verification
Description
Registry pattern that captures and validates contract code hashes, preventing malicious contract upgrades and metamorphic contract attacks.
Implementation
// AttestationProviderRegistryV7_Immutable.sol
function registerProvider(
bytes32 providerId,
address provider,
string calldata providerType,
string calldata description
) external onlyRole(GOVERNOR_ROLE) {
// Verify provider is a contract
uint256 codeSize;
assembly { codeSize := extcodesize(provider) }
if (codeSize == 0) revert NotAContract(provider);
// SECURITY: Capture code hash at registration
bytes32 codeHash;
assembly { codeHash := extcodehash(provider) }
providers[providerId] = ProviderInfo({
providerAddress: provider,
codeHash: codeHash, // ✅ Store code hash
active: true,
registeredAt: block.timestamp,
description: description,
providerType: providerType
});
}
// Retrieval with integrity verification
function getProvider(bytes32 providerId) external view returns (address) {
ProviderInfo storage info = providers[providerId];
// Check provider exists and is active
if (info.providerAddress == address(0)) return address(0);
if (!info.active) return address(0);
// SECURITY: Verify code hasn't changed
address providerAddr = info.providerAddress;
bytes32 currentHash;
assembly { currentHash := extcodehash(providerAddr) }
if (currentHash != info.codeHash) {
return address(0); // ✅ Code changed - graceful degradation
}
return info.providerAddress;
}Security Benefits
- Prevents Malicious Upgrades: Detects contract code changes
- Blocks SELFDESTRUCT Attacks: Detects destroy + redeploy
- Stops Metamorphic Contracts: Code hash validation catches metamorphism
- Graceful Degradation: Returns
address(0)instead of reverting (prevents DOS)
Registries Using Pattern
AttestationProviderRegistryV7_Immutable- Attestation system providersIntegraVerifierRegistryV7_Immutable- ZK proof verifiersIntegraResolverRegistryV7_Immutable- Document resolvers
Testing Strategy
-
Code Change Detection:
- Register contract with initial code
- Upgrade contract (if upgradeable)
- Verify
getProvider()returnsaddress(0)
-
SELFDESTRUCT Attack:
- Register contract
- SELFDESTRUCT contract
- Deploy new contract at same address
- Verify code hash mismatch detected
-
Graceful Degradation:
- Test calling contracts handle
address(0)properly - Verify events emitted for unavailable providers
- Test fallback logic
- Test calling contracts handle
Pattern 6: Front-Running Protection
Description
Attestation-based access control includes recipient validation to prevent front-running attacks where an attacker intercepts and uses someone else’s attestation proof.
Implementation
// EASAttestationProviderV7.sol - Step 6 of 13-step verification
function verifyCapabilities(
bytes calldata proof,
address recipient,
bytes32 documentHash,
uint256 requiredCapability
) external view returns (bool verified, uint256 grantedCapabilities) {
// ... previous steps
// STEP 6: FRONT-RUNNING PROTECTION
// Verify attestation recipient matches the caller
if (attestation.recipient != recipient) {
return (false, 0); // Wrong recipient - proof stolen or front-run
}
// ... remaining steps
}Attack Scenario Prevented
// WITHOUT recipient validation:
// 1. Alice generates attestation for herself
// 2. Bob observes Alice's transaction in mempool
// 3. Bob copies Alice's attestation proof
// 4. Bob front-runs with higher gas, using Alice's proof
// 5. Bob gains Alice's capabilities ❌
// WITH recipient validation:
// 1. Alice generates attestation for herself (recipient = Alice)
// 2. Bob observes Alice's transaction
// 3. Bob copies Alice's proof
// 4. Bob front-runs with higher gas
// 5. Verification fails: attestation.recipient (Alice) != Bob ✅Benefits
- Prevents Proof Theft: Attestations bound to specific recipient
- Mempool Safety: Safe to broadcast transactions with proofs
- No Replay Attacks: Proofs can’t be reused by different addresses
Pattern 7: Graceful Degradation
Description
Instead of reverting when external dependencies fail, the system returns safe default values (address(0), false) and allows callers to decide how to handle failures.
Implementation
// Registry returns address(0) instead of reverting
function getProvider(bytes32 providerId) external view returns (address) {
ProviderInfo storage info = providers[providerId];
if (info.providerAddress == address(0)) return address(0); // Not found
if (!info.active) return address(0); // Inactive
if (currentHash != info.codeHash) return address(0); // Code changed
return info.providerAddress;
}
// Caller decides how to handle
function _verifyCapability(...) internal {
address provider = PROVIDER_REGISTRY.getProvider(providerId);
if (provider == address(0)) {
// Option 1: Revert (critical operation)
revert ProviderNotFound(providerId);
// Option 2: Emit event and skip (optional operation)
emit ProviderUnavailable(providerId);
return;
// Option 3: Use fallback provider
provider = fallbackProvider;
}
}Benefits
- Prevents DOS: One failed dependency doesn’t break entire system
- Flexible Handling: Callers choose between revert/skip/fallback
- Transparent: Events emitted for failures
- Progressive Failure: System degrades gracefully instead of hard failure
Pattern 8: Emergency Controls
Description
Time-limited emergency powers with progressive expiry, allowing rapid response while ensuring decentralization.
Implementation
// IntegraDocumentRegistryV7_Immutable.sol
address public immutable emergencyAddress; // IMMUTABLE
uint256 public immutable emergencyExpiry; // Set at deployment
constructor(..., address _emergencyAddress) {
emergencyAddress = _emergencyAddress;
emergencyExpiry = block.timestamp + 180 days; // 6 months
}
function emergencyUnlockResolvers(
bytes32 integraHash,
string calldata justification
) external nonReentrant {
// TIME-GATED AUTHORIZATION
if (block.timestamp < emergencyExpiry) {
// First 6 months: Emergency address OR governance
if (msg.sender != emergencyAddress && !hasRole(GOVERNOR_ROLE, msg.sender)) {
revert UnauthorizedEmergencyUnlock(msg.sender);
}
} else {
// After 6 months: Only governance
if (!hasRole(GOVERNOR_ROLE, msg.sender)) {
revert EmergencyPowersExpired();
}
}
doc.resolversLocked = false;
emit ResolversEmergencyUnlocked(
integraHash,
msg.sender,
justification, // ✅ Transparent justification
block.timestamp,
msg.sender == emergencyAddress,
block.timestamp < emergencyExpiry
);
}Security Features
- Time-Limited: Emergency powers expire after 6 months
- Immutable Address: Cannot be changed after deployment
- Dual Authorization: Emergency OR governance during active period
- Transparent: Requires justification string in event
- Progressive Decentralization: Auto-expires to governance-only
- Trackable: Events include all context for monitoring
Recommended Configuration
- Emergency Address: Use Gnosis Safe multisig (3-of-5 or 5-of-9)
- Monitoring: Alert on all
ResolversEmergencyUnlockedevents - Public Reporting: Monthly transparency reports of emergency usage
- Documentation: Clear procedures for emergency scenarios
Testing Strategy
Security Test Suite
-
Reentrancy Tests:
describe("Reentrancy Protection", () => { it("should prevent reentrancy on verifyCapability", async () => { const maliciousProvider = await deployMaliciousProvider(); // Attempt reentrancy attack await expect( contract.verifyCapability(..., maliciousProvider) ).to.be.revertedWith("ReentrancyGuard: reentrant call"); }); }); -
Access Control Tests:
describe("Multi-Layer Access Control", () => { it("should enforce attestation capabilities", async () => { // Test without valid attestation await expect( tokenizer.claimToken(integraHash, invalidProof) ).to.be.revertedWith("NoCapability"); }); it("should enforce document ownership", async () => { // Test non-owner attempting transfer await expect( registry.connect(attacker).transferOwnership(integraHash, newOwner) ).to.be.revertedWith("Unauthorized"); }); }); -
Pausability Tests:
describe("Emergency Controls", () => { it("should pause all operations", async () => { await contract.pause(); await expect( contract.registerDocument(...) ).to.be.revertedWith("Pausable: paused"); }); }); -
Code Hash Tests:
describe("Code Integrity", () => { it("should detect code changes", async () => { await registry.registerProvider(id, provider, ...); await provider.upgrade(newImplementation); const retrieved = await registry.getProvider(id); expect(retrieved).to.equal(ethers.ZeroAddress); }); });
Integration Guidelines
For Integrators
-
Always Use Modifiers:
function yourFunction(...) external nonReentrant // ✅ Always include whenNotPaused // ✅ Always include requireOwnerOrExecutor(integraHash) // ✅ Access control { // ... implementation } -
Handle Graceful Degradation:
address provider = PROVIDER_REGISTRY.getProvider(providerId); if (provider == address(0)) { emit ProviderUnavailable(providerId); return; // Or use fallback logic } -
Monitor Emergency Events:
contract.on("ResolversEmergencyUnlocked", (hash, unlocker, justification) => { logAlert(`Emergency unlock: ${justification}`); });
References
- OpenZeppelin Security: https://docs.openzeppelin.com/contracts/4.x/api/security
- Ethereum Attestation Service: https://docs.attest.sh/
- Smart Contract Security Best Practices: https://consensys.github.io/smart-contract-best-practices/
See Also
- Access Control Patterns - Detailed access control architecture
- Registry Patterns - Code hash verification deep dive
- Emergency Controls - Operational procedures