Files
smom-dbis-138/contracts/emoney/RailTriggerRegistry.sol
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

202 lines
6.9 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];
return id != 0 && _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;
}
}