- Updated DBIS_ConversionRouter and DBIS_SettlementRouter to utilize IDBIS_EIP712Helper for EIP-712 hashing and signature recovery, improving stack depth management. - Refactored minting logic in DBIS_GRU_MintController to streamline recipient processing. - Enhanced BUILD_NOTES.md with updated build instructions and test coverage details. - Added new functions in DBIS_SignerRegistry for duplicate signer checks and active signer validation. - Introduced a new submodule, DBIS_EIP712Helper, to encapsulate EIP-712 related functionalities. Made-with: Cursor
191 lines
8.0 KiB
Solidity
191 lines
8.0 KiB
Solidity
// 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);
|
|
}
|
|
|
|
}
|