Files
smom-dbis-138/contracts/emoney/RailTriggerRegistry.sol
defiQUG 50ab378da9 feat: Implement Universal Cross-Chain Asset Hub - All phases complete
PRODUCTION-GRADE IMPLEMENTATION - All 7 Phases Done

This is a complete, production-ready implementation of an infinitely
extensible cross-chain asset hub that will never box you in architecturally.

## Implementation Summary

### Phase 1: Foundation 
- UniversalAssetRegistry: 10+ asset types with governance
- Asset Type Handlers: ERC20, GRU, ISO4217W, Security, Commodity
- GovernanceController: Hybrid timelock (1-7 days)
- TokenlistGovernanceSync: Auto-sync tokenlist.json

### Phase 2: Bridge Infrastructure 
- UniversalCCIPBridge: Main bridge (258 lines)
- GRUCCIPBridge: GRU layer conversions
- ISO4217WCCIPBridge: eMoney/CBDC compliance
- SecurityCCIPBridge: Accredited investor checks
- CommodityCCIPBridge: Certificate validation
- BridgeOrchestrator: Asset-type routing

### Phase 3: Liquidity Integration 
- LiquidityManager: Multi-provider orchestration
- DODOPMMProvider: DODO PMM wrapper
- PoolManager: Auto-pool creation

### Phase 4: Extensibility 
- PluginRegistry: Pluggable components
- ProxyFactory: UUPS/Beacon proxy deployment
- ConfigurationRegistry: Zero hardcoded addresses
- BridgeModuleRegistry: Pre/post hooks

### Phase 5: Vault Integration 
- VaultBridgeAdapter: Vault-bridge interface
- BridgeVaultExtension: Operation tracking

### Phase 6: Testing & Security 
- Integration tests: Full flows
- Security tests: Access control, reentrancy
- Fuzzing tests: Edge cases
- Audit preparation: AUDIT_SCOPE.md

### Phase 7: Documentation & Deployment 
- System architecture documentation
- Developer guides (adding new assets)
- Deployment scripts (5 phases)
- Deployment checklist

## Extensibility (Never Box In)

7 mechanisms to prevent architectural lock-in:
1. Plugin Architecture - Add asset types without core changes
2. Upgradeable Contracts - UUPS proxies
3. Registry-Based Config - No hardcoded addresses
4. Modular Bridges - Asset-specific contracts
5. Composable Compliance - Stackable modules
6. Multi-Source Liquidity - Pluggable providers
7. Event-Driven - Loose coupling

## Statistics

- Contracts: 30+ created (~5,000+ LOC)
- Asset Types: 10+ supported (infinitely extensible)
- Tests: 5+ files (integration, security, fuzzing)
- Documentation: 8+ files (architecture, guides, security)
- Deployment Scripts: 5 files
- Extensibility Mechanisms: 7

## Result

A future-proof system supporting:
- ANY asset type (tokens, GRU, eMoney, CBDCs, securities, commodities, RWAs)
- ANY chain (EVM + future non-EVM via CCIP)
- WITH governance (hybrid risk-based approval)
- WITH liquidity (PMM integrated)
- WITH compliance (built-in modules)
- WITHOUT architectural limitations

Add carbon credits, real estate, tokenized bonds, insurance products,
or any future asset class via plugins. No redesign ever needed.

Status: Ready for Testing → Audit → Production
2026-01-24 07:01:37 -08:00

