Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions contracts/interfaces/engine/IFixDescriptorEngine.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;

/*
* @dev minimum interface to define a FixDescriptorEngine
*/
interface IFixDescriptorEngine {
/**
* @notice Returns the address of the token this engine is bound to.
* @return token The associated token contract address.
*/
function token() external view returns (address token);
}
45 changes: 45 additions & 0 deletions contracts/interfaces/modules/IFixDescriptorEngineModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;

/* ==== Engine === */
import {IFixDescriptorEngine} from "../engine/IFixDescriptorEngine.sol";

/**
* @title IFixDescriptorEngineModule
* @notice Minimal interface for integrating a FIX descriptor engine module.
* @dev Provides methods to set and retrieve a FIX descriptor engine used for managing FIX descriptors.
*/
interface IFixDescriptorEngineModule {
/* ============ Events ============ */
/**
* @notice Emitted when a new FIX descriptor engine is set.
* @param newFixDescriptorEngine The address of the newly assigned FIX descriptor engine contract.
*/
event FixDescriptorEngine(IFixDescriptorEngine indexed newFixDescriptorEngine);
/* ============ Error ============ */
/**
* @dev Reverts if the new FIX descriptor engine is the same as the current one.
*/
error CMTAT_FixDescriptorModule_SameValue();
/**
* @dev Reverts if the provided engine is not bound to the current token.
*/
error CMTAT_FixDescriptorModule_InvalidTokenBinding(address expectedToken, address actualToken);
/* ============ Functions ============ */
/**
* @notice Sets the address of the FIX descriptor engine contract.
* @dev The FIX descriptor engine is responsible for managing FIX descriptors and SBE data.
* Emits a {FixDescriptorEngine} event.
* Reverts with {CMTAT_FixDescriptorModule_SameValue} if the new engine is the same as the current one.
* @param fixDescriptorEngine_ The new FIX descriptor engine contract address to set.
*/
function setFixDescriptorEngine(
IFixDescriptorEngine fixDescriptorEngine_
) external;
/**
* @notice Returns the currently set FIX descriptor engine.
* @return The address of the active FIX descriptor engine contract.
*/
function fixDescriptorEngine() external view returns (IFixDescriptorEngine);
}
92 changes: 92 additions & 0 deletions contracts/mocks/FixDescriptorEngineMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: MPL-2.0
pragma solidity ^0.8.20;

import {IFixDescriptorEngine} from "../interfaces/engine/IFixDescriptorEngine.sol";
import {IFixDescriptor} from "./library/fix/IFixDescriptor.sol";

