752 lines
26 KiB
Solidity
752 lines
26 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
|
|
import "@openzeppelin/contracts/utils/Pausable.sol";
|
|
import "@openzeppelin/contracts/utils/Nonces.sol";
|
|
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
import "../compliance/LegallyCompliantBaseV2.sol";
|
|
import "./interfaces/ICompliantFiatTokenV2.sol";
|
|
|
|
/**
|
|
* @title CompliantFiatTokenV2
|
|
* @notice Canonical GRU c* V2 money token with permit, authorization transfers, explicit audit attribution,
|
|
* role-gated mint/burn, and version-aware asset identity.
|
|
*/
|
|
contract CompliantFiatTokenV2 is ERC20, Pausable, Nonces, EIP712, LegallyCompliantBaseV2, ICompliantFiatTokenV2 {
|
|
using ECDSA for bytes32;
|
|
|
|
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
|
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
|
|
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
|
|
bytes32 public constant BRIDGE_ROLE = keccak256("BRIDGE_ROLE");
|
|
bytes32 public constant SUPPLY_ADMIN_ROLE = keccak256("SUPPLY_ADMIN_ROLE");
|
|
bytes32 public constant METADATA_ADMIN_ROLE = keccak256("METADATA_ADMIN_ROLE");
|
|
bytes32 public constant GOVERNANCE_ROLE = keccak256("GOVERNANCE_ROLE");
|
|
bytes32 public constant JURISDICTION_ADMIN_ROLE = keccak256("JURISDICTION_ADMIN_ROLE");
|
|
bytes32 public constant REGULATOR_ROLE = keccak256("REGULATOR_ROLE");
|
|
bytes32 public constant SUPERVISOR_ROLE = keccak256("SUPERVISOR_ROLE");
|
|
bytes32 public constant EMERGENCY_ADMIN_ROLE = keccak256("EMERGENCY_ADMIN_ROLE");
|
|
|
|
bytes32 public constant OPERATION_TRANSFER = keccak256("TRANSFER");
|
|
bytes32 public constant OPERATION_MINT = keccak256("MINT");
|
|
bytes32 public constant OPERATION_BURN = keccak256("BURN");
|
|
bytes32 public constant OPERATION_TRANSFER_WITH_AUTHORIZATION = keccak256("TRANSFER_WITH_AUTHORIZATION");
|
|
bytes32 public constant OPERATION_RECEIVE_WITH_AUTHORIZATION = keccak256("RECEIVE_WITH_AUTHORIZATION");
|
|
|
|
bytes32 public constant PERMIT_TYPEHASH =
|
|
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
|
|
bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH =
|
|
keccak256(
|
|
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
|
|
);
|
|
bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH =
|
|
keccak256(
|
|
"ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
|
|
);
|
|
bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH =
|
|
keccak256("CancelAuthorization(address authorizer,bytes32 nonce)");
|
|
|
|
uint8 private immutable _decimalsStorage;
|
|
string private _currencyCode;
|
|
string private _tokenURI;
|
|
string public versionTag;
|
|
string public symbolDisplay;
|
|
bytes32 public immutable assetId;
|
|
bytes32 public immutable assetVersionId;
|
|
bool public forwardCanonical;
|
|
address private _owner;
|
|
address public governanceController;
|
|
bytes32 public governanceProfileId;
|
|
bytes32 public supervisionProfileId;
|
|
bytes32 public storageNamespace;
|
|
string public primaryJurisdiction;
|
|
string private _regulatoryDisclosureURI;
|
|
string private _reportingURI;
|
|
address public canonicalUnderlyingAsset;
|
|
bool public supervisionRequired;
|
|
bool public governmentApprovalRequired;
|
|
uint256 public minimumUpgradeNoticePeriod;
|
|
|
|
uint256 public supplyCap;
|
|
uint256 public mintingPeriodCap;
|
|
uint256 public mintingPeriodDuration;
|
|
uint256 public currentMintingPeriodStart;
|
|
uint256 public mintedInCurrentPeriod;
|
|
|
|
string[] private _legacyAliases;
|
|
mapping(bytes32 => bool) private _legacyAliasExists;
|
|
mapping(address => mapping(bytes32 => bool)) private _authorizationStates;
|
|
|
|
struct OperationContext {
|
|
bool active;
|
|
bytes32 operationType;
|
|
address initiator;
|
|
address authorizer;
|
|
address executor;
|
|
bytes32 reasonHash;
|
|
bytes32 accountingRef;
|
|
bytes32 messageCorrelationId;
|
|
bytes32 additionalDataHash;
|
|
}
|
|
|
|
OperationContext private _operationContext;
|
|
|
|
error ERC2612ExpiredSignature(uint256 deadline);
|
|
error ERC2612InvalidSigner(address signer, address owner);
|
|
error AuthorizationExpired(uint256 validBefore);
|
|
error AuthorizationNotYetValid(uint256 validAfter);
|
|
error AuthorizationAlreadyUsed(address authorizer, bytes32 nonce);
|
|
error AuthorizationInvalidSigner(address signer, address authorizer);
|
|
error AuthorizationMustBeUsedByPayee(address payee, address caller);
|
|
error SupplyCapExceeded(uint256 cap, uint256 attemptedTotalSupply);
|
|
error MintCapExceeded(uint256 cap, uint256 attemptedPeriodMint);
|
|
error EmptyAlias();
|
|
error DuplicateAlias(string aliasValue);
|
|
error OwnerUnauthorized(address caller);
|
|
error ZeroOwner();
|
|
error GovernanceControllerOnly(address caller);
|
|
error GovernanceControllerNotConfigured();
|
|
error ZeroGovernanceController();
|
|
|
|
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
|
|
|
modifier onlyGovernanceExecution() {
|
|
if (governanceController == address(0)) revert GovernanceControllerNotConfigured();
|
|
if (_msgSender() != governanceController) revert GovernanceControllerOnly(_msgSender());
|
|
_;
|
|
}
|
|
|
|
constructor(
|
|
string memory name_,
|
|
string memory symbol_,
|
|
uint8 decimals_,
|
|
string memory currencyCode_,
|
|
string memory versionTag_,
|
|
address initialOperator,
|
|
address admin,
|
|
uint256 initialSupply,
|
|
bool forwardCanonical_
|
|
) ERC20(name_, symbol_) EIP712(name_, versionTag_) LegallyCompliantBaseV2(admin) {
|
|
_decimalsStorage = decimals_;
|
|
_currencyCode = currencyCode_;
|
|
versionTag = versionTag_;
|
|
symbolDisplay = symbol_;
|
|
forwardCanonical = forwardCanonical_;
|
|
_owner = admin;
|
|
assetId = keccak256(bytes(string.concat("GRU:", symbol_)));
|
|
assetVersionId = keccak256(bytes(string.concat("GRU:", symbol_, ":", versionTag_)));
|
|
|
|
_grantRole(MINTER_ROLE, initialOperator);
|
|
_grantRole(BURNER_ROLE, initialOperator);
|
|
_grantRole(PAUSER_ROLE, initialOperator);
|
|
_grantRole(BRIDGE_ROLE, initialOperator);
|
|
_grantRole(SUPPLY_ADMIN_ROLE, admin);
|
|
_grantRole(METADATA_ADMIN_ROLE, admin);
|
|
_grantRole(GOVERNANCE_ROLE, admin);
|
|
_grantRole(JURISDICTION_ADMIN_ROLE, admin);
|
|
_grantRole(REGULATOR_ROLE, admin);
|
|
_grantRole(SUPERVISOR_ROLE, admin);
|
|
_grantRole(EMERGENCY_ADMIN_ROLE, admin);
|
|
_grantRole(MINTER_ROLE, admin);
|
|
_grantRole(BURNER_ROLE, admin);
|
|
_grantRole(PAUSER_ROLE, admin);
|
|
_grantRole(BRIDGE_ROLE, admin);
|
|
|
|
governanceProfileId = keccak256(bytes(string.concat("GRU:GOV:", symbol_, ":", versionTag_)));
|
|
supervisionProfileId = keccak256(bytes(string.concat("GRU:SUP:", currencyCode_)));
|
|
storageNamespace = keccak256(bytes(string.concat("gru.storage.asset.", symbol_, ".", versionTag_)));
|
|
primaryJurisdiction = LEGAL_JURISDICTION;
|
|
supervisionRequired = true;
|
|
minimumUpgradeNoticePeriod = 7 days;
|
|
|
|
emit OwnershipTransferred(address(0), admin);
|
|
|
|
if (initialSupply > 0) {
|
|
_setOperationContext(
|
|
OPERATION_MINT,
|
|
initialOperator,
|
|
initialOperator,
|
|
initialOperator,
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0)
|
|
);
|
|
_mint(initialOperator, initialSupply);
|
|
}
|
|
}
|
|
|
|
function decimals() public view override(ERC20, IERC20Metadata) returns (uint8) {
|
|
return _decimalsStorage;
|
|
}
|
|
|
|
function currencyCode() external view returns (string memory) {
|
|
return _currencyCode;
|
|
}
|
|
|
|
function owner() public view returns (address) {
|
|
return _owner;
|
|
}
|
|
|
|
function tokenURI() external view returns (string memory) {
|
|
return _tokenURI;
|
|
}
|
|
|
|
function regulatoryDisclosureURI() external view returns (string memory) {
|
|
return _regulatoryDisclosureURI;
|
|
}
|
|
|
|
function reportingURI() external view returns (string memory) {
|
|
return _reportingURI;
|
|
}
|
|
|
|
function wrappedTransport() external pure returns (bool) {
|
|
return false;
|
|
}
|
|
|
|
function legacyAliases() external view returns (string[] memory) {
|
|
return _legacyAliases;
|
|
}
|
|
|
|
function authorizationState(address authorizer, bytes32 nonce) public view returns (bool) {
|
|
return _authorizationStates[authorizer][nonce];
|
|
}
|
|
|
|
function DOMAIN_SEPARATOR() external view returns (bytes32) {
|
|
return _domainSeparatorV4();
|
|
}
|
|
|
|
function nonces(address tokenOwner) public view override(Nonces, IERC20Permit) returns (uint256) {
|
|
return super.nonces(tokenOwner);
|
|
}
|
|
|
|
function pause() external {
|
|
_checkPauseAuthority();
|
|
_pause();
|
|
}
|
|
|
|
function unpause() external {
|
|
_checkPauseAuthority();
|
|
_unpause();
|
|
}
|
|
|
|
function transferOwnership(address newOwner) external {
|
|
if (_msgSender() != _owner && !hasRole(DEFAULT_ADMIN_ROLE, _msgSender())) {
|
|
revert OwnerUnauthorized(_msgSender());
|
|
}
|
|
if (newOwner == address(0)) revert ZeroOwner();
|
|
address previousOwner = _owner;
|
|
_owner = newOwner;
|
|
emit OwnershipTransferred(previousOwner, newOwner);
|
|
}
|
|
|
|
function setGovernanceController(address governanceController_) external onlyRole(EMERGENCY_ADMIN_ROLE) {
|
|
if (governanceController_ == address(0)) revert ZeroGovernanceController();
|
|
governanceController = governanceController_;
|
|
}
|
|
|
|
function setForwardCanonical(bool value) external onlyGovernanceExecution {
|
|
_setForwardCanonicalValue(value);
|
|
}
|
|
|
|
function emergencySetForwardCanonical(bool value) external onlyRole(EMERGENCY_ADMIN_ROLE) {
|
|
_setForwardCanonicalValue(value);
|
|
}
|
|
|
|
function setTokenURI(string calldata tokenURI_) external onlyGovernanceExecution {
|
|
_setTokenURIValue(tokenURI_);
|
|
}
|
|
|
|
function emergencySetPresentationMetadata(
|
|
bool forwardCanonical_,
|
|
string calldata tokenURI_,
|
|
string calldata symbolDisplay_
|
|
) external onlyRole(EMERGENCY_ADMIN_ROLE) {
|
|
_setForwardCanonicalValue(forwardCanonical_);
|
|
_setTokenURIValue(tokenURI_);
|
|
_setSymbolDisplayValue(symbolDisplay_);
|
|
}
|
|
|
|
function setSymbolDisplay(string calldata symbolDisplay_) external onlyGovernanceExecution {
|
|
_setSymbolDisplayValue(symbolDisplay_);
|
|
}
|
|
|
|
function addLegacyAlias(string calldata aliasValue) external onlyGovernanceExecution {
|
|
_addLegacyAliasValue(aliasValue);
|
|
}
|
|
|
|
function emergencyAddLegacyAlias(string calldata aliasValue) external onlyRole(EMERGENCY_ADMIN_ROLE) {
|
|
_addLegacyAliasValue(aliasValue);
|
|
}
|
|
|
|
function setSupplyControls(
|
|
uint256 supplyCap_,
|
|
uint256 mintingPeriodCap_,
|
|
uint256 mintingPeriodDuration_
|
|
) external onlyRole(SUPPLY_ADMIN_ROLE) {
|
|
supplyCap = supplyCap_;
|
|
mintingPeriodCap = mintingPeriodCap_;
|
|
mintingPeriodDuration = mintingPeriodDuration_;
|
|
if (mintingPeriodDuration_ > 0 && currentMintingPeriodStart == 0) {
|
|
currentMintingPeriodStart = block.timestamp;
|
|
}
|
|
emit SupplyControlsUpdated(supplyCap_, mintingPeriodCap_, mintingPeriodDuration_);
|
|
}
|
|
|
|
function setGovernanceProfileId(bytes32 governanceProfileId_) external onlyGovernanceExecution {
|
|
_setGovernanceProfileIdValue(governanceProfileId_);
|
|
}
|
|
|
|
function setSupervisionProfileId(bytes32 supervisionProfileId_) external onlyGovernanceExecution {
|
|
_setSupervisionProfileIdValue(supervisionProfileId_);
|
|
}
|
|
|
|
function setStorageNamespace(bytes32 storageNamespace_) external onlyGovernanceExecution {
|
|
_setStorageNamespaceValue(storageNamespace_);
|
|
}
|
|
|
|
function setPrimaryJurisdiction(string calldata jurisdiction_) external onlyGovernanceExecution {
|
|
_setPrimaryJurisdictionValue(jurisdiction_);
|
|
}
|
|
|
|
function setRegulatoryDisclosureURI(string calldata disclosureURI_) external onlyGovernanceExecution {
|
|
_setRegulatoryDisclosureURIValue(disclosureURI_);
|
|
}
|
|
|
|
function setReportingURI(string calldata reportingURI_) external onlyGovernanceExecution {
|
|
_setReportingURIValue(reportingURI_);
|
|
}
|
|
|
|
function setCanonicalUnderlyingAsset(address canonicalUnderlyingAsset_) external onlyGovernanceExecution {
|
|
_setCanonicalUnderlyingAssetValue(canonicalUnderlyingAsset_);
|
|
}
|
|
|
|
function setSupervisionConfiguration(
|
|
bool supervisionRequired_,
|
|
bool governmentApprovalRequired_,
|
|
uint256 minimumUpgradeNoticePeriod_
|
|
) external onlyGovernanceExecution {
|
|
_setSupervisionConfigurationValue(
|
|
supervisionRequired_,
|
|
governmentApprovalRequired_,
|
|
minimumUpgradeNoticePeriod_
|
|
);
|
|
}
|
|
|
|
function emergencySetGovernanceMetadata(
|
|
bytes32 governanceProfileId_,
|
|
bytes32 supervisionProfileId_,
|
|
bytes32 storageNamespace_,
|
|
string calldata jurisdiction_,
|
|
address canonicalUnderlyingAsset_,
|
|
bool supervisionRequired_,
|
|
bool governmentApprovalRequired_,
|
|
uint256 minimumUpgradeNoticePeriod_
|
|
) external onlyRole(EMERGENCY_ADMIN_ROLE) {
|
|
_setGovernanceProfileIdValue(governanceProfileId_);
|
|
_setSupervisionProfileIdValue(supervisionProfileId_);
|
|
_setStorageNamespaceValue(storageNamespace_);
|
|
_setPrimaryJurisdictionValue(jurisdiction_);
|
|
_setCanonicalUnderlyingAssetValue(canonicalUnderlyingAsset_);
|
|
_setSupervisionConfigurationValue(
|
|
supervisionRequired_,
|
|
governmentApprovalRequired_,
|
|
minimumUpgradeNoticePeriod_
|
|
);
|
|
}
|
|
|
|
function emergencySetDisclosureMetadata(
|
|
string calldata disclosureURI_,
|
|
string calldata reportingURI_
|
|
) external onlyRole(EMERGENCY_ADMIN_ROLE) {
|
|
_setRegulatoryDisclosureURIValue(disclosureURI_);
|
|
_setReportingURIValue(reportingURI_);
|
|
}
|
|
|
|
function recordRegulatoryApproval(
|
|
bytes32 approvalId,
|
|
string calldata actionType,
|
|
bytes32 referenceHash
|
|
) external onlyRole(REGULATOR_ROLE) {
|
|
emit RegulatoryApprovalRecorded(approvalId, actionType, referenceHash);
|
|
}
|
|
|
|
function recordSupervisoryNotice(
|
|
bytes32 noticeId,
|
|
string calldata category,
|
|
string calldata uri
|
|
) external onlyRole(SUPERVISOR_ROLE) {
|
|
emit SupervisoryNoticeRecorded(noticeId, category, uri);
|
|
}
|
|
|
|
function permit(
|
|
address tokenOwner,
|
|
address spender,
|
|
uint256 value,
|
|
uint256 deadline,
|
|
uint8 v,
|
|
bytes32 r,
|
|
bytes32 s
|
|
) public {
|
|
if (block.timestamp > deadline) revert ERC2612ExpiredSignature(deadline);
|
|
|
|
bytes32 structHash = keccak256(
|
|
abi.encode(PERMIT_TYPEHASH, tokenOwner, spender, value, _useNonce(tokenOwner), deadline)
|
|
);
|
|
bytes32 digest = _hashTypedDataV4(structHash);
|
|
address signer = ECDSA.recover(digest, v, r, s);
|
|
if (signer != tokenOwner) revert ERC2612InvalidSigner(signer, tokenOwner);
|
|
|
|
_approve(tokenOwner, spender, value);
|
|
}
|
|
|
|
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
|
|
mint(to, amount, bytes32(0));
|
|
}
|
|
|
|
function mint(address to, uint256 amount, bytes32 reasonHash) public onlyRole(MINTER_ROLE) {
|
|
_consumeMintCapacity(amount);
|
|
_setOperationContext(
|
|
OPERATION_MINT,
|
|
_msgSender(),
|
|
_msgSender(),
|
|
_msgSender(),
|
|
reasonHash,
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0)
|
|
);
|
|
_mint(to, amount);
|
|
}
|
|
|
|
function burn(uint256 amount) external {
|
|
_setOperationContext(
|
|
OPERATION_BURN,
|
|
_msgSender(),
|
|
_msgSender(),
|
|
_msgSender(),
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0)
|
|
);
|
|
_burn(_msgSender(), amount);
|
|
}
|
|
|
|
function burn(address from, uint256 amount, bytes32 reasonHash) public onlyRole(BURNER_ROLE) {
|
|
_setOperationContext(
|
|
OPERATION_BURN,
|
|
from,
|
|
from,
|
|
_msgSender(),
|
|
reasonHash,
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0)
|
|
);
|
|
_burn(from, amount);
|
|
}
|
|
|
|
function transferWithAuthorization(
|
|
address from,
|
|
address to,
|
|
uint256 value,
|
|
uint256 validAfter,
|
|
uint256 validBefore,
|
|
bytes32 nonce,
|
|
uint8 v,
|
|
bytes32 r,
|
|
bytes32 s
|
|
) external {
|
|
_useAuthorization(
|
|
from,
|
|
to,
|
|
value,
|
|
validAfter,
|
|
validBefore,
|
|
nonce,
|
|
v,
|
|
r,
|
|
s,
|
|
TRANSFER_WITH_AUTHORIZATION_TYPEHASH,
|
|
OPERATION_TRANSFER_WITH_AUTHORIZATION
|
|
);
|
|
}
|
|
|
|
function receiveWithAuthorization(
|
|
address from,
|
|
address to,
|
|
uint256 value,
|
|
uint256 validAfter,
|
|
uint256 validBefore,
|
|
bytes32 nonce,
|
|
uint8 v,
|
|
bytes32 r,
|
|
bytes32 s
|
|
) external {
|
|
if (_msgSender() != to) revert AuthorizationMustBeUsedByPayee(to, _msgSender());
|
|
_useAuthorization(
|
|
from,
|
|
to,
|
|
value,
|
|
validAfter,
|
|
validBefore,
|
|
nonce,
|
|
v,
|
|
r,
|
|
s,
|
|
RECEIVE_WITH_AUTHORIZATION_TYPEHASH,
|
|
OPERATION_RECEIVE_WITH_AUTHORIZATION
|
|
);
|
|
}
|
|
|
|
function cancelAuthorization(address authorizer, bytes32 nonce, uint8 v, bytes32 r, bytes32 s) external {
|
|
if (_authorizationStates[authorizer][nonce]) revert AuthorizationAlreadyUsed(authorizer, nonce);
|
|
bytes32 structHash = keccak256(abi.encode(CANCEL_AUTHORIZATION_TYPEHASH, authorizer, nonce));
|
|
bytes32 digest = _hashTypedDataV4(structHash);
|
|
address signer = ECDSA.recover(digest, v, r, s);
|
|
if (signer != authorizer) revert AuthorizationInvalidSigner(signer, authorizer);
|
|
|
|
_authorizationStates[authorizer][nonce] = true;
|
|
emit AuthorizationCanceled(authorizer, nonce);
|
|
}
|
|
|
|
function _update(address from, address to, uint256 amount) internal override whenNotPaused {
|
|
OperationContext memory ctx = _operationContext;
|
|
if (!ctx.active) {
|
|
ctx = OperationContext({
|
|
active: false,
|
|
operationType: OPERATION_TRANSFER,
|
|
initiator: from == address(0) ? _msgSender() : from,
|
|
authorizer: from == address(0) ? _msgSender() : from,
|
|
executor: _msgSender(),
|
|
reasonHash: bytes32(0),
|
|
accountingRef: bytes32(0),
|
|
messageCorrelationId: bytes32(0),
|
|
additionalDataHash: bytes32(0)
|
|
});
|
|
}
|
|
|
|
_enforceCompliantOperation(
|
|
ctx.operationType,
|
|
ctx.initiator,
|
|
ctx.authorizer,
|
|
ctx.executor,
|
|
from,
|
|
to,
|
|
amount,
|
|
ctx.reasonHash,
|
|
ctx.accountingRef,
|
|
ctx.messageCorrelationId,
|
|
ctx.additionalDataHash
|
|
);
|
|
|
|
super._update(from, to, amount);
|
|
|
|
_recordCompliantOperation(
|
|
ctx.operationType,
|
|
ctx.initiator,
|
|
ctx.authorizer,
|
|
ctx.executor,
|
|
from,
|
|
to,
|
|
amount,
|
|
ctx.reasonHash,
|
|
ctx.accountingRef,
|
|
ctx.messageCorrelationId,
|
|
ctx.additionalDataHash
|
|
);
|
|
|
|
if (_operationContext.active) {
|
|
delete _operationContext;
|
|
}
|
|
}
|
|
|
|
function _useAuthorization(
|
|
address from,
|
|
address to,
|
|
uint256 value,
|
|
uint256 validAfter,
|
|
uint256 validBefore,
|
|
bytes32 nonce,
|
|
uint8 v,
|
|
bytes32 r,
|
|
bytes32 s,
|
|
bytes32 typeHash,
|
|
bytes32 operationType
|
|
) internal {
|
|
if (block.timestamp <= validAfter) revert AuthorizationNotYetValid(validAfter);
|
|
if (block.timestamp >= validBefore) revert AuthorizationExpired(validBefore);
|
|
if (_authorizationStates[from][nonce]) revert AuthorizationAlreadyUsed(from, nonce);
|
|
|
|
bytes32 structHash = keccak256(
|
|
abi.encode(typeHash, from, to, value, validAfter, validBefore, nonce)
|
|
);
|
|
bytes32 digest = _hashTypedDataV4(structHash);
|
|
address signer = ECDSA.recover(digest, v, r, s);
|
|
if (signer != from) revert AuthorizationInvalidSigner(signer, from);
|
|
|
|
_authorizationStates[from][nonce] = true;
|
|
_setOperationContext(
|
|
operationType,
|
|
from,
|
|
from,
|
|
_msgSender(),
|
|
bytes32(0),
|
|
bytes32(0),
|
|
bytes32(0),
|
|
nonce
|
|
);
|
|
_transfer(from, to, value);
|
|
emit AuthorizationUsed(from, to, nonce, value);
|
|
}
|
|
|
|
function _consumeMintCapacity(uint256 amount) internal {
|
|
if (supplyCap > 0 && totalSupply() + amount > supplyCap) {
|
|
revert SupplyCapExceeded(supplyCap, totalSupply() + amount);
|
|
}
|
|
|
|
if (mintingPeriodCap == 0 || mintingPeriodDuration == 0) {
|
|
return;
|
|
}
|
|
|
|
if (currentMintingPeriodStart == 0 || block.timestamp >= currentMintingPeriodStart + mintingPeriodDuration) {
|
|
currentMintingPeriodStart = block.timestamp;
|
|
mintedInCurrentPeriod = 0;
|
|
}
|
|
|
|
uint256 attemptedPeriodMint = mintedInCurrentPeriod + amount;
|
|
if (attemptedPeriodMint > mintingPeriodCap) {
|
|
revert MintCapExceeded(mintingPeriodCap, attemptedPeriodMint);
|
|
}
|
|
|
|
mintedInCurrentPeriod = attemptedPeriodMint;
|
|
}
|
|
|
|
function _setOperationContext(
|
|
bytes32 operationType,
|
|
address initiator,
|
|
address authorizer,
|
|
address executor,
|
|
bytes32 reasonHash,
|
|
bytes32 accountingRef,
|
|
bytes32 messageCorrelationId,
|
|
bytes32 additionalDataHash
|
|
) internal {
|
|
_operationContext = OperationContext({
|
|
active: true,
|
|
operationType: operationType,
|
|
initiator: initiator,
|
|
authorizer: authorizer,
|
|
executor: executor,
|
|
reasonHash: reasonHash,
|
|
accountingRef: accountingRef,
|
|
messageCorrelationId: messageCorrelationId,
|
|
additionalDataHash: additionalDataHash
|
|
});
|
|
}
|
|
|
|
function _enforceCompliantOperation(
|
|
bytes32,
|
|
address,
|
|
address,
|
|
address,
|
|
address,
|
|
address,
|
|
uint256,
|
|
bytes32,
|
|
bytes32,
|
|
bytes32,
|
|
bytes32
|
|
) internal view virtual {
|
|
// Stub hook for future PolicyRouter / ComplianceGate / ReserveGate integration.
|
|
}
|
|
|
|
function _checkPauseAuthority() internal view {
|
|
if (_msgSender() == _owner) {
|
|
return;
|
|
}
|
|
if (!hasRole(PAUSER_ROLE, _msgSender())) {
|
|
revert OwnerUnauthorized(_msgSender());
|
|
}
|
|
}
|
|
|
|
function _setForwardCanonicalValue(bool value) internal {
|
|
forwardCanonical = value;
|
|
emit ForwardCanonicalUpdated(value);
|
|
}
|
|
|
|
function _setTokenURIValue(string memory tokenURI_) internal {
|
|
_tokenURI = tokenURI_;
|
|
emit TokenURIUpdated(tokenURI_);
|
|
}
|
|
|
|
function _setSymbolDisplayValue(string memory symbolDisplay_) internal {
|
|
symbolDisplay = symbolDisplay_;
|
|
emit SymbolDisplayUpdated(symbolDisplay_);
|
|
}
|
|
|
|
function _addLegacyAliasValue(string memory aliasValue) internal {
|
|
if (bytes(aliasValue).length == 0) revert EmptyAlias();
|
|
bytes32 aliasHash = keccak256(bytes(aliasValue));
|
|
if (_legacyAliasExists[aliasHash]) revert DuplicateAlias(aliasValue);
|
|
_legacyAliasExists[aliasHash] = true;
|
|
_legacyAliases.push(aliasValue);
|
|
emit LegacyAliasAdded(aliasValue);
|
|
}
|
|
|
|
function _setGovernanceProfileIdValue(bytes32 governanceProfileId_) internal {
|
|
governanceProfileId = governanceProfileId_;
|
|
emit GovernanceProfileUpdated(governanceProfileId_);
|
|
}
|
|
|
|
function _setSupervisionProfileIdValue(bytes32 supervisionProfileId_) internal {
|
|
supervisionProfileId = supervisionProfileId_;
|
|
emit SupervisionProfileUpdated(supervisionProfileId_);
|
|
}
|
|
|
|
function _setStorageNamespaceValue(bytes32 storageNamespace_) internal {
|
|
storageNamespace = storageNamespace_;
|
|
emit StorageNamespaceUpdated(storageNamespace_);
|
|
}
|
|
|
|
function _setPrimaryJurisdictionValue(string memory jurisdiction_) internal {
|
|
primaryJurisdiction = jurisdiction_;
|
|
emit PrimaryJurisdictionUpdated(jurisdiction_);
|
|
}
|
|
|
|
function _setRegulatoryDisclosureURIValue(string memory disclosureURI_) internal {
|
|
_regulatoryDisclosureURI = disclosureURI_;
|
|
emit RegulatoryDisclosureURIUpdated(disclosureURI_);
|
|
}
|
|
|
|
function _setReportingURIValue(string memory reportingURI_) internal {
|
|
_reportingURI = reportingURI_;
|
|
emit ReportingURIUpdated(reportingURI_);
|
|
}
|
|
|
|
function _setCanonicalUnderlyingAssetValue(address canonicalUnderlyingAsset_) internal {
|
|
canonicalUnderlyingAsset = canonicalUnderlyingAsset_;
|
|
emit CanonicalUnderlyingAssetUpdated(canonicalUnderlyingAsset_);
|
|
}
|
|
|
|
function _setSupervisionConfigurationValue(
|
|
bool supervisionRequired_,
|
|
bool governmentApprovalRequired_,
|
|
uint256 minimumUpgradeNoticePeriod_
|
|
) internal {
|
|
supervisionRequired = supervisionRequired_;
|
|
governmentApprovalRequired = governmentApprovalRequired_;
|
|
minimumUpgradeNoticePeriod = minimumUpgradeNoticePeriod_;
|
|
emit SupervisionConfigurationUpdated(
|
|
supervisionRequired_,
|
|
governmentApprovalRequired_,
|
|
minimumUpgradeNoticePeriod_
|
|
);
|
|
}
|
|
}
|