204 lines
7.0 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./interfaces/IRailTriggerRegistry.sol";
import "./libraries/RailTypes.sol";
/**
* @title RailTriggerRegistry
* @notice Canonical registry of payment rails, message types, and trigger lifecycle
* @dev Manages trigger state machine and enforces idempotency by instructionId
*/
contract RailTriggerRegistry is IRailTriggerRegistry, AccessControl {
bytes32 public constant RAIL_OPERATOR_ROLE = keccak256("RAIL_OPERATOR_ROLE");
bytes32 public constant RAIL_ADAPTER_ROLE = keccak256("RAIL_ADAPTER_ROLE");
uint256 private _nextTriggerId;
mapping(uint256 => Trigger) private _triggers;
mapping(bytes32 => uint256) private _triggerByInstructionId; // instructionId => triggerId
/**
* @notice Initializes the registry with an admin address
* @param admin Address that will receive DEFAULT_ADMIN_ROLE
*/
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
/**
* @notice Creates a new trigger
* @dev Requires RAIL_OPERATOR_ROLE. Enforces idempotency by instructionId.
* @param t Trigger struct with all required fields
* @return id The assigned trigger ID
*/
function createTrigger(Trigger calldata t) external override onlyRole(RAIL_OPERATOR_ROLE) returns (uint256 id) {
require(t.token != address(0), "RailTriggerRegistry: zero token");
require(t.amount > 0, "RailTriggerRegistry: zero amount");
require(t.accountRefId != bytes32(0), "RailTriggerRegistry: zero accountRefId");
require(t.instructionId != bytes32(0), "RailTriggerRegistry: zero instructionId");
require(t.state == RailTypes.State.CREATED, "RailTriggerRegistry: invalid initial state");
// Enforce idempotency: check if instructionId already exists
require(!instructionIdExists(t.instructionId), "RailTriggerRegistry: duplicate instructionId");
id = _nextTriggerId++;
uint64 timestamp = uint64(block.timestamp);
_triggers[id] = Trigger({
id: id,
rail: t.rail,
msgType: t.msgType,
accountRefId: t.accountRefId,
walletRefId: t.walletRefId,
token: t.token,
amount: t.amount,
currencyCode: t.currencyCode,
instructionId: t.instructionId,
state: RailTypes.State.CREATED,
createdAt: timestamp,
updatedAt: timestamp
});
_triggerByInstructionId[t.instructionId] = id;
emit TriggerCreated(
id,
uint8(t.rail),
t.msgType,
t.instructionId,
t.accountRefId,
t.token,
t.amount
);
}
/**
* @notice Updates the state of a trigger
* @dev Requires RAIL_ADAPTER_ROLE. Enforces valid state transitions.
* @param id The trigger ID
* @param newState The new state
* @param reason Optional reason code for the state change
*/
function updateState(
uint256 id,
RailTypes.State newState,
bytes32 reason
) external override onlyRole(RAIL_ADAPTER_ROLE) {
require(triggerExists(id), "RailTriggerRegistry: trigger not found");
Trigger storage trigger = _triggers[id];
RailTypes.State oldState = trigger.state;
// Validate state transition
require(isValidStateTransition(oldState, newState), "RailTriggerRegistry: invalid state transition");
trigger.state = newState;
trigger.updatedAt = uint64(block.timestamp);
emit TriggerStateUpdated(id, uint8(oldState), uint8(newState), reason);
}
/**
* @notice Returns a trigger by ID
* @param id The trigger ID
* @return The trigger struct
*/
function getTrigger(uint256 id) external view override returns (Trigger memory) {
require(triggerExists(id), "RailTriggerRegistry: trigger not found");
return _triggers[id];
}
/**
* @notice Returns a trigger by instructionId
* @param instructionId The instruction ID
* @return The trigger struct
*/
function getTriggerByInstructionId(bytes32 instructionId) external view override returns (Trigger memory) {
uint256 id = _triggerByInstructionId[instructionId];
require(id != 0 || _triggers[id].instructionId == instructionId, "RailTriggerRegistry: trigger not found");
return _triggers[id];
}
/**
* @notice Checks if a trigger exists
* @param id The trigger ID
* @return true if trigger exists
*/
function triggerExists(uint256 id) public view override returns (bool) {
return _triggers[id].id == id && _triggers[id].instructionId != bytes32(0);
}
/**
* @notice Checks if an instructionId already exists
* @param instructionId The instruction ID to check
* @return true if instructionId exists
*/
function instructionIdExists(bytes32 instructionId) public view override returns (bool) {
uint256 id = _triggerByInstructionId[instructionId];
// Check if trigger exists and has matching instructionId
// Note: We can't use id != 0 check because first trigger has ID 0
return _triggers[id].id == id && _triggers[id].instructionId == instructionId;
}
/**
* @notice Validates a state transition
* @param from Current state
* @param to Target state
* @return true if transition is valid
*/
function isValidStateTransition(
RailTypes.State from,
RailTypes.State to
) internal pure returns (bool) {
// Cannot transition to CREATED
if (to == RailTypes.State.CREATED) {
return false;
}
// Terminal states cannot transition
if (
from == RailTypes.State.SETTLED ||
from == RailTypes.State.REJECTED ||
from == RailTypes.State.CANCELLED ||
from == RailTypes.State.RECALLED
) {
return false;
}
// Valid transitions
if (from == RailTypes.State.CREATED) {
return to == RailTypes.State.VALIDATED || to == RailTypes.State.REJECTED || to == RailTypes.State.CANCELLED;
}
if (from == RailTypes.State.VALIDATED) {
return (
to == RailTypes.State.SUBMITTED_TO_RAIL ||
to == RailTypes.State.REJECTED ||
to == RailTypes.State.CANCELLED
);
}
if (from == RailTypes.State.SUBMITTED_TO_RAIL) {
return (
to == RailTypes.State.PENDING ||
to == RailTypes.State.REJECTED ||
to == RailTypes.State.CANCELLED ||
to == RailTypes.State.RECALLED
);
}
if (from == RailTypes.State.PENDING) {
return (
to == RailTypes.State.SETTLED ||
to == RailTypes.State.REJECTED ||
to == RailTypes.State.CANCELLED ||
to == RailTypes.State.RECALLED
);
}
return false;
}
}