ComplianceChecker

Purpose

ComplianceChecker evaluates whether an address satisfies any configured ComplianceOption. Each option is a conjunctive set of required SBTs; an address is compliant with an option if it satisfies all SBT checks in that option. The contract exposes both a boolean check and an enforcing requireCompliant gate.

Imports

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {IComplianceChecker, ComplianceOption} from "./interfaces/IComplianceChecker.sol";
  • ComplianceOption (from your interface) contains:

    struct ComplianceOption {
      IVerificationSBT[] requiredSBTs;
    }

    (Inferred from usage: _complianceOptions[i].requiredSBTs[j].isVerificationSBTValid(user).)

Roles

bytes32 public constant COMPLIANCE_ADMIN_ROLE = keccak256("COMPLIANCE_ADMIN_ROLE");
  • DEFAULT_ADMIN_ROLE: top-level role admin.

  • COMPLIANCE_ADMIN_ROLE: can update the compliance options.

Storage

ComplianceOption[] internal _complianceOptions;

The full set of allowed “paths” to compliance. Users must pass all SBT checks inside any one option to be considered compliant.

Constructor

constructor(address defaultAdmin, address complianceAdmin) {
  _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
  _grantRole(COMPLIANCE_ADMIN_ROLE, complianceAdmin);
}

Events

  • event ComplianceSetupChanged(ComplianceOption[] complianceOptions);

Errors

  • error ComplianceCheckFailed(); (used by requireCompliant)


Public / External

getComplianceOptions

function getComplianceOptions() external view returns (ComplianceOption[] memory);

Returns the full array of configured options (deep-copied to memory).


setComplianceOptions

function setComplianceOptions(ComplianceOption[] calldata complianceOptions)
  public onlyRole(COMPLIANCE_ADMIN_ROLE);

What it does

  • Replaces the entire configuration of compliance options in one call.

Effects

  • delete _complianceOptions; then pushes each provided option.

  • Emits: ComplianceSetupChanged(complianceOptions).

Notes

  • This is an overwrite, not an append. Callers should provide the full desired set each time.


isCompliant

function isCompliant(address user) public view returns (bool);

Logic

  • For each ComplianceOption in _complianceOptions:

    • For each requiredSBT in the option:

      • If any requiredSBT.isVerificationSBTValid(user) == false → this option fails.

    • If all SBTs in the option pass → return true.

  • If no option passes → return false.

Complexity

  • O(options × sbts per option); view-only.


requireCompliant

function requireCompliant(address user) public view;

Enforces compliance: require(isCompliant(user), ComplianceCheckFailed());

Used by:

  • CompliantDepositRegistry.getDepositAddress and registerDepositAddress.

  • Your other modules (e.g., Whitelist) can also call it for hard gating.


Integration Notes

  • Configure options for different flows (e.g., KYC vs KYB) as separate ComplianceOption entries. A user needs to pass one of them entirely.

  • Each SBT contract must expose isVerificationSBTValid(address) and should be trustworthy (correctly attesting identity/sanctions/age/etc.).

  • Changes are immediate: once setComplianceOptions is called, all downstream checks reflect the new policy.

Security Considerations

  • Centralized policy: COMPLIANCE_ADMIN_ROLE can tighten/loosen requirements instantly; use a multi-sig or governance process.

  • SBT trust: the system correctness depends on SBT issuers and their isVerificationSBTValid logic.


Quick Call Examples

Admin: add a batch, open challenge window, finalize automatically after period

// roles already granted
registry.addDepositAddresses(new string);
// wait until block.timestamp > latestBatchUnlockTime
// investors can now self-register from this finalized space

Canceler: challenge the latest batch before unlock

registry.challengeLatestBatch(); // pops the entire latest batch

Investor: register & read address (gates on compliance)

string memory addr = registry.registerDepositAddress();
// later queries will re-check compliance:
string memory same = registry.getDepositAddress(msg.sender);

Last updated