Files
smom-dbis-138/contracts/dbis/DBIS_SettlementRouter.sol

191 lines
8.0 KiB
Solidity
Raw Normal View History

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./IDBISTypes.sol";
import "./IDBIS_EIP712Helper.sol";
import "./DBIS_RootRegistry.sol";
import "./DBIS_ParticipantRegistry.sol";
import "./DBIS_SignerRegistry.sol";
import "./DBIS_GRU_MintController.sol";
contract DBIS_SettlementRouter is AccessControl, Pausable, ReentrancyGuard {
bytes32 public constant ROUTER_ADMIN_ROLE = keccak256("ROUTER_ADMIN");
uint256 public constant CHAIN_ID = 138;
bytes32 public constant PARTICIPANT_REGISTRY_KEY = keccak256("ParticipantRegistry");
bytes32 public constant SIGNER_REGISTRY_KEY = keccak256("SignerRegistry");
bytes32 public constant GRU_MINT_CONTROLLER_KEY = keccak256("GRUMintController");
bytes32 private constant EIP712_DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
bytes32 private constant MINTAUTH_TYPEHASH = keccak256(
"MintAuth(bytes32 messageId,bytes32 isoType,bytes32 isoHash,bytes32 accountingRef,uint8 fundsStatus,bytes32 corridor,uint8 assetClass,bytes32 recipientsHash,bytes32 amountsHash,uint64 notBefore,uint64 expiresAt,uint256 chainId,address verifyingContract)"
);
DBIS_RootRegistry public rootRegistry;
address public eip712Lib;
mapping(bytes32 => bool) public usedMessageIds;
uint256 public maxAmountPerMessage;
mapping(bytes32 => uint256) public corridorDailyCap;
mapping(bytes32 => mapping(uint256 => uint256)) public corridorUsedToday;
event SettlementRecorded(
bytes32 indexed messageId,
bytes32 indexed isoType,
bytes32 isoHash,
bytes32 accountingRef,
uint8 fundsStatus,
bytes32 corridor,
uint8 assetClass,
uint256 totalAmount
);
event MintExecuted(bytes32 indexed messageId, address indexed recipient, uint256 amount, uint8 assetClass);
event MessageIdConsumed(bytes32 indexed messageId);
event RouterPaused(bool paused);
constructor(address admin, address _rootRegistry, address _eip712Lib) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(ROUTER_ADMIN_ROLE, admin);
rootRegistry = DBIS_RootRegistry(_rootRegistry);
eip712Lib = _eip712Lib;
maxAmountPerMessage = type(uint256).max;
}
function setMaxAmountPerMessage(uint256 cap) external onlyRole(ROUTER_ADMIN_ROLE) {
maxAmountPerMessage = cap;
}
function setCorridorDailyCap(bytes32 corridor, uint256 cap) external onlyRole(ROUTER_ADMIN_ROLE) {
corridorDailyCap[corridor] = cap;
}
function pause() external onlyRole(ROUTER_ADMIN_ROLE) {
_pause();
emit RouterPaused(true);
}
function unpause() external onlyRole(ROUTER_ADMIN_ROLE) {
_unpause();
emit RouterPaused(false);
}
function _domainSeparator() private view returns (bytes32) {
return keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256("DBISSettlementRouter"),
keccak256("1"),
CHAIN_ID,
address(this)
)
);
}
function _hashMintAuth(IDBISTypes.MintAuth calldata auth) private view returns (bytes32) {
IDBIS_EIP712Helper helper = IDBIS_EIP712Helper(eip712Lib);
bytes32 rh = helper.hashAddressArray(auth.recipients);
bytes32 ah = helper.hashUint256Array(auth.amounts);
return helper.getMintAuthStructHash(
MINTAUTH_TYPEHASH,
auth.messageId,
auth.isoType,
auth.isoHash,
auth.accountingRef,
uint8(auth.fundsStatus),
auth.corridor,
uint8(auth.assetClass),
rh,
ah,
auth.notBefore,
auth.expiresAt,
auth.chainId,
auth.verifyingContract
);
}
function getMintAuthDigest(IDBISTypes.MintAuth calldata auth) external view returns (bytes32) {
return IDBIS_EIP712Helper(eip712Lib).getDigest(_domainSeparator(), _hashMintAuth(auth));
}
function submitMintAuth(IDBISTypes.MintAuth calldata auth, bytes[] calldata signatures) external nonReentrant whenNotPaused {
require(auth.chainId == CHAIN_ID, "DBIS: wrong chain");
require(auth.verifyingContract == address(this), "DBIS: wrong contract");
require(block.timestamp >= auth.notBefore && block.timestamp <= auth.expiresAt, "DBIS: time window");
require(!usedMessageIds[auth.messageId], "DBIS: replay");
require(auth.recipients.length == auth.amounts.length, "DBIS: length mismatch");
require(auth.recipients.length > 0, "DBIS: no recipients");
uint256 totalAmount = _sumAmounts(auth.amounts);
require(totalAmount <= maxAmountPerMessage, "DBIS: cap exceeded");
uint256 day = block.timestamp / 1 days;
require(_checkCorridorCap(auth.corridor, day, totalAmount), "DBIS: corridor cap");
_requireRecipientsOperational(auth.recipients);
_recoverSigners(auth, signatures);
usedMessageIds[auth.messageId] = true;
corridorUsedToday[auth.corridor][day] = corridorUsedToday[auth.corridor][day] + totalAmount;
_emitSettlementEvents(auth, totalAmount);
DBIS_GRU_MintController mintController = DBIS_GRU_MintController(rootRegistry.getComponent(GRU_MINT_CONTROLLER_KEY));
require(address(mintController) != address(0), "DBIS: no mint controller");
mintController.mintFromAuthorization(auth);
}
function _sumAmounts(uint256[] calldata amounts) private pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < amounts.length; i++) total += amounts[i];
return total;
}
function _checkCorridorCap(bytes32 corridor, uint256 day, uint256 totalAmount) private view returns (bool) {
uint256 cap = corridorDailyCap[corridor];
if (cap == 0) return true;
return corridorUsedToday[corridor][day] + totalAmount <= cap;
}
function _requireRecipientsOperational(address[] calldata recipients) private view {
DBIS_ParticipantRegistry participantReg = DBIS_ParticipantRegistry(rootRegistry.getComponent(PARTICIPANT_REGISTRY_KEY));
require(address(participantReg) != address(0), "DBIS: no participant registry");
for (uint256 i = 0; i < recipients.length; i++) {
require(participantReg.isOperationalWallet(recipients[i]), "DBIS: not operational wallet");
}
}
function _recoverSigners(IDBISTypes.MintAuth calldata auth, bytes[] calldata signatures) private view returns (address[] memory signers) {
DBIS_SignerRegistry signerReg = DBIS_SignerRegistry(rootRegistry.getComponent(SIGNER_REGISTRY_KEY));
require(address(signerReg) != address(0), "DBIS: no signer registry");
IDBIS_EIP712Helper helper = IDBIS_EIP712Helper(eip712Lib);
bytes32 digest = helper.getDigest(_domainSeparator(), _hashMintAuth(auth));
signers = helper.recoverSigners(digest, signatures);
require(!signerReg.hasDuplicateSigners(signers), "DBIS: duplicate signer");
require(signerReg.areSignersActiveAtBlock(signers, block.number), "DBIS: signer not active at block");
(bool ok, ) = signerReg.validateSigners(signers);
require(ok, "DBIS: quorum not met");
}
function _emitSettlementEvents(IDBISTypes.MintAuth calldata auth, uint256 totalAmount) private {
emit SettlementRecorded(
auth.messageId,
auth.isoType,
auth.isoHash,
auth.accountingRef,
uint8(auth.fundsStatus),
auth.corridor,
uint8(auth.assetClass),
totalAmount
);
for (uint256 i = 0; i < auth.recipients.length; i++) {
emit MintExecuted(auth.messageId, auth.recipients[i], auth.amounts[i], uint8(auth.assetClass));
}
emit MessageIdConsumed(auth.messageId);
}
}