/*
* @title a FixDescriptorEngine mock for testing, not suitable for production
*/
contract FixDescriptorEngineMock is IFixDescriptorEngine, IFixDescriptor {
address public immutable token;
IFixDescriptor.FixDescriptor private _descriptor;
bool private _initialized;
bool private _verifyFieldResult; // for testing verification results

constructor(address token_, address admin) {
token = token_;
// admin not used but kept for consistency with SnapshotEngineMock pattern
_verifyFieldResult = true; // default to true for tests
}

/**
* @notice Set the descriptor for testing
* @param descriptor The FixDescriptor struct to store
*/
function setFixDescriptor(IFixDescriptor.FixDescriptor memory descriptor) external {
bytes32 oldRoot = _descriptor.fixRoot;
_descriptor = descriptor;
_initialized = true;

if (oldRoot != bytes32(0)) {
emit FixDescriptorUpdated(oldRoot, descriptor.fixRoot, descriptor.fixSBEPtr);
} else {
emit FixDescriptorSet(
descriptor.fixRoot,
descriptor.schemaHash,
descriptor.fixSBEPtr,
descriptor.fixSBELen
);
}
}

/**
* @notice Set the verifyField result for testing
* @param result The boolean result to return from verifyField
*/
function setVerifyFieldResult(bool result) external {
_verifyFieldResult = result;
}

/**
* @notice Get the complete FIX descriptor
* @return descriptor The FixDescriptor struct
*/
function getFixDescriptor() external view override returns (IFixDescriptor.FixDescriptor memory descriptor) {
return _descriptor;
}

/**
* @notice Get the Merkle root commitment
* @return root The fixRoot for verification
*/
function getFixRoot() external view override returns (bytes32 root) {
return _descriptor.fixRoot;
}

/**
* @notice Verify a specific field against the committed descriptor
* @param pathSBE SBE-encoded bytes of the field path
* @param value Raw FIX value bytes
* @param proof Merkle proof (sibling hashes)
* @param directions Direction array (true=right child, false=left child)
* @return valid True if the proof is valid (returns configured test result)
*/
function verifyField(
bytes calldata pathSBE,
bytes calldata value,
bytes32[] calldata proof,
bool[] calldata directions
) external view override returns (bool valid) {
// Return configured result for testing
return _verifyFieldResult;
}

/**
* @notice Get the descriptor engine address
* @return engine Address of the FixDescriptorEngine contract (returns self)
*/
function getDescriptorEngine() external view override returns (address engine) {
return address(this);
}
}
68 changes: 68 additions & 0 deletions contracts/mocks/library/fix/IFixDescriptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
* @title IFixDescriptor
* @notice Standard interface for assets with embedded FIX descriptors
* @dev Asset contracts (ERC20, ERC721, etc.) implement this to expose their FIX descriptor
* This is a local copy for testing purposes to avoid external package dependencies
*/
interface IFixDescriptor {
/// @notice FIX descriptor structure
struct FixDescriptor {
bytes32 schemaHash; // FIX schema/dictionary hash
bytes32 fixRoot; // Merkle root commitment
address fixSBEPtr; // SSTORE2 data contract address
uint32 fixSBELen; // SBE data length
string schemaURI; // Optional SBE schema URI (ipfs:// or https://)
}

/// @notice Emitted when descriptor is first set
event FixDescriptorSet(
bytes32 indexed fixRoot,
bytes32 indexed schemaHash,
address fixSBEPtr,
uint32 fixSBELen
);

/// @notice Emitted when descriptor is updated
event FixDescriptorUpdated(
bytes32 indexed oldRoot,
bytes32 indexed newRoot,
address newPtr
);

/**
* @notice Get the complete FIX descriptor for this asset
* @return descriptor The FixDescriptor struct
*/
function getFixDescriptor() external view returns (FixDescriptor memory descriptor);

/**
* @notice Get the Merkle root commitment
* @return root The fixRoot for verification
*/
function getFixRoot() external view returns (bytes32 root);

/**
* @notice Verify a specific field against the committed descriptor
* @param pathSBE SBE-encoded bytes of the field path
* @param value Raw FIX value bytes
* @param proof Merkle proof (sibling hashes)
* @param directions Direction array (true=right child, false=left child)
* @return valid True if the proof is valid
*/
function verifyField(
bytes calldata pathSBE,
bytes calldata value,
bytes32[] calldata proof,
bool[] calldata directions
) external view returns (bool valid);

/**
* @notice Get the descriptor engine address (optional)
* @dev Returns address(0) if token uses embedded storage instead of engine
* @return engine Address of the FixDescriptorEngine contract, or address(0) if not using engine
*/
function getDescriptorEngine() external view returns (address engine);
}
4 changes: 3 additions & 1 deletion contracts/modules/0_CMTATBaseCommon.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {ExtraInformationModule} from "./wrapper/extensions/ExtraInformationModul
import {ERC20EnforcementModule, ERC20EnforcementModuleInternal} from "./wrapper/extensions/ERC20EnforcementModule.sol";
import {DocumentEngineModule, IERC1643} from "./wrapper/extensions/DocumentEngineModule.sol";
import {SnapshotEngineModule} from "./wrapper/extensions/SnapshotEngineModule.sol";
import {FixDescriptorEngineModule} from "./wrapper/extensions/FixDescriptorEngineModule.sol";
// options
import {ERC20BaseModule, ERC20Upgradeable} from "./wrapper/core/ERC20BaseModule.sol";
/* ==== Interface and other library === */
Expand All @@ -30,6 +31,7 @@ abstract contract CMTATBaseCommon is
SnapshotEngineModule,
ERC20EnforcementModule,
DocumentEngineModule,
FixDescriptorEngineModule,
ExtraInformationModule,
// Interfaces
IBurnMintERC20,
Expand Down Expand Up @@ -108,7 +110,7 @@ abstract contract CMTATBaseCommon is
* - Input validation is also managed by the functions burn and mint
* - You can mint more tokens than burnt
*/
function burnAndMint(address from, address to, uint256 amountToBurn, uint256 amountToMint, bytes calldata data)
function burnAndMint(address from, address to, uint256 amountToBurn, uint256 amountToMint, bytes calldata data)
public virtual override(IBurnMintERC20) {
ERC20BurnModule.burn(from, amountToBurn, data);
ERC20MintModule.mint(to, amountToMint, data);
Expand Down
7 changes: 7 additions & 0 deletions contracts/modules/1_CMTATBaseAccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {ExtraInformationModule} from "./wrapper/extensions/ExtraInformationModul
import {ERC20EnforcementModule} from "./wrapper/extensions/ERC20EnforcementModule.sol";
import {DocumentEngineModule, IERC1643} from "./wrapper/extensions/DocumentEngineModule.sol";
import {SnapshotEngineModule} from "./wrapper/extensions/SnapshotEngineModule.sol";
import {FixDescriptorEngineModule} from "./wrapper/extensions/FixDescriptorEngineModule.sol";
// options
import {ERC20BaseModule, ERC20Upgradeable} from "./wrapper/core/ERC20BaseModule.sol";
/* ==== Interface and other library === */
Expand Down Expand Up @@ -103,4 +104,10 @@ abstract contract CMTATBaseAccessControl is
* - the caller must have the `SNAPSHOOTER_ROLE`.
*/
function _authorizeSnapshots() internal virtual override(SnapshotEngineModule) onlyRole(SNAPSHOOTER_ROLE){}

/**
* @custom:access-control
* - the caller must have the `DESCRIPTOR_ENGINE_ROLE`.
*/
function _authorizeFixDescriptorEngine() internal virtual override(FixDescriptorEngineModule) onlyRole(DESCRIPTOR_ENGINE_ROLE){}
}
100 changes: 100 additions & 0 deletions contracts/modules/wrapper/extensions/FixDescriptorEngineModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: MPL-2.0

