TL;DR
Transforming RCP’s five regulatory pillars into executable smart contracts is not merely a technical implementation—it’s a journey of finding balance between philosophical ideals and code constraints. This article explores how Traceability, Privacy, Enforceability, Finality, and Tokenizability are converted into an interdependent modular system, candidly sharing the design decisions we faced. We delve deeply into our ERC-1400 interface compatibility strategy, Diamond Pattern-based modular architecture, and the quest for balance between upgradability and immutability.
From Philosophy to Code: The Journey of Concretizing Theory
Over the past few months, we’ve built RCP’s philosophical foundation, analyzed the limitations of existing standards, and presented a new paradigm for regulatory compliance. Now we stand at a critical turning point: the moment of transforming abstract concepts into executable code.
The first challenge we encountered was unexpectedly linguistic. The gap between legal language used by regulators and machine language understood by smart contracts was deeper than anticipated. For instance, how do we translate the legal expression “within reasonable time” into uint256 timestamp? How do we encode “appropriate measures” as bytes32 action?
“For the proposition Code is Law to become reality, we must first be able to accurately translate law into code. However, perfectly expressing legal nuances and context in zeros and ones is nearly impossible.”
— Oraclizer Architecture Team Internal Minutes
Our design began by acknowledging this fundamental limitation. If perfect translation is impossible, how do we create the most faithful approximation possible?
Philosophy of Code Translation for Five Regulatory Pillars
Each regulatory pillar is not merely an independent feature but an interdependent system deeply intertwined with others:
- Traceability records all actions, but without Privacy, sensitive information becomes exposed.
- Privacy protects information, but without Traceability, regulatory audits become impossible.
- Enforceability enables regulatory actions, but without Finality, there’s no legal certainty.
- Finality ensures transaction completeness, but without Enforceability, intervention during problems is impossible.
- Tokenizability makes all these elements applicable to various asset classes.
// Theoretical Ideal
"All transactions must be traceable while privacy is protected,
regulatory intervention must be possible while transaction finality is guaranteed"
// Reality of Code
interface IRCP {
function recordTransaction() external; // Traceability
function maskSensitiveData() external; // Privacy
function executeRegulatory() external; // Enforceability
function finalizeTransaction() external; // Finality
// How do we satisfy all of this simultaneously?
}
RCP System Architecture: Birth of Modular Design
Monolithic vs Modular: The Moment of Design Decision
In the early design phase, we stood at a critical crossroads. Should we put all functionality into one massive contract like ERC-3643, or separate it into multiple specialized modules?
The temptation of the monolithic approach was strong:
- Simple interface with single entry point
- Easy consistency management with all state in one place
- Possible gas cost reduction through internal function calls
However, facing the 24KB contract size limit, this approach wasn’t realistic. More importantly, we had to consider the evolution potential of the regulatory environment. What if we had to redeploy the entire system every time new regulations were added or existing ones changed?
Ultimately, we chose a modular architecture, which proved to be the right decision:
// Core of Modular Design: Each module is independent yet interoperable
contract RCPDiamond {
mapping(bytes4 => address) internal facetAddresses;
// Each regulatory pillar implemented as independent Facet
// - TracabilityFacet
// - PrivacyFacet
// - EnforceabilityFacet
// - FinalityFacet
// - TokenizabilityFacet
fallback() external payable {
address facet = facetAddresses[msg.sig];
require(facet != address(0), "Function does not exist");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 { revert(0, returndatasize()) }
default { return(0, returndatasize()) }
}
}
}
ERC-1400 Relationship: Interface Compatibility Strategy
An important distinction must be made here. We are not inheriting ERC-1400 but implementing interface compatibility.
Why Diamond + ERC-1400 Interface?
// ❌ Traditional Inheritance (What We Don't Do)
contract MyToken is ERC1400, ERC1594, ERC1410 {
// Hits 24KB limit
// Full redeployment on upgrades
}
// ✅ Our Approach: Diamond + Interface Compatibility
contract RCPDiamond {
fallback() external payable {
// ERC1400 functions delegated to Facets
address facet = facetAddresses[msg.sig];
// delegatecall to facet
}
}
contract ERC1400Facet {
// Actual ERC1400 logic implementation
function transferWithData(...) external { }
}
Benefits of This Approach:
- Perfect compatibility with ERC-1400 ecosystem (same function signatures)
- All benefits of modularization internally (no size limit, independent upgrades)
- Integration with existing wallets/exchanges possible (identical interface)
This is our strategic choice to “add regulatory compliance functionality while not disconnecting from the existing ecosystem.” We integrate ERC-1400’s fragmented modules (ERC-1594, ERC-1410, ERC-1643, ERC-1644) behind a single Diamond proxy while providing a standard interface to the outside world.
Establishing Core and Facet Relationships
The Core Contract contains only minimal immutable logic:
- Module routing mechanism
- Basic authority management structure
- Emergency stop function (Circuit Breaker)
- State consistency validator
Facet Contracts handle their specialized domains:
- Independently upgradable
- Communication through standardized interfaces
- Self state management (but coordinated through Core)
- Selective activation/deactivation
This structure was inspired by the Diamond Pattern (EIP-2535) but modified to reflect the specifics of regulatory compliance:
// RCP-Specialized Diamond Variation
library RCPStorage {
bytes32 constant STORAGE_POSITION = keccak256("rcp.storage.position");
struct Layout {
// Core state
mapping(address => bool) regulators;
mapping(bytes32 => uint8) complianceStatus;
// Module-specific storage slots
mapping(bytes32 => bytes32) moduleStorage;
// Cross-module coordination
uint256 globalNonce;
mapping(uint256 => bytes32) pendingActions;
}
function layout() internal pure returns (Layout storage l) {
bytes32 position = STORAGE_POSITION;
assembly {
l.slot := position
}
}
}
Permission Management Hierarchy
Permission management in a regulatory compliance system requires complexity beyond a simple owner/non-owner dichotomy. We designed a multi-tiered authority structure:
RCP Permission Management Hierarchy
| Role | Direct Permissions | Inherited Permissions |
|---|---|---|
| EMERGENCY | System Control | All Lower Permissions |
| REGULATOR | Regulatory Actions | COMPLIANCE + OPERATOR + USER |
| COMPLIANCE | Compliance Ops | OPERATOR + USER |
| OPERATOR | Daily Ops | USER |
| USER | Basic Functions | None |
enum Role {
USER, // Basic user
OPERATOR, // Operator (daily management)
COMPLIANCE, // Compliance officer
REGULATOR, // Regulatory authority
EMERGENCY // Emergency authority (multi-sig)
}
contract RCPAccessControl {
using EnumerableSet for EnumerableSet.AddressSet;
mapping(Role => EnumerableSet.AddressSet) private roleMembers;
mapping(Role => mapping(bytes4 => bool)) private roleFunctions;
modifier onlyRole(Role _role) {
require(hasRole(_role, msg.sender), "Unauthorized");
_;
}
// Hierarchical permission inheritance
function hasRole(Role _role, address _account) public view returns (bool) {
// EMERGENCY holds all permissions
if (roleMembers[Role.EMERGENCY].contains(_account)) return true;
// REGULATOR includes COMPLIANCE permissions
if (_role <= Role.COMPLIANCE &&
roleMembers[Role.REGULATOR].contains(_account)) return true;
return roleMembers[_role].contains(_account);
}
}
State Management Principles: Informational vs Standards
Interestingly, there was a subtle difference between the conceptual state defined in the Informational EIP and the actual state that needs to be implemented in the Standards Track EIP:
Informational (Ideal State Definition):
- “Assets can have states such as frozen, seized, confiscated”
- “Each state must clearly reflect its legal meaning”
Standards (Reality of Implementation):
- States encoded as
uint8(gas efficiency) - State transition matrix (allowing only valid transitions)
- Timestamp-based state expiration
library AssetState {
// Ideal: Clear legal meaning
enum LegalStatus {
ACTIVE,
FROZEN,
SEIZED,
CONFISCATED,
LIQUIDATED,
RESTRICTED,
RECOVERED
}
// Reality: Representing composite states with bit flags
uint8 constant ACTIVE_FLAG = 0x01;
uint8 constant FROZEN_FLAG = 0x02;
uint8 constant SEIZED_FLAG = 0x04;
uint8 constant TIME_LOCKED = 0x08;
uint8 constant REGULATOR_LOCKED = 0x10;
function encodeState(
LegalStatus _status,
uint256 _expiry,
address _authority
) internal pure returns (bytes32) {
// Compress all information into 32 bytes
return bytes32(
uint256(_status) |
(_expiry << 8) |
(uint256(uint160(_authority)) << 96)
);
}
}
Behind Design Decisions: Honest Considerations
The Dilemma of Inter-Module Communication
The requirement for each module to be independent yet cooperative caused circular dependency issues. What if the Privacy module calls the Traceability module, and Traceability calls Privacy back?
Our solution was a combination of Event-Driven Architecture and Command Pattern:
contract ModuleCoordinator {
event ActionRequested(bytes32 actionId, address module, bytes data);
event ActionCompleted(bytes32 actionId, bool success);
struct PendingAction {
address initiator;
address target;
bytes4 selector;
bytes data;
uint256 deadline;
bytes32[] dependencies;
}
mapping(bytes32 => PendingAction) public pendingActions;
function requestCrossModuleAction(
address _target,
bytes calldata _data,
bytes32[] calldata _deps
) external returns (bytes32 actionId) {
actionId = keccak256(abi.encode(block.timestamp, msg.sender, _target));
pendingActions[actionId] = PendingAction({
initiator: msg.sender,
target: _target,
selector: bytes4(_data),
data: _data,
deadline: block.timestamp + ACTION_TIMEOUT,
dependencies: _deps
});
emit ActionRequested(actionId, _target, _data);
// Queuing for asynchronous execution
actionQueue.push(actionId);
}
}
Balancing Upgradability vs Immutability
A regulatory compliance system must adapt to change while also providing trustworthy immutability. How do we resolve this contradiction?
Our approach was “Selective Immutability”:
- Permanently Immutable Areas:
- Core authority structure
- Audit logs
- Completed transaction records
- Conditionally Mutable Areas:
- Regulatory rules (multi-sig required)
- Module implementations (timelock applied)
- Threshold parameters (governance vote)
- Freely Mutable Areas:
- Operational parameters
- Whitelists
- Fee structures
contract SelectiveUpgradability {
uint256 constant UPGRADE_DELAY = 48 hours;
mapping(address => uint256) public upgradeTimelocks;
mapping(address => bool) public permanentModules;
function proposeUpgrade(address _module, address _newImpl) external onlyRole(Role.REGULATOR) {
require(!permanentModules[_module], "Module is permanent");
upgradeTimelocks[_module] = block.timestamp + UPGRADE_DELAY;
emit UpgradeProposed(_module, _newImpl, upgradeTimelocks[_module]);
}
function executeUpgrade(address _module, address _newImpl) external {
require(block.timestamp >= upgradeTimelocks[_module], "Timelock active");
require(!emergencyPause, "System paused");
// Actual upgrade logic
_performUpgrade(_module, _newImpl);
// Reset timelock
delete upgradeTimelocks[_module];
}
function makeModulePermanent(address _module) external onlyRole(Role.EMERGENCY) {
permanentModules[_module] = true;
emit ModuleMadePermanent(_module);
}
}
Gas Optimization vs Functional Completeness
The complexity of regulatory compliance inevitably entails high gas costs. Even when executed on L3, contract efficiency remains crucial. What if we perform KYC verification, blacklist checks, and regulatory rule validation for every transaction?
Why contract optimization is necessary even on L3:
- 24KB contract size limit: EVM’s fundamental constraint remains the same on L3
- State storage costs: L3 still has costs for storing and managing state
- Complexity management: Complex logic increases execution time and memory usage
- Cross-chain compatibility: Must be efficient when deployed on other chains
We experimented with several optimization strategies:
1. Lazy Evaluation:
Execute only essential validations immediately, defer the rest until necessary:
function transfer(address to, uint256 amount) public returns (bool) {
// Immediate: essential checks only
require(!blacklist[msg.sender] && !blacklist[to], "Blacklisted");
// Deferred: complex compliance validation
if (amount > LARGE_TRANSFER_THRESHOLD) {
_scheduleComplianceCheck(msg.sender, to, amount);
}
return _transfer(msg.sender, to, amount);
}
2. Batch Processing:
Process multiple validations at once to minimize state access:
function batchCompliance(ComplianceCheck[] calldata checks) external {
uint256 gasStart = gasleft();
for (uint i = 0; i < checks.length && gasleft() > MIN_GAS_RESERVE; i++) {
_processComplianceCheck(checks[i]);
}
uint256 gasUsed = gasStart - gasleft();
_distributeGasCost(gasUsed, checks.length);
}
3. State Compression:
Compress complex states into bit flags to optimize storage space:
// Before: multiple mappings (each SSTORE = 20,000 gas, storage space waste) mapping(address => bool) public kycVerified; mapping(address => bool) public amlCleared; mapping(address => uint8) public riskLevel; // After: single compressed state (1 SSTORE, minimal storage space) mapping(address => uint256) public complianceState; // bits 0-7: risk level // bit 8: KYC verified // bit 9: AML cleared // bits 10-255: future use
Actual impact of these optimizations:
- 40% contract size reduction → All features implementable within 24KB limit
- 60% state storage savings → Reduced L3 state DB burden
- 50% execution complexity reduction → Faster transaction processing
Gas Optimization Strategy Comparison
require(!blacklist[sender]);
// Deferred: complex compliance
if(amount > THRESHOLD) {
_scheduleCompliance();
}
for(uint i = 0; i < batch.length
&& gasleft() > MIN_GAS; i++) {
_processCompliance(batch[i]);
}
uint256 packed =
riskLevel | (kyc << 8) |
(aml << 9) | (flags << 10);
Cross-Chain Considerations: Different Depths of Two EIPs
The Informational EIP addresses cross-chain conceptually:
- “Regulatory compliance must be consistent regardless of chain”
- “Regulatory state must be maintained during cross-chain asset movement”
The Standards Track EIP must present concrete implementation:
interface ICrossChainCompliance {
struct CrossChainMessage {
uint256 sourceChain;
uint256 targetChain;
bytes32 assetId;
uint8 regulatoryState;
bytes proof;
}
function initiateCrossChain(
uint256 targetChain,
bytes32 assetId,
bytes calldata proof
) external returns (bytes32 messageId);
function receiveCrossChain(
CrossChainMessage calldata message
) external returns (bool);
// Chain-specific adapters
mapping(uint256 => address) public chainAdapters;
}
This difference taught us an important lesson: ideals are simple, but implementation is complex.
Concretizing EIP Proposal Strategy
Informational EIP: Building Conceptual Consensus
The Informational EIP answers “what” and “why”:
- Why are RCP’s five pillars necessary?
- What problems does each pillar solve?
- How do they harmonize?
This serves as a philosophical declaration. Without mandating specific implementation, it presents principles that all implementations must follow.
Standards Track EIP: Presenting Technical Standards
The Standards Track EIP answers “how”:
- Concrete interface definitions
- Gas-efficient implementation patterns
- Security considerations and test cases
// ERC-TRUST Core Interface (Standards Track)
interface IERC_TRUST is IERC165 {
// Interface for each of 5 pillars
function supportsCompliance(bytes4 interfaceId) external view returns (bool);
// Traceability
function getTransactionTrace(bytes32 txId) external view returns (TraceData memory);
// Privacy
function getRedactedData(bytes32 dataId, address viewer) external view returns (bytes memory);
// Enforceability
function executeRegulatoryAction(RegulatoryAction action, bytes calldata data) external;
// Finality
function confirmFinality(bytes32 txId) external returns (bool);
// Tokenizability
function getAssetClass() external view returns (bytes32);
}
Complementary Relationship of Two Proposals
The two EIPs mutually reference and strengthen each other:
- Informational serves as “North Star” providing direction
- Standards proves with concrete implementation
- Continuous improvement through feedback loop
Informational EIP (Ideal)
↓
[Provides Guidance]
↓
Standards Track EIP (Implementation)
↓
[Real Experience]
↓
Informational Update
Phased Approach for Community Adoption
We adopted a “Progressive Enhancement” strategy:
Phase 1: Minimal Viable Compliance (3 months)
- Core + Enforceability module only
- Basic FREEZE/UNFREEZE functionality
- Maintain ERC-20 compatibility
Phase 2: Enhanced Compliance (6 months)
- All 6 regulatory actions
- Identity Management module
- Audit trail functionality
Phase 3: Full RCP Implementation (12 months)
- All 5 pillars implemented
- Cross-chain support
- Advanced privacy features
Technical Challenges Faced and Creative Solutions
Challenge 1: Reentrancy in Regulatory Actions
Regulatory actions must execute across multiple contracts, which increases the risk of reentrancy attacks.
Initial Attempt (Failed):
function executeFreeze(address target) external onlyRegulator {
// Danger: State change before external call
frozen[target] = true;
// External call - reentrant!
INotification(target).notifyFreeze();
emit AssetFrozen(target);
}
Improved Solution:
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private reentrancyStatus = NOT_ENTERED;
modifier nonReentrantRegulatory() {
require(reentrancyStatus != ENTERED, "Reentrant call");
reentrancyStatus = ENTERED;
_;
reentrancyStatus = NOT_ENTERED;
}
function executeFreeze(address target) external onlyRegulator nonReentrantRegulatory {
// 1. Checks
require(!frozen[target], "Already frozen");
// 2. Effects
frozen[target] = true;
emit AssetFrozen(target);
// 3. Interactions (last!)
try INotification(target).notifyFreeze() {
// Success
} catch {
// Freeze is valid even if fails
emit NotificationFailed(target);
}
}
Challenge 2: Privacy vs Auditability Paradox
Problem: Must track all transactions (Traceability), but must protect sensitive information (Privacy).
Solution: Merkle Tree-based Selective Disclosure
library PrivacyPreservingAudit {
struct AuditRecord {
bytes32 publicHash; // Public hash
bytes32 privateDataRoot; // Merkle root of private data
mapping(address => bytes32[]) authorizedProofs; // Proofs by authority
}
function createAuditRecord(
bytes memory publicData,
bytes memory privateData,
address[] memory authorizedViewers
) internal returns (bytes32 recordId) {
bytes32 publicHash = keccak256(publicData);
// Chunk private data and create merkle tree
bytes32[] memory leaves = _chunkAndHash(privateData);
bytes32 root = _computeMerkleRoot(leaves);
recordId = keccak256(abi.encode(publicHash, root));
// Generate proofs for each viewer to see only necessary parts
for (uint i = 0; i < authorizedViewers.length; i++) {
bytes32[] memory proofs = _generateProofs(
leaves,
_getViewerPermissions(authorizedViewers[i])
);
records[recordId].authorizedProofs[authorizedViewers[i]] = proofs;
}
}
}
Challenge 3: State Explosion
5 pillars × 6 regulatory actions × N assets = Exponential state growth
Solution: State Compression + Lazy Deletion
library CompressedState {
// 256 bits = 32 bytes full utilization
struct PackedCompliance {
uint32 timestamp; // 4 bytes
uint16 flags; // 2 bytes
uint8 pillar; // 1 byte
uint8 action; // 1 byte
address authority; // 20 bytes
uint32 expiry; // 4 bytes
}
function pack(PackedCompliance memory data) internal pure returns (uint256) {
return uint256(data.timestamp) << 224 |
uint256(data.flags) << 208 |
uint256(data.pillar) << 200 |
uint256(data.action) << 192 |
uint256(uint160(data.authority)) << 32 |
uint256(data.expiry);
}
function unpack(uint256 packed) internal pure returns (PackedCompliance memory) {
return PackedCompliance({
timestamp: uint32(packed >> 224),
flags: uint16(packed >> 208),
pillar: uint8(packed >> 200),
action: uint8(packed >> 192),
authority: address(uint160(packed >> 32)),
expiry: uint32(packed)
});
}
}
Lessons Learned from Failures
Failure 1: The Trap of Over-Engineering
In the initial design, we tried to handle every possible scenario. The result was a complex monster that no one could understand.
Lesson: “Perfect is the enemy of good” – Elegantly handling 80% of cases is better than forcing 100%.
Failure 2: Underestimating Gas Costs
A design that was theoretically perfect consumed 5 million gas per transaction on testnet.
Lesson: No matter how elegant the design, it’s meaningless without economic feasibility.
Failure 3: Overlooking Standard Adoption Difficulty
We thought everyone would follow if we created our perfect standard.
Lesson: Backward compatibility and gradual migration paths can be more important than innovation.
Technical Foundation for Future Implementation
Module Implementation Priorities
Roadmap for actual implementation in upcoming research:
Critical Path (Essential Implementation):
- Core Contract + Router
- Enforceability Module (basic 6 actions)
- Basic Access Control
Important Features:
- Identity Management
- Audit Trail
- Cross-module Coordination
Advanced Features:
- Cross-chain Bridge
- Privacy Preserving Audit
- Automated Compliance
Testing Strategy
contract RCPTestFramework {
// 1. Unit Tests: Independent module testing
function testEnforceabilityModule() public {
// Test FREEZE -> SEIZE -> CONFISCATE sequence
}
// 2. Integration Tests: Inter-module interaction
function testCrossModuleInteraction() public {
// Scenario where Privacy calls Traceability
}
// 3. Stress Tests: Extreme situations
function testGasUnderExtremeSituation() public {
// 1000 simultaneous regulatory actions
}
// 4. Invariant Tests: Verify invariant conditions
function invariant_totalSupplyConsistent() public view {
// Total supply consistent after all state changes
}
}
Security Considerations Checklist
Items to verify during implementation:
- ☐ All
externalfunctions have reentrancy protection - ☐ Confirm authority verification cannot be bypassed
- ☐ Prevent integer overflow/underflow
- ☐ Remove DoS attack vectors
- ☐ Front-running prevention mechanism
- ☐ Verify emergency stop function works
- ☐ Maintain state consistency during upgrades
Conclusion: Beginning the Codification Journey
Over the past few months, we’ve built RCP’s philosophical foundation, analyzed existing standards, and presented a new paradigm. Today, we’ve taken the first step in transforming those abstract ideals into concrete architecture.
Of course, this is not perfect. Our designed architecture still contains compromises and trade-offs. Perfectly translating regulatory language into code is impossible, and attempting to handle every edge case makes the system unusably complex.
But this is the essence of engineering. Not finding the perfect solution, but finding the best balance point within given constraints. Our modular architecture isn’t perfect, but it’s evolvable. Our authority management is complex, but it reflects the complexity of reality.
“We attempt to perform the complex symphony of regulatory compliance with the instrument of smart contracts. Not every note can be perfect, but we can create overall harmony.”
— Oraclizer Architecture Team
Through this architectural design, we’ve established an important foundation. In our next research, we’ll explore strategic questions based on this technical design. Why do we need two approaches: Informational EIP and Standards Track EIP? How will we integrate with the existing ecosystem? And how will these strategic choices impact community adoption?
RCP’s codification is not merely technical implementation. It’s an experiment proving that decentralization and regulatory compliance can coexist, a challenge toward a future where code can become law.
Thank you to everyone who has joined us on this journey. Now the real work begins: turning philosophy into code, ideals into reality.
References
- EIP-2535: Diamonds, Multi-Facet Proxy. (2020). https://eips.ethereum.org/EIPS/eip-2535
- ERC-1400: Security Token Standard. (2018). https://github.com/ethereum/EIPs/issues/1400
- OpenZeppelin. (2024). Smart Contract Security Best Practices. https://docs.openzeppelin.com/contracts/4.x/
- Solidity Documentation. (2024). Security Considerations. https://docs.soliditylang.org/en/latest/security-considerations.html