pragma solidity ^0.8.20;

/* ==== OpenZeppelin === */
import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
/* ==== Engine === */
import {IFixDescriptorEngine, IFixDescriptorEngineModule} from "../../../interfaces/modules/IFixDescriptorEngineModule.sol";

abstract contract FixDescriptorEngineModule is Initializable, IFixDescriptorEngineModule {
/* ============ State Variables ============ */
bytes32 public constant DESCRIPTOR_ENGINE_ROLE = keccak256("DESCRIPTOR_ENGINE_ROLE");

/* ============ ERC-7201 ============ */
// keccak256(abi.encode(uint256(keccak256("CMTAT.storage.FixDescriptorEngineModule")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant FixDescriptorEngineModuleStorageLocation = 0xc09aa28957960c2b82e3fad477567fe122f23bca69560181151331ec7041c600;
/* ==== ERC-7201 State Variables === */
struct FixDescriptorEngineModuleStorage {
IFixDescriptorEngine _fixDescriptorEngine;
}

/* ============ Modifier ============ */
modifier onlyDescriptorEngine() {
_authorizeFixDescriptorEngine();
_;
}
/* ============ Initializer Function ============ */
/**
* @dev
*
* - The grant to the admin role is done by AccessControlDefaultAdminRules
* - The control of the zero address is done by AccessControlDefaultAdminRules
*
*/
function __FixDescriptorEngineModule_init_unchained(IFixDescriptorEngine fixDescriptorEngine_)
internal virtual onlyInitializing {
if (address(fixDescriptorEngine_) != address (0)) {
FixDescriptorEngineModuleStorage storage $ = _getFixDescriptorEngineModuleStorage();
_setFixDescriptorEngine($, fixDescriptorEngine_);
}
}


/*//////////////////////////////////////////////////////////////
PUBLIC/EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////*/

/* ============ State Restricted Functions ============ */
/**
* @inheritdoc IFixDescriptorEngineModule
* @custom:access-control
* - The caller must have the `DESCRIPTOR_ENGINE_ROLE`.
*/
function setFixDescriptorEngine(
IFixDescriptorEngine fixDescriptorEngine_
) public virtual override(IFixDescriptorEngineModule) onlyDescriptorEngine {
FixDescriptorEngineModuleStorage storage $ = _getFixDescriptorEngineModuleStorage();
require($._fixDescriptorEngine != fixDescriptorEngine_, CMTAT_FixDescriptorModule_SameValue());
if (address(fixDescriptorEngine_) != address(0)) {
address engineToken = fixDescriptorEngine_.token();
require(
engineToken == address(this),
CMTAT_FixDescriptorModule_InvalidTokenBinding(address(this), engineToken)
);
}
_setFixDescriptorEngine($, fixDescriptorEngine_);
}


/* ============ View functions ============ */

/**
* @inheritdoc IFixDescriptorEngineModule
*/
function fixDescriptorEngine() public view virtual override(IFixDescriptorEngineModule) returns (IFixDescriptorEngine) {
FixDescriptorEngineModuleStorage storage $ = _getFixDescriptorEngineModuleStorage();
return $._fixDescriptorEngine;
}
/*//////////////////////////////////////////////////////////////
INTERNAL/PRIVATE FUNCTIONS
//////////////////////////////////////////////////////////////*/

function _setFixDescriptorEngine(
FixDescriptorEngineModuleStorage storage $, IFixDescriptorEngine fixDescriptorEngine_
) internal virtual {
$._fixDescriptorEngine = fixDescriptorEngine_;
emit FixDescriptorEngine(fixDescriptorEngine_);
}

/* ============ Access Control ============ */
function _authorizeFixDescriptorEngine() internal virtual;
/* ============ ERC-7201 ============ */
function _getFixDescriptorEngineModuleStorage() private pure returns (FixDescriptorEngineModuleStorage storage $) {
assembly {
$.slot := FixDescriptorEngineModuleStorageLocation
}
}


}
Loading