diff --git a/README.md b/README.md
index c7951eb6..cfd7c4ee 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ Document model note:
## Who uses CMTAT?
-CMTAT is used in production by major financial institutions including **UBS**, **Taurus SA**, **Zand Trust **, **Daura**, **Obligate**, and **Syz Group** to tokenize equities, artwork, bonds, structured products, money market funds, and stablecoins.
+CMTAT is used in production by major financial institutions including **UBS**, **Taurus SA**, **Zand Trust**, **Daura**, **Obligate**, and **Syz Group** to tokenize equities, artwork, bonds, structured products, money market funds, and stablecoins.
### Example Per Use Case
diff --git a/contracts/mocks/engine/CMTATDocumentEngineModuleMock.sol b/contracts/mocks/engine/CMTATDocumentEngineModuleMock.sol
index 34ac309b..f44dad97 100644
--- a/contracts/mocks/engine/CMTATDocumentEngineModuleMock.sol
+++ b/contracts/mocks/engine/CMTATDocumentEngineModuleMock.sol
@@ -26,5 +26,5 @@ contract CMTATDocumentEngineModuleMock is CMTATBaseCore, DocumentEngineModule {
__DocumentEngineModule_init_unchained(documentEngine_);
}
- function _authorizeDocumentManagement() internal virtual override(DocumentEngineModule) onlyRole(DOCUMENT_ROLE) {}
+ function _authorizeDocumentManagement() internal virtual override(DocumentEngineModule) onlyRole(DOCUMENT_ENGINE_ROLE) {}
}
diff --git a/contracts/modules/wrapper/options/DocumentEngineModule.sol b/contracts/modules/wrapper/options/DocumentEngineModule.sol
index faceedb6..0a6286cc 100644
--- a/contracts/modules/wrapper/options/DocumentEngineModule.sol
+++ b/contracts/modules/wrapper/options/DocumentEngineModule.sol
@@ -16,7 +16,7 @@ import {IDocumentEngineModule} from "../../../interfaces/modules/IDocumentEngine
*/
abstract contract DocumentEngineModule is Initializable, IDocumentEngineModule {
/* ============ ERC-7201 ============ */
- bytes32 public constant DOCUMENT_ROLE = keccak256("DOCUMENT_ROLE");
+ bytes32 public constant DOCUMENT_ENGINE_ROLE = keccak256("DOCUMENT_ENGINE_ROLE");
// keccak256(abi.encode(uint256(keccak256("CMTAT.storage.DocumentEngineModule")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant DocumentEngineModuleStorageLocation = 0xbd0905600c85d707dc53eba2e146c1c2527cd32ac3ff6b86846155151b3e2700;
/* ==== ERC-7201 State Variables === */
diff --git a/doc/README.md b/doc/README.md
index 23784b5b..1cac701b 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -2,7 +2,7 @@
> To use the CMTAT, we recommend the latest audited version, from the [Releases](https://github.com/CMTA/CMTAT/releases) page. Currently, it is the version [v3.0.0](https://github.com/CMTA/CMTAT/releases/tag/v3.0.0).
>
-> PDF files of README are available here: [CMTATSpecificationV3.0.0.pdf](./specification/CMTATSpecificationV3.0.0.pdf), [CMTATSpecificationV3.1.0.pdf](./specification/CMTATSpecificationV3.1.0.pdf)
+> PDF files of README are available here: [CMTATSpecificationV3.0.0.pdf](./specification/CMTATSpecificationV3.0.0.pdf), [CMTATSpecificationV3.2.0.pdf](./specification/CMTATSpecificationV3.2.0.pdf)
## Introduction
@@ -37,7 +37,7 @@ CMTAT has been built with five main goals:
- Technicals: [ERC-2771](https://eips.ethereum.org/EIPS/eip-2771) (MetaTx/Gasless), [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201), [ERC-7802](https://eips.ethereum.org/EIPS/eip-7802),...
4. Security by undergoing audits from trusted firms like [ADBK](https://abdk.consulting) and [Halborn](https://www.halborn.com), and by implementing a range of industry best practices.
- - Strong code statements coverage(~99.43%) with 3078 automated tests executed
+ - Strong code statements coverage(~99.43%) with 5630 automated tests executed
- Run static analyzer ([Aderyn](https://github.com/Cyfrin/aderyn), [Slither](https://github.com/crytic/slither/tree/master)), as well as AI Auditing tools ([Nethermind Audit Agent](https://auditagent.nethermind.io), [Wake Arena](https://ackee.xyz)), before and after the audits
- RBAC Access Control to clearly separates the different roles and permissions
@@ -197,27 +197,6 @@ CMTAT comes with several different deployment versions to meet specific use case
| MetaTx/Gasless with ERC-2771 | All deployment version, except Debt, DebtEngine, Permit & Light version |
| ERC-2612 Permit + [ERC-6357 Multicall](https://eips.ethereum.org/EIPS/eip-6357)
(gas sponsorship approval & batch transaction) | CMTAT Permit (Standalone / Upgradeable) |
-##### Contract sizes
-
-Measured with `solc 0.8.34`, optimizer enabled (200 runs). EVM deployed bytecode limit: **24.576 KiB**.
-
-The deployed size is identical between standalone and upgradeable for the same variant; the initcode is larger for standalone contracts since it embeds the full constructor logic rather than a proxy initializer.
-
-| Deployment version | Deployed (KiB) | Initcode standalone (KiB) | Initcode upgradeable (KiB) |
-| ------------------ | -------------- | ------------------------- | -------------------------- |
-| CMTAT Standard | 22.243 | 25.635 | 22.569 |
-| CMTAT Snapshot | 22.067 | 25.459 | 22.394 |
-| CMTAT Light | 11.298 | 13.048 | 11.507 |
-| CMTAT Allowlist | 19.879 | 23.056 | 20.205 |
-| CMTAT Debt | 23.187 | 26.301 | 23.396 |
-| CMTAT DebtEngine | 23.791 | 26.905 | 24.000 |
-| CMTAT ERC-7551 | 22.807 | 26.198 | 23.133 |
-| CMTAT ERC-1363 | 23.805 | 27.238 | 24.131 |
-| CMTAT Permit | 23.268 | 26.557 | 23.477 |
-| CMTAT UUPS | 23.544 | — | 23.896 |
-
-All variants are within the deployed bytecode limit.
-
#### CMTAT for stablecoins
Here is a comparison between the features present in major custodian stablecoin and the library CMTAT.
@@ -2466,7 +2445,7 @@ To deploy CMTAT without a proxy, in standalone mode, you need to use the contrac
Here is the surya inheritance schema:
-
+
### Upgradeable (with a proxy)
@@ -2491,7 +2470,7 @@ See the OpenZeppelin [Upgrades plugins](https://docs.openzeppelin.com/upgrades-p
-
+
#### Implementation details
@@ -3188,8 +3167,24 @@ Alternatively, you can install Hardhat [globally](https://v2.hardhat.org/hardhat
npm run-script size
```
+Measured with `solc 0.8.34`, optimizer enabled (200 runs). EVM deployed bytecode limit: **24.576 KiB**.
+
+The deployed size is identical between standalone and upgradeable for the same variant; the initcode is larger for standalone contracts since it embeds the full constructor logic rather than a proxy initializer.
+
+| Deployment version | Deployed (KiB) | Initcode standalone (KiB) | Initcode upgradeable (KiB) |
+| ------------------ | -------------- | ------------------------- | -------------------------- |
+| CMTAT Standard | 22.243 | 25.635 | 22.569 |
+| CMTAT Snapshot | 22.067 | 25.459 | 22.394 |
+| CMTAT Light | 11.298 | 13.048 | 11.507 |
+| CMTAT Allowlist | 19.879 | 23.056 | 20.205 |
+| CMTAT Debt | 23.187 | 26.301 | 23.396 |
+| CMTAT DebtEngine | 23.791 | 26.905 | 24.000 |
+| CMTAT ERC-7551 | 22.807 | 26.198 | 23.133 |
+| CMTAT ERC-1363 | 23.805 | 27.238 | 24.131 |
+| CMTAT Permit | 23.268 | 26.557 | 23.477 |
+| CMTAT UUPS | 23.544 | — | 23.896 |
-
+All variants are within the deployed bytecode limit.
---
diff --git a/doc/SUMMARY.md b/doc/SUMMARY.md
index b0752367..f966ff6d 100644
--- a/doc/SUMMARY.md
+++ b/doc/SUMMARY.md
@@ -4,7 +4,7 @@
- **Snapshot** (`CMTATStandaloneSnapshot` / `CMTATUpgradeableSnapshot`) - Same as standard + SnapshotEngine support
- **Light** - Minimal for stablecoins
- **Allowlist** - Whitelist-based transfers (KYC)
-- **Debt** - Bond-specific fields (maturity, coupon)
+- **Debt** - Bond-specific fields (maturity, coupon) + SnapshotEngine support
- **DebtEngine** - Debt with external engine + SnapshotEngine support
- **ERC-7551** - German eWpG compliance
- **ERC-1363** - transferAndCall support
@@ -27,7 +27,7 @@
```
Level 0 (independent mixins):
- CMTATBaseCommon - Core ERC20 + Mint + Burn + Validation + Access Control
+ CMTATBaseCommon - Core ERC20 + Mint + Burn + ERC20Enforcement (partial freeze) + ExtraInformation (no RBAC, no pause, no address freeze, no transfer validation)
CMTATBaseCore - Core modules only (light variant)
CMTATBaseGeneric - Non-ERC20 modules only
CMTATBaseSnapshot - Pure mixin: ERC20Upgradeable + SnapshotEngineModule (_update hook)
diff --git a/doc/modules/base/0_CMTATBaseCore.md b/doc/modules/base/0_CMTATBaseCore.md
index fcaa7700..5281dbec 100644
--- a/doc/modules/base/0_CMTATBaseCore.md
+++ b/doc/modules/base/0_CMTATBaseCore.md
@@ -4,6 +4,23 @@ This document defines the CMTAT Base Core Module for the CMTA Token specificatio
[TOC]
+## Hierarchy Context
+
+`CMTATBaseCore` sits at **level 0** in the CMTAT inheritance hierarchy. It is the self-contained base used exclusively by the **Light** deployment variants (`CMTATStandaloneLight`, `CMTATUpgradeableLight`).
+
+Unlike `CMTATBaseCommon` (also level 0), `CMTATBaseCore` bundles access control, pause, full validation (`ValidationModule`, `ValidationModuleAllowance`), and enforcement into a single compact base:
+
+| Feature | `CMTATBaseCore` | `CMTATBaseCommon` |
+|---|---|---|
+| ERC-20 (mint, burn, base) | ✓ | ✓ |
+| `AccessControlModule` (RBAC, concrete `_authorize*` overrides) | ✓ | — |
+| `PauseModule` + `EnforcementModule` + `ValidationModule` | ✓ | — |
+| `ValidationModuleAllowance` (approve/permit checks) | ✓ | — |
+| `ERC20EnforcementModule` (partial freeze, forced transfer) | — | ✓ |
+| `ExtraInformationModule` | — | ✓ |
+
+`CMTATBaseCommon` is intended to be composed further up the hierarchy (through `CMTATBaseAccessControl` at level 2), where RBAC, enforcement, and extension modules are layered on separately. `CMTATBaseCore` collapses that into one level for the Light case, where only core operations (mint, burn, pause, freeze, `forcedBurn`) are needed.
+
## Schema

diff --git a/doc/modules/base/surya_report/surya_report_0_CMTATBaseCommon.sol.md b/doc/modules/base/surya_report/surya_report_0_CMTATBaseCommon.sol.md
index a1b0f5f0..9e890052 100644
--- a/doc/modules/base/surya_report/surya_report_0_CMTATBaseCommon.sol.md
+++ b/doc/modules/base/surya_report/surya_report_0_CMTATBaseCommon.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/0_CMTATBaseCommon.sol | 07c0f430a12b113d1a6ead1e9a4918e10a105662 |
+| ./modules/0_CMTATBaseCommon.sol | ef3159d406b8c61dbc2520fb37c0c29c5827d2bc |
### Contracts Description Table
@@ -15,28 +15,17 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseCommon** | Implementation | VersionModule, ERC20MintModule, ERC20BurnModule, ERC20BaseModule, SnapshotEngineModule, ERC20EnforcementModule, DocumentEngineModule, ExtraInformationModule, AccessControlModule, IBurnMintERC20, IERC5679 |||
-| └ | __CMTAT_commonModules_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
+| **CMTATBaseCommon** | Implementation | VersionModule, ERC20MintModule, ERC20BurnModule, ERC20BaseModule, ERC20EnforcementModule, ExtraInformationModule, IBurnMintERC20, IERC5679 |||
| └ | decimals | Public ❗️ | |NO❗️ |
| └ | name | Public ❗️ | |NO❗️ |
| └ | symbol | Public ❗️ | |NO❗️ |
-| └ | supportsInterface | Public ❗️ | |NO❗️ |
| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
| └ | burnAndMint | Public ❗️ | 🛑 |NO❗️ |
| └ | _checkTransferred | Internal 🔒 | 🛑 | |
-| └ | _update | Internal 🔒 | 🛑 | |
| └ | _mintOverride | Internal 🔒 | 🛑 | |
| └ | _burnOverride | Internal 🔒 | 🛑 | |
| └ | _minterTransferOverride | Internal 🔒 | 🛑 | |
-| └ | _authorizeERC20AttributeManagement | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeMint | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeBurn | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeDocumentManagement | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeExtraInfoManagement | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeERC20Enforcer | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeForcedTransfer | Internal 🔒 | 🛑 | onlyRole |
-| └ | _authorizeSnapshots | Internal 🔒 | 🛑 | onlyRole |
### Legend
diff --git a/doc/modules/base/surya_report/surya_report_0_CMTATBaseCore.sol.md b/doc/modules/base/surya_report/surya_report_0_CMTATBaseCore.sol.md
index 41642b66..d85109f6 100644
--- a/doc/modules/base/surya_report/surya_report_0_CMTATBaseCore.sol.md
+++ b/doc/modules/base/surya_report/surya_report_0_CMTATBaseCore.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/0_CMTATBaseCore.sol | 8a4a5979a8c928967f5316539ff24ff15b509a4b |
+| ./modules/0_CMTATBaseCore.sol | 6e879243185b8c9f76fcedb367b2aee8b27ba60e |
### Contracts Description Table
@@ -15,7 +15,7 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseCore** | Implementation | Initializable, ContextUpgradeable, VersionModule, ERC20MintModule, ERC20BurnModule, ValidationModuleCore, ERC20BaseModule, AccessControlModule, IForcedBurnERC20, IBurnMintERC20, IERC7551ERC20EnforcementEvent, IERC5679 |||
+| **CMTATBaseCore** | Implementation | Initializable, ContextUpgradeable, VersionModule, ERC20MintModule, ERC20BurnModule, ValidationModuleAllowance, ERC20BaseModule, AccessControlModule, IForcedBurnERC20, IBurnMintERC20, IERC7551ERC20EnforcementEvent, IERC5679, IERC7943FungibleTransferError |||
| └ | initialize | Public ❗️ | 🛑 | initializer |
| └ | __CMTAT_init | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_openzeppelin_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
@@ -25,6 +25,7 @@
| └ | symbol | Public ❗️ | |NO❗️ |
| └ | supportsInterface | Public ❗️ | |NO❗️ |
| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
| └ | burnAndMint | Public ❗️ | 🛑 |NO❗️ |
| └ | forcedBurn | Public ❗️ | 🛑 | onlyERC20ForcedBurnManager |
diff --git a/doc/modules/base/surya_report/surya_report_0_CMTATBaseGeneric.sol.md b/doc/modules/base/surya_report/surya_report_0_CMTATBaseGeneric.sol.md
index 628be5f1..2484b42a 100644
--- a/doc/modules/base/surya_report/surya_report_0_CMTATBaseGeneric.sol.md
+++ b/doc/modules/base/surya_report/surya_report_0_CMTATBaseGeneric.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/0_CMTATBaseGeneric.sol | e6a7560e642a8d9cf0793b42515b73b9b767f5f5 |
+| ./modules/0_CMTATBaseGeneric.sol | 2db1ecb2c69ee4f60e17917ff565907e44da1daf |
### Contracts Description Table
@@ -15,7 +15,7 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseGeneric** | Implementation | Initializable, ContextUpgradeable, ValidationModule, VersionModule, DocumentEngineModule, ExtraInformationModule, AccessControlModule |||
+| **CMTATBaseGeneric** | Implementation | Initializable, ContextUpgradeable, ValidationModule, VersionModule, DocumentERC1643Module, ExtraInformationModule, AccessControlModule |||
| └ | __CMTAT_init | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_openzeppelin_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_modules_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
diff --git a/doc/modules/base/surya_report/surya_report_2_CMTATBaseDebt.sol.md b/doc/modules/base/surya_report/surya_report_0_CMTATBaseSnapshot.sol.md
similarity index 72%
rename from doc/modules/base/surya_report/surya_report_2_CMTATBaseDebt.sol.md
rename to doc/modules/base/surya_report/surya_report_0_CMTATBaseSnapshot.sol.md
index 074fade3..0819ad75 100644
--- a/doc/modules/base/surya_report/surya_report_2_CMTATBaseDebt.sol.md
+++ b/doc/modules/base/surya_report/surya_report_0_CMTATBaseSnapshot.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/2_CMTATBaseDebt.sol | e426d5e5e529f32ab738f8f40eb3365b1c359b65 |
+| ./modules/0_CMTATBaseSnapshot.sol | a6d54586c8a7ed63bffceed0c7bd1abe35b5e5cd |
### Contracts Description Table
@@ -15,8 +15,8 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseDebt** | Implementation | CMTATBaseRuleEngine, DebtEngineModule |||
-| └ | _authorizeDebtManagement | Internal 🔒 | 🛑 | onlyRole |
+| **CMTATBaseSnapshot** | Implementation | ERC20Upgradeable, SnapshotEngineModule |||
+| └ | _update | Internal 🔒 | 🛑 | |
### Legend
diff --git a/doc/modules/base/surya_report/surya_report_1_CMTATBaseDocument.sol.md b/doc/modules/base/surya_report/surya_report_1_CMTATBaseDocument.sol.md
new file mode 100644
index 00000000..d85f71c1
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_1_CMTATBaseDocument.sol.md
@@ -0,0 +1,26 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/1_CMTATBaseDocument.sol | 6d9a534014f54d5736d99d1dc3f7e6d4ba7cb93d |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseDocument** | Implementation | DocumentERC1643Module |||
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_2_CMTATBaseAccessControl.sol.md b/doc/modules/base/surya_report/surya_report_2_CMTATBaseAccessControl.sol.md
new file mode 100644
index 00000000..6e38fe59
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_2_CMTATBaseAccessControl.sol.md
@@ -0,0 +1,35 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/2_CMTATBaseAccessControl.sol | cf934aa8b26c9bdc2aeb336391b3c4098da70c18 |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseAccessControl** | Implementation | AccessControlModule, CMTATBaseCommon, CMTATBaseDocument |||
+| └ | __CMTAT_commonModules_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
+| └ | supportsInterface | Public ❗️ | |NO❗️ |
+| └ | _authorizeERC20AttributeManagement | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeMint | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeBurn | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeDocumentManagement | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeExtraInfoManagement | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeERC20Enforcer | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeForcedTransfer | Internal 🔒 | 🛑 | onlyRole |
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_1_CMTATBaseAllowlist.sol.md b/doc/modules/base/surya_report/surya_report_3_CMTATBaseAllowlist.sol.md
similarity index 59%
rename from doc/modules/base/surya_report/surya_report_1_CMTATBaseAllowlist.sol.md
rename to doc/modules/base/surya_report/surya_report_3_CMTATBaseAllowlist.sol.md
index becb961c..93574cec 100644
--- a/doc/modules/base/surya_report/surya_report_1_CMTATBaseAllowlist.sol.md
+++ b/doc/modules/base/surya_report/surya_report_3_CMTATBaseAllowlist.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/1_CMTATBaseAllowlist.sol | a03434fbd8327f1ce333d2d710a1989c3275fbe6 |
+| ./modules/3_CMTATBaseAllowlist.sol | 517b746b223a533ad42e9dad2a49520c1fd14d1f |
### Contracts Description Table
@@ -15,20 +15,34 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseAllowlist** | Implementation | Initializable, ContextUpgradeable, CMTATBaseCommon, ValidationModuleAllowlist, ValidationModuleCore, ERC2771Module |||
+| **CMTATBaseAllowlist** | Implementation | Initializable, ContextUpgradeable, CMTATBaseAccessControl, ValidationModuleAllowlist, ValidationModuleAllowance, ERC2771Module, ERC20EnforcementERC7551Module, IERC7943FungibleTransferError |||
| └ | initialize | Public ❗️ | 🛑 | initializer |
| └ | __CMTAT_init | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_openzeppelin_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_modules_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
+| └ | approve | Public ❗️ | 🛑 | whenNotPaused |
+| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
+| └ | decimals | Public ❗️ | |NO❗️ |
+| └ | name | Public ❗️ | |NO❗️ |
+| └ | symbol | Public ❗️ | |NO❗️ |
| └ | canTransfer | Public ❗️ | |NO❗️ |
| └ | canTransferFrom | Public ❗️ | |NO❗️ |
| └ | _authorizePause | Internal 🔒 | 🛑 | onlyRole |
| └ | _authorizeDeactivate | Internal 🔒 | 🛑 | onlyRole |
| └ | _authorizeFreeze | Internal 🔒 | 🛑 | onlyRole |
| └ | _authorizeAllowlistManagement | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeERC20Enforcer | Internal 🔒 | 🛑 | |
+| └ | _authorizeForcedTransfer | Internal 🔒 | 🛑 | |
| └ | _canMintBurnByModule | Internal 🔒 | | |
-| └ | _canTransferGenericByModule | Internal 🔒 | | |
+| └ | _canMintByModuleAndRevert | Internal 🔒 | | |
+| └ | _canBurnByModuleAndRevert | Internal 🔒 | | |
+| └ | _canSend | Internal 🔒 | | |
+| └ | _canReceive | Internal 🔒 | | |
+| └ | _canTransferStandardByModule | Internal 🔒 | | |
+| └ | _canTransferStandardByModuleAndRevert | Internal 🔒 | | |
| └ | _checkTransferred | Internal 🔒 | 🛑 | |
+| └ | getFrozenTokens | Public ❗️ | |NO❗️ |
| └ | _msgSender | Internal 🔒 | | |
| └ | _contextSuffixLength | Internal 🔒 | | |
| └ | _msgData | Internal 🔒 | | |
diff --git a/doc/modules/base/surya_report/surya_report_1_CMTATBaseRuleEngine.sol.md b/doc/modules/base/surya_report/surya_report_3_CMTATBaseRuleEngine.sol.md
similarity index 80%
rename from doc/modules/base/surya_report/surya_report_1_CMTATBaseRuleEngine.sol.md
rename to doc/modules/base/surya_report/surya_report_3_CMTATBaseRuleEngine.sol.md
index 2b0d9b11..bc23215b 100644
--- a/doc/modules/base/surya_report/surya_report_1_CMTATBaseRuleEngine.sol.md
+++ b/doc/modules/base/surya_report/surya_report_3_CMTATBaseRuleEngine.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/1_CMTATBaseRuleEngine.sol | a8f439a039694270eba59e58d219a64f094a02ca |
+| ./modules/3_CMTATBaseRuleEngine.sol | b9bc5276aceceae7c6224027123f31f4ed47c61b |
### Contracts Description Table
@@ -15,12 +15,14 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseRuleEngine** | Implementation | CMTATBaseCommon, ValidationModuleRuleEngine |||
+| **CMTATBaseRuleEngine** | Implementation | CMTATBaseAccessControl, ValidationModuleRuleEngine, IERC7943FungibleTransferError |||
| └ | initialize | Public ❗️ | 🛑 | initializer |
+| └ | _initialize | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_init | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_openzeppelin_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_internal_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
| └ | __CMTAT_modules_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
+| └ | approve | Public ❗️ | 🛑 | whenNotPaused |
| └ | canTransfer | Public ❗️ | |NO❗️ |
| └ | canTransferFrom | Public ❗️ | |NO❗️ |
| └ | _authorizePause | Internal 🔒 | 🛑 | onlyRole |
diff --git a/doc/modules/base/surya_report/surya_report_4_CMTATBaseDebt.sol.md b/doc/modules/base/surya_report/surya_report_4_CMTATBaseDebt.sol.md
new file mode 100644
index 00000000..8f161dd7
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_4_CMTATBaseDebt.sol.md
@@ -0,0 +1,35 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/4_CMTATBaseDebt.sol | 593ad8bf57d7bbd52a5ee426a3e0f11d2e2d49c6 |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseDebt** | Implementation | CMTATBaseRuleEngine, DebtModule, CMTATBaseSnapshot |||
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
+| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
+| └ | decimals | Public ❗️ | |NO❗️ |
+| └ | name | Public ❗️ | |NO❗️ |
+| └ | symbol | Public ❗️ | |NO❗️ |
+| └ | _update | Internal 🔒 | 🛑 | |
+| └ | _authorizeDebtManagement | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeSnapshots | Internal 🔒 | 🛑 | onlyRole |
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_2_CMTATBaseERC1404.sol.md b/doc/modules/base/surya_report/surya_report_4_CMTATBaseERC1404.sol.md
similarity index 92%
rename from doc/modules/base/surya_report/surya_report_2_CMTATBaseERC1404.sol.md
rename to doc/modules/base/surya_report/surya_report_4_CMTATBaseERC1404.sol.md
index a84c376c..109c43e4 100644
--- a/doc/modules/base/surya_report/surya_report_2_CMTATBaseERC1404.sol.md
+++ b/doc/modules/base/surya_report/surya_report_4_CMTATBaseERC1404.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/2_CMTATBaseERC1404.sol | 8769828373a6eb13fa46ead7e2ff0e5978ad2d85 |
+| ./modules/4_CMTATBaseERC1404.sol | 2f40fd05006c12aae809d94f8db9f500fc0d6eba |
### Contracts Description Table
diff --git a/doc/modules/base/surya_report/surya_report_3_CMTATBaseERC20CrossChain.sol.md b/doc/modules/base/surya_report/surya_report_5_CMTATBaseERC20CrossChain.sol.md
similarity index 86%
rename from doc/modules/base/surya_report/surya_report_3_CMTATBaseERC20CrossChain.sol.md
rename to doc/modules/base/surya_report/surya_report_5_CMTATBaseERC20CrossChain.sol.md
index 952cd362..989d2adc 100644
--- a/doc/modules/base/surya_report/surya_report_3_CMTATBaseERC20CrossChain.sol.md
+++ b/doc/modules/base/surya_report/surya_report_5_CMTATBaseERC20CrossChain.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/3_CMTATBaseERC20CrossChain.sol | 7d21beb134346630d07186c7ebb7672af11df4e9 |
+| ./modules/5_CMTATBaseERC20CrossChain.sol | 5900451fe62bc9ac7ae8d5ba8b03c8851206fb60 |
### Contracts Description Table
@@ -16,19 +16,20 @@
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
| **CMTATBaseERC20CrossChain** | Implementation | ERC20CrossChainModule, CCIPModule, CMTATBaseERC1404 |||
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
-| └ | _mintOverride | Internal 🔒 | 🛑 | |
-| └ | _burnOverride | Internal 🔒 | 🛑 | |
-| └ | _minterTransferOverride | Internal 🔒 | 🛑 | |
| └ | decimals | Public ❗️ | |NO❗️ |
| └ | name | Public ❗️ | |NO❗️ |
| └ | symbol | Public ❗️ | |NO❗️ |
| └ | supportsInterface | Public ❗️ | |NO❗️ |
+| └ | _mintOverride | Internal 🔒 | 🛑 | |
+| └ | _burnOverride | Internal 🔒 | 🛑 | |
+| └ | _minterTransferOverride | Internal 🔒 | 🛑 | |
| └ | _authorizeCCIPSetAdmin | Internal 🔒 | 🛑 | onlyRole |
| └ | _checkTokenBridge | Internal 🔒 | 🛑 | whenNotPaused |
| └ | _authorizeBurnFrom | Internal 🔒 | 🛑 | onlyRole whenNotPaused |
-| └ | _update | Internal 🔒 | 🛑 | |
+| └ | _authorizeSelfBurn | Internal 🔒 | 🛑 | onlyRole whenNotPaused |
### Legend
diff --git a/doc/modules/base/surya_report/surya_report_6_CMTATBaseDebtEngine.sol.md b/doc/modules/base/surya_report/surya_report_6_CMTATBaseDebtEngine.sol.md
new file mode 100644
index 00000000..0ad6c074
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_6_CMTATBaseDebtEngine.sol.md
@@ -0,0 +1,35 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/6_CMTATBaseDebtEngine.sol | bfeb152012d9b39da44296cc36fa90eb0f2b8035 |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseDebtEngine** | Implementation | DebtEngineModule, CMTATBaseERC20CrossChain, CMTATBaseSnapshot |||
+| └ | _update | Internal 🔒 | 🛑 | |
+| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
+| └ | decimals | Public ❗️ | |NO❗️ |
+| └ | name | Public ❗️ | |NO❗️ |
+| └ | symbol | Public ❗️ | |NO❗️ |
+| └ | _authorizeDebtEngineManagement | Internal 🔒 | 🛑 | onlyRole |
+| └ | _authorizeSnapshots | Internal 🔒 | 🛑 | onlyRole |
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_6_CMTATBaseERC2612.sol.md b/doc/modules/base/surya_report/surya_report_6_CMTATBaseERC2612.sol.md
new file mode 100644
index 00000000..80d81a3c
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_6_CMTATBaseERC2612.sol.md
@@ -0,0 +1,34 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/6_CMTATBaseERC2612.sol | 45670849fccf2fa62ef778ee4800b8a0ff0040d9 |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseERC2612** | Implementation | CMTATBaseERC20CrossChain, ERC20PermitUpgradeable, MulticallUpgradeable |||
+| └ | __CMTAT_openzeppelin_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
+| └ | permit | Public ❗️ | 🛑 |NO❗️ |
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
+| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
+| └ | decimals | Public ❗️ | |NO❗️ |
+| └ | name | Public ❗️ | |NO❗️ |
+| └ | symbol | Public ❗️ | |NO❗️ |
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_4_CMTATBaseERC2771.sol.md b/doc/modules/base/surya_report/surya_report_6_CMTATBaseERC2771.sol.md
similarity index 91%
rename from doc/modules/base/surya_report/surya_report_4_CMTATBaseERC2771.sol.md
rename to doc/modules/base/surya_report/surya_report_6_CMTATBaseERC2771.sol.md
index cdef2c5f..a0e88367 100644
--- a/doc/modules/base/surya_report/surya_report_4_CMTATBaseERC2771.sol.md
+++ b/doc/modules/base/surya_report/surya_report_6_CMTATBaseERC2771.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/4_CMTATBaseERC2771.sol | 18a1e63f5488933e24d4b1c9de1d12ecd30f36af |
+| ./modules/6_CMTATBaseERC2771.sol | f10975c79346dd77a446db967bf938e9de46af36 |
### Contracts Description Table
diff --git a/doc/modules/base/surya_report/surya_report_7_CMTATBaseERC2771Snapshot.sol.md b/doc/modules/base/surya_report/surya_report_7_CMTATBaseERC2771Snapshot.sol.md
new file mode 100644
index 00000000..b0b2ff7d
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_7_CMTATBaseERC2771Snapshot.sol.md
@@ -0,0 +1,37 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/7_CMTATBaseERC2771Snapshot.sol | 73abac918adab1aa31671e98a0cecb7e6435cd73 |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseERC2771Snapshot** | Implementation | CMTATBaseERC2771, CMTATBaseSnapshot |||
+| └ | _update | Internal 🔒 | 🛑 | |
+| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
+| └ | decimals | Public ❗️ | |NO❗️ |
+| └ | name | Public ❗️ | |NO❗️ |
+| └ | symbol | Public ❗️ | |NO❗️ |
+| └ | _msgSender | Internal 🔒 | | |
+| └ | _msgData | Internal 🔒 | | |
+| └ | _contextSuffixLength | Internal 🔒 | | |
+| └ | _authorizeSnapshots | Internal 🔒 | 🛑 | onlyRole |
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_7_CMTATBaseERC7551Enforcement.sol.md b/doc/modules/base/surya_report/surya_report_7_CMTATBaseERC7551Enforcement.sol.md
new file mode 100644
index 00000000..cebd5a77
--- /dev/null
+++ b/doc/modules/base/surya_report/surya_report_7_CMTATBaseERC7551Enforcement.sol.md
@@ -0,0 +1,38 @@
+## Sūrya's Description Report
+
+### Files Description Table
+
+
+| File Name | SHA-1 Hash |
+|-------------|--------------|
+| ./modules/7_CMTATBaseERC7551Enforcement.sol | 56d471f72113e2f281ea95c65ff066fd8bd44f7c |
+
+
+### Contracts Description Table
+
+
+| Contract | Type | Bases | | |
+|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
+| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
+||||||
+| **CMTATBaseERC7551Enforcement** | Implementation | CMTATBaseERC2771, ERC20EnforcementERC7551Module |||
+| └ | _authorizeERC20Enforcer | Internal 🔒 | 🛑 | |
+| └ | _authorizeForcedTransfer | Internal 🔒 | 🛑 | |
+| └ | _msgSender | Internal 🔒 | | |
+| └ | _msgData | Internal 🔒 | | |
+| └ | _contextSuffixLength | Internal 🔒 | | |
+| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
+| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
+| └ | name | Public ❗️ | |NO❗️ |
+| └ | symbol | Public ❗️ | |NO❗️ |
+| └ | decimals | Public ❗️ | |NO❗️ |
+| └ | getFrozenTokens | Public ❗️ | |NO❗️ |
+
+
+### Legend
+
+| Symbol | Meaning |
+|:--------:|-----------|
+| 🛑 | Function can modify state |
+| 💵 | Function is payable |
diff --git a/doc/modules/base/surya_report/surya_report_5_CMTATBaseERC1363.sol.md b/doc/modules/base/surya_report/surya_report_8_CMTATBaseERC1363.sol.md
similarity index 88%
rename from doc/modules/base/surya_report/surya_report_5_CMTATBaseERC1363.sol.md
rename to doc/modules/base/surya_report/surya_report_8_CMTATBaseERC1363.sol.md
index 300cf0d1..c291424e 100644
--- a/doc/modules/base/surya_report/surya_report_5_CMTATBaseERC1363.sol.md
+++ b/doc/modules/base/surya_report/surya_report_8_CMTATBaseERC1363.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/5_CMTATBaseERC1363.sol | dcff19612eb00bcdf0f95c3d310c597a74b41ef9 |
+| ./modules/8_CMTATBaseERC1363.sol | 55f464bf7e8bc8fb2ed679b564f13727f61bbf96 |
### Contracts Description Table
@@ -15,15 +15,15 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseERC1363** | Implementation | ERC1363Upgradeable, CMTATBaseERC2771 |||
+| **CMTATBaseERC1363** | Implementation | ERC1363Upgradeable, CMTATBaseERC7551Enforcement |||
| └ | __CMTAT_openzeppelin_init_unchained | Internal 🔒 | 🛑 | onlyInitializing |
+| └ | approve | Public ❗️ | 🛑 |NO❗️ |
| └ | transfer | Public ❗️ | 🛑 |NO❗️ |
| └ | transferFrom | Public ❗️ | 🛑 |NO❗️ |
| └ | supportsInterface | Public ❗️ | |NO❗️ |
| └ | decimals | Public ❗️ | |NO❗️ |
| └ | name | Public ❗️ | |NO❗️ |
| └ | symbol | Public ❗️ | |NO❗️ |
-| └ | _update | Internal 🔒 | 🛑 | |
| └ | _msgSender | Internal 🔒 | | |
| └ | _contextSuffixLength | Internal 🔒 | | |
| └ | _msgData | Internal 🔒 | | |
diff --git a/doc/modules/base/surya_report/surya_report_5_CMTATBaseERC7551.sol.md b/doc/modules/base/surya_report/surya_report_8_CMTATBaseERC7551.sol.md
similarity index 79%
rename from doc/modules/base/surya_report/surya_report_5_CMTATBaseERC7551.sol.md
rename to doc/modules/base/surya_report/surya_report_8_CMTATBaseERC7551.sol.md
index a9622910..83be16aa 100644
--- a/doc/modules/base/surya_report/surya_report_5_CMTATBaseERC7551.sol.md
+++ b/doc/modules/base/surya_report/surya_report_8_CMTATBaseERC7551.sol.md
@@ -5,7 +5,7 @@
| File Name | SHA-1 Hash |
|-------------|--------------|
-| ./modules/5_CMTATBaseERC7551.sol | 6fb7d71399fbf4f0135697c09f928ee646074e05 |
+| ./modules/8_CMTATBaseERC7551.sol | ea759e98774152ca4f29fcd2b45e77f401fc6103 |
### Contracts Description Table
@@ -15,7 +15,7 @@
|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:|
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
||||||
-| **CMTATBaseERC7551** | Implementation | CMTATBaseERC2771, ERC7551Module |||
+| **CMTATBaseERC7551** | Implementation | CMTATBaseERC7551Enforcement, ERC7551Module |||
| └ | _authorizeExtraInfoManagement | Internal 🔒 | 🛑 | |
diff --git a/doc/modules/controllers/validation.md b/doc/modules/controllers/validation.md
index 3ace40b6..a570f7b9 100644
--- a/doc/modules/controllers/validation.md
+++ b/doc/modules/controllers/validation.md
@@ -22,10 +22,105 @@ This document defines the Validation Module for the CMTA Token specification. Th
+## Architecture
+
+`ValidationModule` is the base transfer-check coordinator. It is extended by:
+
+- `ValidationModuleCore` — adds `canTransfer` and `canTransferFrom` (used by all standard deployment variants)
+- `ValidationModuleRuleEngine` — chains an external `RuleEngine` into the transfer path
+- `ValidationModuleAllowlist` — overrides `_canSend` / `_canReceive` with allowlist membership checks (used by the Allowlist deployment variant)
+- `ValidationModuleAllowance` — enforces pause and freeze checks on `approve` / `permit` (used by the Permit deployment variant)
+
+## Check Composition
+
+A transfer is permitted only when all of the following pass:
+
+1. **Not paused** — `PauseModule.paused()` must be false (standard transfers) or not deactivated (mint/burn).
+2. **Not frozen** — `spender`, `from`, and `to` must each pass `EnforcementModule.isFrozen`.
+3. **Not RuleEngine-blocked** — if a `RuleEngine` is set, `operateOnTransfer` must return a passing code.
+4. **Allowlist (Allowlist variant only)** — if allowlist is enabled, `from`, `to`, and `spender` must each be allowlisted.
+
+Mint and burn paths skip the spender freeze check and instead check whether the recipient (`mint`) or source (`burn`) address is frozen and whether the contract is deactivated.
+
## API for Ethereum
-> This section describes the Ethereum API of the Validation Module.
+### `ValidationModule`
+
+#### `canSend(address) → bool`
+
+```solidity
+function canSend(address account) public view virtual returns (bool allowed)
+```
+
+Returns `true` if `account` is currently permitted to send tokens (i.e., act as a sender, spender, or burn source). Base implementation returns `!isFrozen(account)`. Overridden by `ValidationModuleAllowlist` to also require allowlist membership when the allowlist is enabled.
+
+Implements `IERC7943FungibleSendReceiveCheck`.
+
+| Parameter | Type | Description |
+|-----------|---------|------------------------|
+| `account` | address | Address to check. |
+
+---
+
+#### `canReceive(address) → bool`
+
+```solidity
+function canReceive(address account) public view virtual returns (bool allowed)
+```
+
+Returns `true` if `account` is currently permitted to receive tokens (i.e., act as a recipient or mint target). Base implementation returns `!isFrozen(account)`. Overridden by `ValidationModuleAllowlist` to also require allowlist membership when the allowlist is enabled.
+
+Implements `IERC7943FungibleSendReceiveCheck`.
+
+| Parameter | Type | Description |
+|-----------|---------|------------------------|
+| `account` | address | Address to check. |
+
+---
+
+### `ValidationModuleCore`
+
+#### `canTransfer(address, address, uint256) → bool`
+
+```solidity
+function canTransfer(address from, address to, uint256 value) public view virtual returns (bool)
+```
+
+Full pre-check for a direct transfer (`msg.sender == from`). Returns `false` if the contract is paused, or if `from` or `to` is frozen (or not allowlisted when allowlist is enabled). If a `RuleEngine` is set it is also consulted.
+
+Implements ERC-3643 `IERC3643ComplianceRead`.
+
+| Parameter | Type | Description |
+|-----------|---------|---------------------------------|
+| `from` | address | Token sender. |
+| `to` | address | Token recipient. |
+| `value` | uint256 | Amount (ignored in base check). |
+
+---
+
+#### `canTransferFrom(address, address, address, uint256) → bool`
+
+```solidity
+function canTransferFrom(address spender, address from, address to, uint256 value) public view virtual returns (bool)
+```
+
+Full pre-check for a delegated transfer (`spender != from`). Extends `canTransfer` by also checking that `spender` is not frozen (or blocked by allowlist/RuleEngine).
+
+Implements ERC-7551 `IERC7551Compliance`.
+
+| Parameter | Type | Description |
+|-----------|---------|---------------------------------|
+| `spender` | address | Address executing the transfer. |
+| `from` | address | Token source. |
+| `to` | address | Token recipient. |
+| `value` | uint256 | Amount (ignored in base check). |
+
+---
+
+### Runtime Path: `_canTransferGenericByModuleAndRevert`
-This module does not provide directly public functions.
+```solidity
+function _canTransferGenericByModuleAndRevert(address spender, address from, address to) internal view virtual
+```
-
+The internal entry point called on every token movement (`_update`). Routes to the appropriate check depending on whether the operation is a mint (`from == address(0)`), burn (`to == address(0)`), or standard transfer, then reverts with `ERC7943CannotSend` or `ERC7943CannotReceive` if the check fails.
diff --git a/doc/modules/controllers/validationAllowlist.md b/doc/modules/controllers/validationAllowlist.md
index 4b5796fd..819f03a8 100644
--- a/doc/modules/controllers/validationAllowlist.md
+++ b/doc/modules/controllers/validationAllowlist.md
@@ -1,11 +1,9 @@
-# Validation RuleEngine Module
+# Validation Allowlist Module
-This document defines the Validation RuleEngine Module for the CMTA Token specification. The goal of this module is to use an external contract (`RuleEngine`) to check the validity of a transfer.
+This document defines the Validation Allowlist Module for the CMTA Token specification. It restricts token transfers so that only addresses on an administrator-managed allowlist can send or receive tokens.
[TOC]
-
-
## Schema
### Inheritance
@@ -16,12 +14,60 @@ This document defines the Validation RuleEngine Module for the CMTA Token specif

+## Architecture
+
+Two contracts together provide the allowlist enforcement layer used in the Allowlist deployment variant:
+
+| Contract | Role |
+|---|---|
+| `ValidationModuleAllowlist` | Overrides `_canSend` / `_canReceive` to add allowlist checks on top of the standard frozen-address checks for transfers, mint, and burn |
+| `ValidationModuleAllowance` | Overrides the allowance authorization path (`approve` / `permit`) to require that both `owner` and `spender` pass `_canSend` |
+
+Both extend `ValidationModule` / `ValidationModuleCore` and work alongside the existing pause and freeze checks — the allowlist check is an additional gate, not a replacement.
+
+## Transfer Enforcement (`ValidationModuleAllowlist`)
+
+### `_canSend(address account) → bool`
+
+Returns `false` (blocking the operation) when **both** conditions hold:
+1. The allowlist is enabled (`_isAllowlistEnabled()` returns true).
+2. `account` is not in the allowlist (`!isAllowlisted(account)`).
+
+When the allowlist is disabled, this falls through to the base `ValidationModule._canSend`, which returns `!isFrozen(account)`.
+
+This check applies to senders, spenders, and burn sources.
+
+### `_canReceive(address account) → bool`
+
+Identical logic applied to the recipient side (mint targets and transfer recipients).
+
+### Effect of `enableAllowlist(bool status)`
+
+| `status` | `_canSend` / `_canReceive` behaviour |
+|---|---|
+| `true` | Allowlist membership required on top of the frozen-address check |
+| `false` | Only the frozen-address check applies (allowlist is ignored) |
+
+Disabling the allowlist does **not** clear the stored membership list — re-enabling it restores the previous state.
+
+### Mint and Burn
+
+Allowlist enforcement also applies to mint and burn paths:
+- **Mint** — the recipient must pass `_canReceive` (reverts with `ERC7943CannotReceive` if not allowlisted when enabled).
+- **Burn** — the token holder must pass `_canSend` (reverts with `ERC7943CannotSend` if not allowlisted when enabled).
+
+Use `forcedTransfer` or `forcedBurn` to move tokens out of a non-allowlisted address when required.
+
+## Allowance Authorization (`ValidationModuleAllowance`)
+`ValidationModuleAllowance` guards `approve` and `permit` calls. Before an allowance is set, `_canAuthorizeAllowanceByModuleAndRevert` checks:
-## API for Ethereum
+1. The contract must not be paused.
+2. The `owner` must pass `_canSend` (not frozen; allowlisted if allowlist enabled).
+3. The `spender` must pass `_canSend` (not frozen; allowlisted if allowlist enabled).
-> This section describes the Ethereum API of the Validation Module.
+Note that the spender check here is a pre-authorization gate, not a transfer check. A spender that later becomes non-allowlisted or frozen will fail at the `transferFrom` call even if the allowance was set earlier.
-This module does not provide directly public functions.
+This module is also used by the Permit deployment variant to enforce the same checks on `permit` signatures.
-< To do>
+See also: [allowlist.md](../options/allowlist/allowlist.md) for the management API (`setAddressAllowlist`, `enableAllowlist`).
diff --git a/doc/modules/extensions/ERC20Enforcement/erc20enforcement.md b/doc/modules/extensions/ERC20Enforcement/erc20enforcement.md
index 5ab4bc76..277015c2 100644
--- a/doc/modules/extensions/ERC20Enforcement/erc20enforcement.md
+++ b/doc/modules/extensions/ERC20Enforcement/erc20enforcement.md
@@ -59,7 +59,7 @@ function getFrozenTokens(address account) external view returns (uint256 frozenB
```solidity
function getFrozenTokens(address account)
-public override(IERC7551ERC20Enforcement, IERC3643ERC20Enforcement)
+public override(IERC3643ERC20Enforcement)
view virtual
returns (uint256)
```
@@ -148,6 +148,44 @@ Unfreezes a specific amount of tokens for a given address, making them transfera
------
+##### `setFrozenTokens(address,uint256)`
+
+```solidity
+function setFrozenTokens(address account, uint256 value) external returns (bool result)
+```
+
+```solidity
+function setFrozenTokens(address account, uint256 value)
+public virtual override(IERC7943FungibleEnforcementSpecific)
+onlyERC20Enforcer
+returns (bool result)
+```
+
+Sets the frozen token balance for a given address to exactly `value`. Unlike `freezePartialTokens` / `unfreezePartialTokens` (which add or subtract), this function replaces the current frozen amount directly.
+
+The frozen amount may exceed the account's current token balance (over-freezing is allowed). The effective non-transferable amount is `min(frozenTokens, balance)`.
+
+**Parameters**
+
+| Name | Type | Description |
+| --------- | ------- | --------------------------------------------------------- |
+| `account` | address | Address whose frozen token balance will be set. |
+| `value` | uint256 | The new absolute frozen token amount for the account. |
+
+**Returns**
+
+| Name | Type | Description |
+| --------- | ---- | ----------------- |
+| `result` | bool | Always `true`. |
+
+**Emits:** `Frozen` (ERC-7943 event — amount is the new frozen balance)
+
+**Requirements:**
+
+- Only authorized users (*ERC20ENFORCER_ROLE*) are allowed to call this function.
+
+------
+
##### `forcedTransfer(address, address,uint256)`
```solidity
@@ -192,9 +230,13 @@ If needed, frozen tokens are automatically unfrozen to fulfill the transfer.
- Only authorized users (*DEFAULT_ADMIN_ROLE*) are allowed to call this function.
-### Interface:`IERC7551ERC20Enforcement`
+---
+
+> **The functions below are defined in `ERC20EnforcementERC7551Module`, not in `ERC20EnforcementModule`.** They are documented here for reference. See [ERC-7551 Module](../../options/erc7551/erc7551.md#ERC20EnforcementERC7551Module) for full details.
+
+### Interface:`IERC7551ERC20Enforcement` (via `ERC20EnforcementERC7551Module`)
-> Defines token enforcement rules, including freezing/unfreezing tokens, tracking active balances, and allowing forced transfers.
+> Defines ERC-7551 token enforcement rules: freezing/unfreezing tokens with `bytes data`, tracking active balances, and forced transfers with audit data.
------
diff --git a/doc/modules/extensions/ExtraInformation/extraInformation.md b/doc/modules/extensions/ExtraInformation/extraInformation.md
index 0150f6e3..30f74356 100644
--- a/doc/modules/extensions/ExtraInformation/extraInformation.md
+++ b/doc/modules/extensions/ExtraInformation/extraInformation.md
@@ -23,10 +23,11 @@ The ExtraInformation Module set the basic properties common to the different CMT
| Field name | Type | Setter | Description |
| ------------- | ------------------------------------------------------------ | ---------------- | ------------------------------------------------------------ |
| `tokenId` | string | `setTokenId` | ISIN or other identifier |
-| `terms` | IERC1643Document
(string name, string URI, bytes32 documentHash, uint256 lastModified) | `setTerms` | Reference to any legally required documentation about the distributed ledger or the smart contract, such as the tokenization terms, the terms of the instrument and other relevant documents (e.g. prospectus or key information document) |
-| `metaData` | string | `setMetaData` | Use case: a link towards a JSON file to describes metadata. See [ERC-7551](https://ethereum-magicians.org/t/erc-7551-crypto-security-token-smart-contract-interface-ewpg/16416) |
+| `terms` | `CMTATTerms` (`string name`, `IERC1643.Document doc`) — setter takes `IERC1643CMTAT.DocumentInfo` (`string name`, `string uri`, `bytes32 documentHash`) | `setTerms` | Reference to any legally required documentation about the distributed ledger or the smart contract, such as the tokenization terms, the terms of the instrument and other relevant documents (e.g. prospectus or key information document) |
| `information` | string | `setInformation` | Supplementary information related to the token |
+> **Note:** `metaData` / `setMetaData` are **not** part of `ExtraInformationModule`. They are defined in `ERC7551Module` and are only available in the ERC-7551 deployment variant.
+
## Schema
@@ -47,7 +48,7 @@ The ExtraInformation Module set the basic properties common to the different CMT
### Structs
-#### `Terms`
+#### `CMTATTerms`
Represents the tokenization terms, including a name and an associated document reference.
@@ -74,17 +75,17 @@ Emitted when the information field is updated (typically containing free-form me
| ---------------- | -------- | --------------------------------------- |
| `newInformation` | `string` | The new metadata or description string. |
-#### `Term(string,(string,bytes32,uint256))`
+#### `Terms(CMTATTerms)`
```solidity
-event Term(Terms newTerm)
+event Terms(CMTATTerms newTerm)
```
Emitted when new tokenization terms are set.
-| Name | Type | Description |
-| --------- | ------- | -------------------------------------- |
-| `newTerm` | `Terms` | The new terms structure being applied. |
+| Name | Type | Description |
+| --------- | ------------- | -------------------------------------- |
+| `newTerm` | `CMTATTerms` | The new terms structure being applied. |
diff --git a/doc/modules/extensions/documentEngine/document.md b/doc/modules/extensions/documentEngine/document.md
index 72fff831..d91c5b2a 100644
--- a/doc/modules/extensions/documentEngine/document.md
+++ b/doc/modules/extensions/documentEngine/document.md
@@ -5,8 +5,11 @@ This document defines Document Module for the CMTA Token specification.
> Interface for managing documents via delegation to an external document engine contract.
> Extends `IERC1643`, the standard for document management.
-> Current status (v3.x): `DocumentEngineModule` is available in the codebase but is not integrated in any shipped deployment contract (`CMTATStandard*`, `Permit`, `Snapshot`, `Debt`, `DebtEngine`, `Allowlist`, `ERC1363`, `ERC7551`, `Light`, `UUPS`).
-> It is currently covered through dedicated test mocks only.
+> **Current status (v3.3.0):** `DocumentEngineModule` is available in the codebase but is **not** integrated in any shipped deployment contract (`CMTATStandard*`, `Permit`, `Snapshot`, `Debt`, `DebtEngine`, `Allowlist`, `ERC1363`, `ERC7551`, `Light`, `UUPS`). It is exercised through dedicated test mocks only (`CMTATDocumentEngineModuleMock`).
+>
+> All shipped deployment variants **except Light** integrate `DocumentERC1643Module` instead — the native in-contract ERC-1643 implementation that stores documents directly in the token contract and uses `DOCUMENT_ROLE` for write authorization. The Light variant (`CMTATBaseCore`) does not extend `CMTATBaseDocument` and therefore has no ERC-1643 document support. `DocumentEngineModule` uses the separate `DOCUMENT_ENGINE_ROLE` to authorize `setDocumentEngine`, `setDocument`, and `removeDocument` calls forwarded to the external engine.
+>
+> See [`doc/technical/document.md`](../../../../doc/technical/document.md) for a comparison of both patterns.
[TOC]
@@ -130,14 +133,14 @@ A structure that stores metadata for a document.
#### Functions
-##### `getDocument(string)->((string,bytes32,uint256))`
+##### `getDocument(bytes32)->((string,bytes32,uint256))`
```public
-function getDocument(string name) external view returns (Document doc)
+function getDocument(bytes32 name) external view returns (Document doc)
```
```solidity
-function getDocument(string memory name)
+function getDocument(bytes32 name)
public view virtual override(IERC1643)
returns (Document memory document)
```
@@ -146,9 +149,9 @@ Retrieves a document by its name.
###### Parameters
-| Name | Type | Description |
-| ------ | -------- | -------------------------------------- |
-| `name` | `string` | The unique identifier of the document. |
+| Name | Type | Description |
+| ------ | --------- | -------------------------------------- |
+| `name` | `bytes32` | The unique identifier of the document. |
@@ -162,22 +165,72 @@ Retrieves a document by its name.
------
-##### `getAllDocuments()-> string[]`
+##### `getAllDocuments()-> bytes32[]`
```solidity
-function getAllDocuments() external view returns (string[] names)
+function getAllDocuments() external view returns (bytes32[] names)
```
```solidity
function getAllDocuments()
public view virtual override(IERC1643)
-returns (string[] memory documentNames_)
+returns (bytes32[] memory documentNames_)
```
Returns the list of all registered document names.
###### Returns
-| Name | Type | Description |
-| ---------------- | ---------- | ---------------------------------- |
-| `documentNames_` | `string[]` | Array of all document identifiers. |
+| Name | Type | Description |
+| ---------------- | ----------- | ---------------------------------- |
+| `documentNames_` | `bytes32[]` | Array of all document identifiers. |
+
+------
+
+##### `setDocument(bytes32,string,bytes32)`
+
+```solidity
+function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external
+```
+
+```solidity
+function setDocument(bytes32 name, string calldata uri, bytes32 documentHash)
+public virtual override(IERC1643)
+onlyDocumentManager
+```
+
+Forwards a document write to the external engine. Creates or updates the document identified by `name`.
+
+###### Parameters
+
+| Name | Type | Description |
+| -------------- | --------- | ---------------------------------------- |
+| `name` | `bytes32` | The unique identifier of the document. |
+| `uri` | `string` | URI pointing to the off-chain document. |
+| `documentHash` | `bytes32` | Cryptographic hash of the document. |
+
+**Requirements:** caller must have `DOCUMENT_ENGINE_ROLE`.
+
+------
+
+##### `removeDocument(bytes32)`
+
+```solidity
+function removeDocument(bytes32 name) external
+```
+
+```solidity
+function removeDocument(bytes32 name)
+public virtual override(IERC1643)
+onlyDocumentManager
+```
+
+Forwards a document removal to the external engine.
+
+###### Parameters
+
+| Name | Type | Description |
+| ------ | --------- | -------------------------------------- |
+| `name` | `bytes32` | The unique identifier of the document. |
+
+**Requirements:** caller must have `DOCUMENT_ENGINE_ROLE`.
diff --git a/doc/modules/extensions/snapshotEngine/Snapshot.md b/doc/modules/extensions/snapshotEngine/Snapshot.md
index 2aa1614f..493ff0ea 100644
--- a/doc/modules/extensions/snapshotEngine/Snapshot.md
+++ b/doc/modules/extensions/snapshotEngine/Snapshot.md
@@ -90,3 +90,53 @@ Returns the currently active snapshot engine.
| Returns | Type | Description |
| ---------------- | ----------------- | --------------------------------------------- |
| `snapshotEngine` | `ISnapshotEngine` | Address of the currently set snapshot engine. |
+
+---
+
+## Base Mixins
+
+### `CMTATBaseSnapshot` (level 0)
+
+`contracts/modules/0_CMTATBaseSnapshot.sol`
+
+A pure mixin that composes `ERC20Upgradeable` and `SnapshotEngineModule`. Its sole role is to wire the ERC-20 `_update` hook into the snapshot engine:
+
+```solidity
+abstract contract CMTATBaseSnapshot is ERC20Upgradeable, SnapshotEngineModule {
+ function _update(address from, address to, uint256 amount) internal virtual override(ERC20Upgradeable) {
+ ISnapshotEngine snapshotEngineLocal = snapshotEngine();
+ if (address(snapshotEngineLocal) != address(0)) {
+ // snapshot balances before transfer, then call operateOnTransfer
+ ERC20Upgradeable._update(from, to, amount);
+ snapshotEngineLocal.operateOnTransfer(from, to, fromBalanceBefore, toBalanceBefore, totalSupplyBefore);
+ } else {
+ ERC20Upgradeable._update(from, to, amount);
+ }
+ }
+}
+```
+
+`CMTATBaseSnapshot` is intentionally access-control-free: the `_authorizeSnapshots()` hook is left `abstract` and is implemented by the consuming base contract (typically enforcing `SNAPSHOOTER_ROLE`).
+
+### `CMTATBaseERC2771Snapshot` (level 7)
+
+`contracts/modules/7_CMTATBaseERC2771Snapshot.sol`
+
+Combines `CMTATBaseERC2771` (gasless meta-transactions via ERC-2771 trusted forwarder) with `CMTATBaseSnapshot`. It resolves the `_update` diamond-inheritance ambiguity by delegating to `CMTATBaseSnapshot._update`, and similarly resolves `_msgSender` / `_msgData` / `_contextSuffixLength` in favour of `CMTATBaseERC2771`.
+
+This is the base contract used by the dedicated **Snapshot** deployment variants (`CMTATStandaloneSnapshot`, `CMTATUpgradeableSnapshot`).
+
+---
+
+## Deployment Variants
+
+| Variant | Base | Available snapshot support |
+|---|---|---|
+| `CMTATStandaloneSnapshot` | `CMTATBaseERC2771Snapshot` | Yes — dedicated variant |
+| `CMTATUpgradeableSnapshot` | `CMTATBaseERC2771Snapshot` | Yes — dedicated variant |
+| `CMTATStandaloneDebt` | `CMTATBaseDebt` (inherits `CMTATBaseSnapshot`) | Yes — included by default |
+| `CMTATUpgradeableDebt` | `CMTATBaseDebt` | Yes — included by default |
+| `CMTATStandaloneDebtEngine` | `CMTATBaseDebtEngine` (inherits `CMTATBaseSnapshot`) | Yes — included by default |
+| `CMTATUpgradeableDebtEngine` | `CMTATBaseDebtEngine` | Yes — included by default |
+
+Issuers using the `Debt` or `DebtEngine` deployment variants do **not** need to deploy the separate `Snapshot` variant to access SnapshotEngine support — it is already available through `CMTATBaseSnapshot` in those base contracts.
diff --git a/doc/modules/options/allowlist/allowlist.md b/doc/modules/options/allowlist/allowlist.md
index eb939a9d..57361b14 100644
--- a/doc/modules/options/allowlist/allowlist.md
+++ b/doc/modules/options/allowlist/allowlist.md
@@ -225,4 +225,21 @@ returns (bool)
| ---- | ------------------------------- |
| bool | `true` if allowlist is enabled. |
+---
+
+## Transfer Enforcement
+
+The allowlist management API above only controls *who is on the list*. The actual enforcement during transfers is performed by `ValidationModuleAllowlist`, which overrides `_canSend` and `_canReceive` from `ValidationModule`:
+
+- **`_canSend(account)`** — returns `false` when the allowlist is enabled and `account` is not allowlisted (in addition to the base frozen-address check).
+- **`_canReceive(account)`** — same logic applied to the recipient side.
+
+When the allowlist is disabled (`enableAllowlist(false)`), these checks are skipped and the standard frozen-address checks apply.
+
+The same allowlist check is applied to mint and burn targets. Minting to a non-allowlisted address or burning from a non-allowlisted address reverts with `ERC7943CannotReceive` / `ERC7943CannotSend` when the allowlist is enabled.
+
+Allowance authorization (`approve`, `permit`) is separately guarded by `ValidationModuleAllowance`: both `owner` and `spender` must pass `_canSend` (i.e., must be allowlisted when the allowlist is enabled and not frozen).
+
+See also: [validationAllowlist.md](../../controllers/validationAllowlist.md)
+
diff --git a/doc/modules/options/erc7551/erc7551.md b/doc/modules/options/erc7551/erc7551.md
index 857aa396..fbcb4bee 100644
--- a/doc/modules/options/erc7551/erc7551.md
+++ b/doc/modules/options/erc7551/erc7551.md
@@ -11,7 +11,7 @@ This document covers the ERC-7551 modules for the CMTA Token specification. Two
`ERC20EnforcementERC7551Module` is also composed in `CMTATBaseERC7551Enforcement` (`contracts/modules/7_CMTATBaseERC7551Enforcement.sol`), which is used by the Standard deployment variants (`CMTATStandardStandalone`, `CMTATStandardUpgradeable`).
-It is also composed in `CMTATBaseAllowlist` (`contracts/modules/2_CMTATBaseAllowlist.sol`), so Allowlist deployment variants expose the same ERC-7551 enforcement functions.
+It is also composed in `CMTATBaseAllowlist` (`contracts/modules/3_CMTATBaseAllowlist.sol`), so Allowlist deployment variants expose the same ERC-7551 enforcement functions.
[TOC]
diff --git a/doc/modules/security/access.md b/doc/modules/security/access.md
index 44842ebb..1ddb09f1 100644
--- a/doc/modules/security/access.md
+++ b/doc/modules/security/access.md
@@ -32,6 +32,19 @@ This behavior is implemented by overriding the function `hasRole` from OpenZeppe

+## rc1 — Document Composition Level (Hierarchy Refactor)
+
+In v3.3.0-rc1, a new `CMTATBaseDocument` contract was introduced at **level 1** in the inheritance hierarchy. It is a thin composition wrapper (`abstract contract CMTATBaseDocument is DocumentERC1643Module {}`) that places document module composition at a distinct inheritance level below the RBAC layer.
+
+Key points:
+- `DOCUMENT_ROLE` is defined as a constant in `DocumentERC1643Module` (the wrapper module).
+- `_authorizeDocumentManagement()` is declared abstract in `DocumentERC1643Module` and is inherited through `CMTATBaseDocument` without implementation.
+- The concrete implementation of `_authorizeDocumentManagement()` remains in `CMTATBaseAccessControl` (level 2): `onlyRole(DOCUMENT_ROLE)`.
+
+The practical effect is that the document composition level is now separable from RBAC management, and the dependency graph makes the layering explicit.
+
## API for Ethereum
See [docs.openzeppelin.com - AccessControl](https://docs.openzeppelin.com/contracts/5.x/api/access#AccessControl)
+
+For the full role and function table, see [access-control.md](../../technical/access-control.md).
diff --git a/doc/technical/access-control.md b/doc/technical/access-control.md
index b96f879b..3d5b980a 100644
--- a/doc/technical/access-control.md
+++ b/doc/technical/access-control.md
@@ -24,7 +24,8 @@ See also [docs.openzeppelin.com - AccessControl](https://docs.openzeppelin.com/c
| `ENFORCER_ROLE` | EnforcementModule | `0x973ef39d76cc2c6090feab1c030bec6ab5db557f64df047a4c4f9b5953cf1df3` |
| `PAUSER_ROLE` | PauseModule | `0x65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a` |
| `SNAPSHOOTER_ROLE` | SnapshotEngineModule | `0x809a0fc49fc0600540f1d39e23454e1f6f215bc7505fa22b17c154616570ddef` |
-| `DOCUMENT_ROLE` | DocumentEngineModule | `0xdd7c9aafbb91d54fb2041db1d5b172ea665309b32f5fffdbddf452802a1e3b20` |
+| `DOCUMENT_ROLE` | DocumentERC1643Module | `0xdd7c9aafbb91d54fb2041db1d5b172ea665309b32f5fffdbddf452802a1e3b20` |
+| `DOCUMENT_ENGINE_ROLE` | DocumentEngineModule | `0x2d6f031e2eef5fafe7386c46356b86b5ed6513f04e6bcb34d4e0ff161e332117` |
| `EXTRA_INFORMATION_ROLE` | ExtraInformationModule | `0x921df7a58eb4ea112afa962b8186161404ecda2e8fe97f8246026d02ad1a74b7` |
| `ERC20ENFORCER_ROLE` | ERC20EnforcementModule | `0xd62f75bf68b069bc8e2abd495a949fafec67a4e5a5b7cb36aedf0dd51eec7e72` |
| `ALLOWLIST_ROLE` | AllowlistModule | `0x26a560d834a19637eccba4611bbc09fb32970bb627da0a70f14f83fdc9822cbc` |
@@ -53,10 +54,26 @@ See also [docs.openzeppelin.com - AccessControl](https://docs.openzeppelin.com/c
| **ERC20EnforcementModule** | `forcedTransfer(address, address, uint256)` | `DEFAULT_ADMIN_ROLE` |
| | `freezePartialTokens(address, uint256)` | `ERC20ENFORCER_ROLE` |
| | `unfreezePartialTokens(address, uint256)` | `ERC20ENFORCER_ROLE` |
+| | `setFrozenTokens(address, uint256)` | `ERC20ENFORCER_ROLE` |
+| **ERC20EnforcementERC7551Module** | `forcedTransfer(address, address, uint256, bytes)` | `DEFAULT_ADMIN_ROLE` |
+| | `freezePartialTokens(address, uint256, bytes)` | `ERC20ENFORCER_ROLE` |
+| | `unfreezePartialTokens(address, uint256, bytes)` | `ERC20ENFORCER_ROLE` |
+| **ExtraInformationModule** | `setTokenId(string)` | `EXTRA_INFORMATION_ROLE` |
+| | `setTerms(DocumentInfo)` | `EXTRA_INFORMATION_ROLE` |
+| | `setInformation(string)` | `EXTRA_INFORMATION_ROLE` |
+| **ERC7551Module** | `setMetaData(string)` | `EXTRA_INFORMATION_ROLE` |
+| | `setTerms(bytes32, string)` | `EXTRA_INFORMATION_ROLE` |
| **SnapshotEngineModule** | `setSnapshotEngine(address)` | `SNAPSHOOTER_ROLE` |
-| **DocumentEngineModule** | `setDocumentEngine(address)` | `DOCUMENT_ROLE` |
+| **DocumentERC1643Module** | `setDocument(bytes32, string, bytes32)` | `DOCUMENT_ROLE` |
+| | `removeDocument(bytes32)` | `DOCUMENT_ROLE` |
+| **DocumentEngineModule** | `setDocumentEngine(address)` | `DOCUMENT_ENGINE_ROLE` |
+| | `setDocument(bytes32, string, bytes32)` | `DOCUMENT_ENGINE_ROLE` |
+| | `removeDocument(bytes32)` | `DOCUMENT_ENGINE_ROLE` |
| **AllowlistModule** | `setAddressAllowlist(address, bool)` | `ALLOWLIST_ROLE` |
+| | `setAddressAllowlist(address, bool, bytes)` | `ALLOWLIST_ROLE` |
| | `batchSetAddressAllowlist(address[], bool[])` | `ALLOWLIST_ROLE` |
+| | `enableAllowlist(bool)` | `ALLOWLIST_ROLE` |
+| **ValidationModuleRuleEngine** | `setRuleEngine(address)` | `DEFAULT_ADMIN_ROLE` |
| **DebtModule** | `setDebt(...)` | `DEBT_ROLE` |
| | `setCreditEvents(...)` | `DEBT_ROLE` |
| **DebtEngineModule** | `setDebtEngine(address)` | `DEBT_ENGINE_ROLE` |
diff --git a/doc/technical/debt.md b/doc/technical/debt.md
index 0e2c9208..e09e5495 100644
--- a/doc/technical/debt.md
+++ b/doc/technical/debt.md
@@ -101,8 +101,18 @@ The `DebtEngine` must implement `debt()` and `creditEvents()`. The CMTAT token c
| `CMTATStandaloneDebtEngine` | Standalone with external DebtEngine |
| `CMTATUpgradeableDebtEngine` | Upgradeable with external DebtEngine |
-The `Debt` version includes `DebtModule` but does not include `ERC2771Module` or `ERC20CrossChain`.
-The `DebtEngine` version includes `DebtEngineModule`, `ERC20CrossChain`, and ERC-1404 functionality, but does not include `ERC2771Module`.
+### Feature Matrix
+
+| Feature | `Debt` | `DebtEngine` |
+|---|---|---|
+| `DebtModule` (on-chain debt data) | ✓ | — |
+| `DebtEngineModule` (external engine) | — | ✓ |
+| `ERC20CrossChainModule` + `CCIPModule` | — | ✓ |
+| ERC-1404 (`restrictedTransferOf`) | — | ✓ (via `CMTATBaseERC20CrossChain`) |
+| `ERC2771Module` (meta-transactions) | — | — |
+| `CMTATBaseSnapshot` (SnapshotEngine support) | ✓ | ✓ |
+
+Both `Debt` and `DebtEngine` variants include `CMTATBaseSnapshot`, which wires the ERC-20 `_update` hook into an optional external `SnapshotEngine`. This means snapshot functionality is available on these variants without deploying the dedicated `Snapshot` deployment variant. Set the engine with `setSnapshotEngine(address)` (requires `SNAPSHOOTER_ROLE`).
## Compatible DebtEngine Releases
diff --git a/doc/technical/deployment.md b/doc/technical/deployment.md
index 48385188..9664143f 100644
--- a/doc/technical/deployment.md
+++ b/doc/technical/deployment.md
@@ -86,11 +86,16 @@ CMTAT implements [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) for namespa
| ERC20BaseModule | `ERC20BaseModuleStorageLocation` | `0x9bd8d607565c0370ae5f91651ca67fd26d4438022bf72037316600e29e6a3a00` |
| PauseModule | - | `0xab1527b6135145d8da1edcbd6b7b270624e17f2b41c74a8c746ff388ad454700` |
| DocumentEngineModule | `DocumentEngineModuleStorageLocation` | `0xbd0905600c85d707dc53eba2e146c1c2527cd32ac3ff6b86846155151b3e2700` |
+| DocumentERC1643Module | `DocumentERC1643ModuleStorageLocation` | `0x24fbb1cf6345ced60d5278ef6f68f4f7576fd9068704b4c8f1eec8f0bbd8a200` |
| ExtraInformationModule | `ExtraInformationModuleStorageLocation` | `0xd2d5d34c4a4dea00599692d3257c0aebc5e0359176118cd2364ab9b008c2d100` |
| SnapshotEngineModule | `SnapshotEngineModuleStorageLocation` | `0x1387b97dfab601d3023cb57858a6be29329babb05c85597ddbe4926c1193a900` |
| CCIPModule | `CCIPModuleStorageLocation` | `0x364fbfd89c0eee55bbc8dd10b1a9bf3e04fba9f3ee606f4c79a82f9941ad7a00` |
| DebtModule | `DebtModuleStorageLocation` | `0xf8a315cc5f2213f6481729acd86e55db7ccc930120ccf9fb78b53dcce75f7c00` |
| ERC7551Module | `ERC7551ModuleStorageLocation` | `0x2727314c926b592b6f70e7d6d2e4677ebcac070f293306927f71fe77858eec00` |
+| OZ `EIP712Upgradeable` | `EIP712StorageLocation` | `0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100` |
+| OZ `NoncesUpgradeable` | `NoncesStorageLocation` | `0x5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb00` |
+
+`EIP712Upgradeable` and `NoncesUpgradeable` slots are present in the Permit deployment variants (`CMTATStandalonePermit`, `CMTATUpgradeablePermit`) which inherit `ERC20PermitUpgradeable`. `DocumentERC1643Module` is present in all deployment variants that extend `CMTATBaseDocument` (level 1) — which is every variant except the mock/generic base. `ValidationModuleAllowance` has no dedicated ERC-7201 slot; it is stateless.
## Initialize Functions
diff --git a/doc/technical/document.md b/doc/technical/document.md
index ce00ab84..3719d45b 100644
--- a/doc/technical/document.md
+++ b/doc/technical/document.md
@@ -3,21 +3,23 @@
CMTAT supports two layers of on-chain document management:
1. **Terms** — the primary tokenization terms stored directly in the token contract via `ExtraInformationModule`.
-2. **Additional documents** — arbitrary documents managed through an external `DocumentEngine` contract via `DocumentEngineModule`.
+2. **Additional documents** — arbitrary ERC-1643 documents, managed through one of two patterns:
+ - **Native storage** (`DocumentERC1643Module`) — documents are stored directly in the token contract (available in all shipped deployment variants).
+ - **External engine delegation** (`DocumentEngineModule`) — documents are managed by a separate `DocumentEngine` contract (not integrated in any shipped deployment variant; available as an optional module).
## Terms
-The tokenization terms are a single `Terms` struct stored in the token contract. They can be read by anyone and set by an address with `EXTRA_INFORMATION_ROLE`.
+The tokenization terms are a single `CMTATTerms` struct stored in the token contract. They can be read by anyone and set by an address with `EXTRA_INFORMATION_ROLE`.
```solidity
interface ICMTATBase {
- struct Terms {
+ struct CMTATTerms {
string name;
IERC1643.Document doc;
}
- event Term(Terms newTerm);
+ event Terms(CMTATTerms newTerm);
- function terms() external view returns (Terms memory);
+ function terms() external view returns (CMTATTerms memory);
function setTerms(IERC1643CMTAT.DocumentInfo calldata terms_) external;
}
```
@@ -27,9 +29,46 @@ A `Document` contains:
- `documentHash` — hash of the document contents for integrity verification
- `lastModified` — block timestamp of the last on-chain update (set automatically)
-## Additional Documents via DocumentEngine (ERC-1643)
+## Additional Documents — Native Storage (`DocumentERC1643Module`)
-The `DocumentEngine` is an optional external contract that implements [ERC-1643](https://github.com/ethereum/EIPs/issues/1643). CMTAT exposes two read-only functions delegated to the engine:
+`DocumentERC1643Module` implements the full ERC-1643 interface directly inside the token contract. Documents are stored in ERC-7201 namespaced storage (slot `0x24fbb1cf6345ced60d5278ef6f68f4f7576fd9068704b4c8f1eec8f0bbd8a200`) using:
+
+```solidity
+mapping(bytes32 => Document) _documents;
+bytes32[] _documentNames;
+mapping(bytes32 => uint256) _documentKey; // 1-based index for O(1) removal
+```
+
+All four ERC-1643 operations are available on the token contract directly — no external engine contract is required:
+
+```solidity
+function getDocument(bytes32 name) external view returns (Document memory);
+function getAllDocuments() external view returns (bytes32[] memory);
+function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external; // requires DOCUMENT_ROLE
+function removeDocument(bytes32 name) external; // requires DOCUMENT_ROLE
+```
+
+`removeDocument` uses a swap-and-pop pattern to keep the `_documentNames` array compact without gaps.
+
+### Deployment Availability
+
+`DocumentERC1643Module` is included in **all shipped CMTAT deployment variants except Light** through the inheritance chain `CMTATBaseDocument` (level 1) → `CMTATBaseAccessControl` (level 2). The Light variant (`CMTATStandaloneLight`, `CMTATUpgradeableLight`) is based on `CMTATBaseCore`, which does not extend `CMTATBaseDocument` and therefore has no ERC-1643 document support. The concrete access-control hook `_authorizeDocumentManagement()` enforces `DOCUMENT_ROLE` in `CMTATBaseAccessControl`.
+
+### Trade-offs vs External Engine
+
+| | `DocumentERC1643Module` | `DocumentEngineModule` |
+|---|---|---|
+| Storage location | On-chain in token contract | External engine contract |
+| Deployment complexity | None (built-in) | Requires separate engine deployment |
+| Token contract size | Adds storage overhead | Keeps token contract lean |
+| Multi-token sharing | One registry per token | One engine can serve many tokens |
+| Shipped in CMTAT variants | Yes (all standard variants) | No |
+
+---
+
+## Additional Documents via External Engine (`DocumentEngineModule`)
+
+`DocumentEngineModule` is an alternative to the native storage pattern. Instead of storing documents in the token contract, it delegates all four ERC-1643 operations to a separate `DocumentEngine` contract that implements [ERC-1643](https://github.com/ethereum/EIPs/issues/1643):
```solidity
interface IERC1643 {
@@ -39,18 +78,20 @@ interface IERC1643 {
uint256 lastModified;
}
- function getDocument(string memory name) external view returns (Document memory doc);
- function getAllDocuments() external view returns (string[] memory);
+ function getDocument(bytes32 name) external view returns (Document memory doc);
+ function getAllDocuments() external view returns (bytes32[] memory);
+ function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external;
+ function removeDocument(bytes32 name) external;
}
```
-**Note**: CMTAT uses `string` for document names instead of the `bytes32` proposed by the EIP, to allow names longer than 32 characters.
+**Note**: CMTAT uses `bytes32` for document identifiers, aligned with the original EIP proposal. Only the CMTAT tokenization terms path (`IERC1643CMTAT.DocumentInfo`) still uses `string name`.
-The engine is completely free to implement its own storage and management logic. CMTAT only calls `getDocument` and `getAllDocuments` — it never writes to the engine.
+The engine is completely free to implement its own storage and management logic. CMTAT forwards both read (`getDocument`, `getAllDocuments`) and write (`setDocument`, `removeDocument`) calls to the engine. Write operations and engine configuration require `DOCUMENT_ENGINE_ROLE`.
### Setting the DocumentEngine
-An address with `DOCUMENT_ROLE` can update the engine:
+An address with `DOCUMENT_ENGINE_ROLE` can update the engine:
```solidity
function setDocumentEngine(address documentEngine_) external;
diff --git a/doc/technical/erc7551.md b/doc/technical/erc7551.md
new file mode 100644
index 00000000..2548b936
--- /dev/null
+++ b/doc/technical/erc7551.md
@@ -0,0 +1,270 @@
+# ERC-7551 Implementation Guide
+
+ERC-7551 is a draft standard for regulated crypto-securities, designed to meet the requirements of the German **eWpG** (Gesetz über elektronische Wertpapiere — Electronic Securities Act). It extends ERC-20 with compliance checks, operator-controlled minting and burning, partial freeze, forced transfers, and on-chain anchoring of legally binding documents.
+
+> CMTAT is a co-author of the ERC-7551 specification. The draft is available at [`doc/ERCSpecification/draft-erc-7551-ewpg.md`](../ERCSpecification/draft-erc-7551-ewpg.md).
+
+---
+
+## Scope
+
+ERC-7551 support in CMTAT is split across two modules that appear in different deployment variants:
+
+| Module | What it adds | Variants that include it |
+|---|---|---|
+| `ERC20EnforcementERC7551Module` | `bytes data` overloads for enforcement functions; `getActiveBalanceOf` | Standard, ERC-7551, ERC-1363, Allowlist |
+| `ERC7551Module` | `metaData` / `setMetaData`; ERC-7551 `setTerms(bytes32,string)` / `termsHash` | ERC-7551 only |
+
+The **ERC-7551 deployment variant** (`CMTATStandaloneERC7551` / `CMTATUpgradeableERC7551`) is the only variant that satisfies the full ERC-7551 document interface. All other variants that include `ERC20EnforcementERC7551Module` expose the enforcement interface but not the document interface.
+
+---
+
+## Inheritance Chain
+
+```
+CMTATBaseERC2771 (6)
+ └── CMTATBaseERC7551Enforcement (7) — adds ERC20EnforcementERC7551Module
+ ├── CMTATBaseERC1363 (8) — adds ERC-1363 transferAndCall [ERC-1363 variant]
+ └── CMTATBaseERC7551 (8) — adds ERC7551Module [ERC-7551 variant only]
+```
+
+`CMTATBaseERC7551Enforcement` (level 7) is also the direct base of `CMTATStandardStandalone` / `CMTATStandardUpgradeable`, so Standard deployments expose the ERC-7551 enforcement functions without the document interface.
+
+`CMTATBaseAllowlist` (level 3) independently composes `ERC20EnforcementERC7551Module` (outside the Standard chain), so the Allowlist variant also exposes the enforcement interface.
+
+---
+
+## Deployment Contracts
+
+| Type | Contract | File |
+|---|---|---|
+| Standalone (immutable) | `CMTATStandaloneERC7551` | `contracts/deployment/ERC7551/CMTATStandaloneERC7551.sol` |
+| Upgradeable (Transparent / Beacon proxy) | `CMTATUpgradeableERC7551` | `contracts/deployment/ERC7551/CMTATUpgradeableERC7551.sol` |
+
+Both extend `CMTATBaseERC7551` (`contracts/modules/8_CMTATBaseERC7551.sol`).
+
+### Constructor / Initializer Parameters
+
+```solidity
+constructor(
+ address forwarderIrrevocable,
+ address admin,
+ ICMTATConstructor.ERC20Attributes memory ERC20Attributes_,
+ ICMTATConstructor.ExtraInformationAttributes memory extraInformationAttributes_,
+ ICMTATConstructor.Engine memory engines_
+)
+```
+
+| Parameter | Type | Description |
+|---|---|---|
+| `forwarderIrrevocable` | `address` | ERC-2771 trusted forwarder (irrevocable after deployment). Pass `address(0)` to disable. |
+| `admin` | `address` | Address receiving `DEFAULT_ADMIN_ROLE`. |
+| `ERC20Attributes_.name` | `string` | Token name. |
+| `ERC20Attributes_.symbol` | `string` | Token symbol. |
+| `ERC20Attributes_.decimalsIrrevocable` | `uint8` | Token decimals (irrevocable). |
+| `extraInformationAttributes_.tokenId` | `string` | ISIN or other identifier. |
+| `extraInformationAttributes_.terms` | `DocumentInfo` | Initial tokenization terms (name, URI, hash). |
+| `extraInformationAttributes_.information` | `string` | Supplementary information string. |
+| `engines_.ruleEngine` | `IRuleEngine` | External rule engine (pass `address(0)` for none). |
+
+The ERC-7551 variant has the same parameter set as Standard. `metaData` is not set at construction time — call `setMetaData` after deployment.
+
+---
+
+## ERC7551Module
+
+`contracts/modules/wrapper/options/ERC7551Module.sol`
+
+Implements `IERC7551Document`. Extends `ExtraInformationModule` and adds ERC-7551-specific document and metadata functions.
+
+### Storage
+
+Uses ERC-7201 namespaced storage (`CMTAT.storage.ERC7551Module`) for the `_metadata` string. The `terms` fields (hash, URI) are stored in `ExtraInformationModule`'s storage and accessed via `_setTerms` / `terms()`.
+
+### Events
+
+#### `MetaData(string)`
+
+```solidity
+event MetaData(string newMetaData)
+```
+
+Emitted when the metadata string is updated.
+
+#### `Terms(bytes32,string)`
+
+```solidity
+event Terms(bytes32 hash_, string uri_)
+```
+
+Emitted when the terms hash and URI are updated via the ERC-7551 `setTerms(bytes32, string)` overload.
+
+> This is different from the CMTAT `Terms(CMTATTerms)` event emitted by `ExtraInformationModule.setTerms(DocumentInfo)`. Both setters write to the same underlying `terms` storage, but they emit distinct events and accept different parameters.
+
+### Functions
+
+#### `metaData() → string`
+
+```solidity
+function metaData() public view virtual override(IERC7551Document) returns (string memory metadata_)
+```
+
+Returns the metadata string. Per ERC-7551, this MUST be a JSON object of key-value pairs describing the crypto security. It MAY be empty.
+
+#### `setMetaData(string)`
+
+```solidity
+function setMetaData(string calldata metadata_) public virtual override(IERC7551Document) onlyExtraInfoManager
+```
+
+Sets the metadata string. Emits `MetaData(metadata_)`.
+
+**Requirements:** caller must have `EXTRA_INFORMATION_ROLE`.
+
+#### `termsHash() → bytes32`
+
+```solidity
+function termsHash() public view virtual override(IERC7551Document) returns (bytes32 hash_)
+```
+
+Returns `terms().doc.documentHash` — the SHA-256 hash of the current terms document. A value of `bytes32(0)` means no terms have been set.
+
+#### `setTerms(bytes32,string)`
+
+```solidity
+function setTerms(bytes32 hash_, string calldata uri_) public virtual override(IERC7551Document) onlyExtraInfoManager
+```
+
+Sets the terms document hash and URI using the ERC-7551 signature. Internally calls `_setTerms(DocumentInfo("", uri_, hash_))` to write the shared `terms` storage, then emits `Terms(hash_, uri_)`.
+
+**Requirements:** caller must have `EXTRA_INFORMATION_ROLE`.
+
+**Note:** the `name` field of `CMTATTerms` is set to `""` when using this setter. To preserve a name, use `ExtraInformationModule.setTerms(DocumentInfo)` instead.
+
+---
+
+## ERC20EnforcementERC7551Module
+
+`contracts/modules/wrapper/options/ERC20EnforcementERC7551Module.sol`
+
+Implements `IERC7551ERC20Enforcement`. Extends `ERC20EnforcementModule` and adds `bytes data` overloads for enforcement functions (for audit trails) plus the `getActiveBalanceOf` view.
+
+### Functions
+
+#### `getActiveBalanceOf(address) → uint256`
+
+```solidity
+function getActiveBalanceOf(address account) public view virtual override(IERC7551ERC20Enforcement) returns (uint256 activeBalance_)
+```
+
+Returns the number of tokens available for standard ERC-20 transfers: `balanceOf(account) - getFrozenTokens(account)`. If frozen tokens exceed the total balance, returns `0`.
+
+#### `getFrozenTokens(address) → uint256`
+
+```solidity
+function getFrozenTokens(address account) public view virtual override(ERC20EnforcementModule, IERC7551ERC20Enforcement) returns (uint256 frozenBalance_)
+```
+
+Returns the frozen token count. Overrides both `ERC20EnforcementModule` and `IERC7551ERC20Enforcement` to resolve the diamond inheritance.
+
+#### `forcedTransfer(address,address,uint256,bytes) → bool`
+
+```solidity
+function forcedTransfer(address from, address to, uint256 value, bytes calldata data)
+ public virtual override(IERC7551ERC20Enforcement) onlyForcedTransferManager returns (bool)
+```
+
+Forced transfer with a `data` payload for audit or compliance metadata. If frozen tokens must be released to cover `value`, unfreezes them automatically before transferring.
+
+Emits:
+- `ForcedTransfer(operator, from, to, value, data)` — always
+- `TokensUnfrozen(from, unfrozenAmount, data)` — only if frozen tokens were released
+
+**Requirements:** caller must have `DEFAULT_ADMIN_ROLE`.
+
+#### `freezePartialTokens(address,uint256,bytes)`
+
+```solidity
+function freezePartialTokens(address account, uint256 value, bytes calldata data)
+ public virtual override(IERC7551ERC20Enforcement) onlyERC20Enforcer
+```
+
+Freezes `value` tokens on `account`. The `data` field is attached to the emitted event for audit purposes.
+
+Emits: `TokensFrozen(account, value, data)`
+
+**Requirements:** caller must have `ERC20ENFORCER_ROLE`.
+
+#### `unfreezePartialTokens(address,uint256,bytes)`
+
+```solidity
+function unfreezePartialTokens(address account, uint256 value, bytes calldata data)
+ public virtual override(IERC7551ERC20Enforcement) onlyERC20Enforcer
+```
+
+Unfreezes `value` tokens on `account`. The `data` field is attached to the emitted event.
+
+Emits: `TokensUnfrozen(account, value, data)`
+
+**Requirements:** caller must have `ERC20ENFORCER_ROLE`.
+
+---
+
+## Interface Compliance
+
+The table below maps each ERC-7551 interface requirement to the implementing CMTAT module.
+
+| ERC-7551 interface | Function / Event | Implementing module | Available in |
+|---|---|---|---|
+| `IERC7551Mint` | `mint(address, uint256, bytes)` | `ERC20MintModule` | All |
+| | `event Mint(minter, account, value, data)` | `ERC20MintModule` | All |
+| `IERC7551Burn` | `burn(address, uint256, bytes)` | `ERC20BurnModule` | All |
+| | `event Burn(burner, account, value, data)` | `ERC20BurnModule` | All |
+| `IERC7551Pause` | `paused()` / `pause()` / `unpause()` | `PauseModule` | All |
+| `IERC7551Compliance` | `canTransfer(from, to, value)` | `ValidationModuleCore` | All |
+| | `canTransferFrom(spender, from, to, value)` | `ValidationModuleCore` | All |
+| `IERC7551ERC20Enforcement` | `getActiveBalanceOf(address)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `getFrozenTokens(address)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `forcedTransfer(from, to, value, bytes)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `freezePartialTokens(account, value, bytes)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `unfreezePartialTokens(account, value, bytes)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `event ForcedTransfer(operator, from, to, value, data)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `event TokensFrozen(account, value, data)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| | `event TokensUnfrozen(account, value, data)` | `ERC20EnforcementERC7551Module` | Standard, ERC-7551, ERC-1363, Allowlist |
+| `IERC7551Document` | `metaData()` / `setMetaData(string)` | `ERC7551Module` | ERC-7551 only |
+| | `termsHash()` | `ERC7551Module` | ERC-7551 only |
+| | `setTerms(bytes32, string)` | `ERC7551Module` | ERC-7551 only |
+| | `event MetaData(string)` | `ERC7551Module` | ERC-7551 only |
+| | `event Terms(bytes32, string)` | `ERC7551Module` | ERC-7551 only |
+
+---
+
+## setTerms Disambiguation
+
+The ERC-7551 variant exposes two `setTerms` overloads with different signatures and semantics:
+
+| Overload | Defined in | Parameters | Emits | Notes |
+|---|---|---|---|---|
+| `setTerms(DocumentInfo)` | `ExtraInformationModule` | `(string name, string uri, bytes32 hash)` | `Terms(CMTATTerms)` | Sets name, URI, and hash |
+| `setTerms(bytes32, string)` | `ERC7551Module` | `(bytes32 hash, string uri)` | `Terms(bytes32, string)` | Sets URI and hash; `name` is set to `""` |
+
+Both overloads write to the same underlying `terms` storage. Callers should use the `ExtraInformationModule` overload when a human-readable name for the terms document is required, and the `ERC7551Module` overload when strict ERC-7551 interface compatibility is needed.
+
+---
+
+## Deviations from the ERC-7551 Specification
+
+The ERC-7551 specification is a **draft**. The following requirements from the spec are not enforced in the current CMTAT implementation:
+
+| Spec requirement | CMTAT behaviour |
+|---|---|
+| `unpause()` MUST revert if `termsHash() == bytes32(0)` | Not enforced. `PauseModule.unpause()` does not check `termsHash`. The caller is responsible for setting a non-zero terms hash before unpausing in eWpG contexts. |
+
+---
+
+## See Also
+
+- [`doc/modules/options/erc7551/erc7551.md`](../modules/options/erc7551/erc7551.md) — module-level API reference
+- [`doc/modules/extensions/ERC20Enforcement/erc20enforcement.md`](../modules/extensions/ERC20Enforcement/erc20enforcement.md) — base enforcement module
+- [`doc/technical/access-control.md`](./access-control.md) — role list and function-to-role table
+- [`doc/ERCSpecification/draft-erc-7551-ewpg.md`](../ERCSpecification/draft-erc-7551-ewpg.md) — ERC-7551 specification
diff --git a/doc/technical/permit-multicall.md b/doc/technical/permit-multicall.md
index a19dbe94..b5cdd505 100644
--- a/doc/technical/permit-multicall.md
+++ b/doc/technical/permit-multicall.md
@@ -31,8 +31,8 @@ function permit(
The same checks apply to both `approve` and `permit`:
- The contract must not be paused.
-- The `owner` must be allowed to send (`canSend` returns true).
-- The `spender` must be allowed to send (`canSend` returns true).
+- The `owner` must not be frozen (checked via `canSend`).
+- The `spender` must not be frozen (checked via `canSend`). In the Allowlist deployment variant, both must also be allowlisted.
These checks are enforced in `ValidationModuleAllowance._canAuthorizeAllowanceByModuleAndRevert`.
diff --git a/doc/technical/snapshot.md b/doc/technical/snapshot.md
index e2d529d6..972489d2 100644
--- a/doc/technical/snapshot.md
+++ b/doc/technical/snapshot.md
@@ -42,7 +42,15 @@ function setSnapshotEngine(address snapshotEngine_) external;
## Deployment Versions
-The Snapshot deployment version (`CMTATStandaloneSnapshot` / `CMTATUpgradeableSnapshot`) extends the standard CMTAT with `SnapshotEngine` support via `CMTATBaseERC2771Snapshot`.
+`CMTATBaseSnapshot` (level 0) is the core mixin that wires the ERC-20 `_update` hook into an optional external `SnapshotEngine`. It is included in several deployment variants:
+
+| Deployment variant | Base | Snapshot support |
+|---|---|---|
+| `CMTATStandaloneSnapshot` / `CMTATUpgradeableSnapshot` | `CMTATBaseERC2771Snapshot` (level 7) | ✓ dedicated variant |
+| `CMTATStandaloneDebt` / `CMTATUpgradeableDebt` | `CMTATBaseDebt` (level 4, inherits `CMTATBaseSnapshot`) | ✓ built-in |
+| `CMTATStandaloneDebtEngine` / `CMTATUpgradeableDebtEngine` | `CMTATBaseDebtEngine` (level 6, inherits `CMTATBaseSnapshot`) | ✓ built-in |
+
+Issuers using the `Debt` or `DebtEngine` deployment variants can configure a `SnapshotEngine` directly on those contracts — there is no need to deploy the dedicated `Snapshot` variant to access this functionality.
It is also possible to extend CMTAT directly to include snapshot logic without an external contract. The [SnapshotEngine](https://github.com/CMTA/SnapshotEngine) repository provides such a deployment variant.
@@ -50,6 +58,7 @@ It is also possible to extend CMTAT directly to include snapshot logic without a
| CMTAT version | SnapshotEngine |
|---|---|
+| CMTAT v3.3.0 | Testing against v0.3.0-compatible engines in progress |
| CMTAT v3.0.0 | [v0.3.0](https://github.com/CMTA/SnapshotEngine/releases/tag/v0.3.0) (unaudited) |
| CMTAT v2.3.0 | SnapshotEngine v0.1.0 (unaudited) |
| CMTAT v2.4.0, v2.5.0 | Included inside SnapshotModule (unaudited) |
diff --git a/doc/technical/stablecoin.md b/doc/technical/stablecoin.md
new file mode 100644
index 00000000..c51472f8
--- /dev/null
+++ b/doc/technical/stablecoin.md
@@ -0,0 +1,427 @@
+# Stablecoin Deployment Guide
+
+This document describes the **Light** deployment variant, which is designed for stablecoins, and explains when to use one of the alternative variants (**Standard** or **Permit**) if more features are required.
+
+## Why Light for Stablecoins
+
+Stablecoins typically need a smaller set of compliance features than equity or debt instruments:
+
+- Issue and redeem tokens (mint / burn).
+- Globally freeze activity (pause).
+- Blacklist individual addresses (address freeze).
+- Emergency burn from a blacklisted address (forced burn).
+- Update name / symbol after deployment.
+
+The Light variant provides exactly these capabilities with no overhead from modules that are irrelevant to most stablecoin designs (document management, snapshots, partial balance freeze, debt fields, or cross-chain bridges). The result is a contract roughly half the size of the Standard variant.
+
+| Variant | Deployed bytecode |
+|---|---|
+| CMTAT Light | 11.298 KiB |
+| CMTAT Standard | 22.243 KiB |
+| CMTAT Permit | 23.268 KiB |
+
+The 24.576 KiB EVM deployed bytecode limit is not a concern for Light, leaving substantial room for integrators who extend the contract.
+
+---
+
+## Light Variant — Feature Set
+
+### Deployment contracts
+
+| Type | Contract | File |
+|---|---|---|
+| Standalone (immutable) | `CMTATStandaloneLight` | `contracts/deployment/light/CMTATStandaloneLight.sol` |
+| Upgradeable (Transparent / Beacon proxy) | `CMTATUpgradeableLight` | `contracts/deployment/light/CMTATUpgradeableLight.sol` |
+
+Both extend `CMTATBaseCore` (`contracts/modules/0_CMTATBaseCore.sol`), which bundles the complete feature set in a single level-0 base.
+
+### Constructor / Initializer Parameters
+
+The Light variant takes a minimal set of constructor parameters compared to other variants:
+
+```solidity
+constructor(
+ address admin,
+ ICMTATConstructor.ERC20Attributes memory ERC20Attributes_
+)
+```
+
+| Parameter | Type | Description |
+|---|---|---|
+| `admin` | `address` | Address receiving `DEFAULT_ADMIN_ROLE` on deployment. |
+| `ERC20Attributes_.name` | `string` | Token name. |
+| `ERC20Attributes_.symbol` | `string` | Token symbol. |
+| `ERC20Attributes_.decimalsIrrevocable` | `uint8` | Token decimals (irrevocable after deployment). |
+
+There is no `forwarderIrrevocable` parameter (no ERC-2771 support), no `extraInformationAttributes_` (no tokenId/terms/information), and no `engines_` (no external engine contracts).
+
+### Module Inventory
+
+| Module | What it provides |
+|---|---|
+| `ERC20BaseModule` | ERC-20 base — `transfer`, `transferFrom`, `approve`, `allowance`, `balanceOf`, `totalSupply`; updatable `name` / `symbol` (requires `DEFAULT_ADMIN_ROLE`); irrevocable `decimals` |
+| `ERC20MintModule` | `mint(address, uint256, bytes)`, `mint(address, uint256)`, `batchMint`, `batchTransfer` — requires `MINTER_ROLE` |
+| `ERC20BurnModule` | `burn(address, uint256, bytes)`, `burn(address, uint256)`, `batchBurn` — requires `BURNER_ROLE` |
+| `PauseModule` | `pause()` / `unpause()` (requires `PAUSER_ROLE`); `deactivateContract()` (requires `DEFAULT_ADMIN_ROLE`); `paused()` / `deactivated()` view functions |
+| `EnforcementModule` | `setAddressFrozen(address, bool)`, `batchSetAddressFrozen` (requires `ENFORCER_ROLE`); `isFrozen(address)` read |
+| `ValidationModule` / `ValidationModuleCore` | Enforces pause and freeze checks on every transfer, `transferFrom`, `approve`, mint, and burn; `canSend(address)`, `canReceive(address)`, `canTransfer(from, to, value)`, `canTransferFrom(spender, from, to, value)` |
+| `ValidationModuleAllowance` | Enforces pause and freeze checks on `approve` |
+| `AccessControlModule` | OpenZeppelin RBAC — `grantRole`, `revokeRole`, `renounceRole`, `hasRole`; `DEFAULT_ADMIN_ROLE` has all roles by default |
+| `VersionModule` | `version()` — returns the CMTAT version string |
+
+`CMTATBaseCore` also defines the following multi-module operations directly:
+
+| Function | Role | Description |
+|---|---|---|
+| `forcedBurn(address, uint256, bytes)` | `DEFAULT_ADMIN_ROLE` | Burns tokens from a frozen (blacklisted) address. The account must be frozen first via `setAddressFrozen`. Emits `ForcedTransfer(operator, account, address(0), value, data)`. |
+| `burnAndMint(address, address, uint256, uint256, bytes)` | `BURNER_ROLE` + `MINTER_ROLE` | Atomically burns from one address and mints to another in a single transaction. |
+
+### Role Summary
+
+| Role | Key operations |
+|---|---|
+| `DEFAULT_ADMIN_ROLE` | Grant/revoke all roles; `deactivateContract`; `forcedBurn`; `setName`; `setSymbol` |
+| `MINTER_ROLE` | `mint`, `batchMint`, `batchTransfer` |
+| `BURNER_ROLE` | `burn`, `batchBurn` |
+| `PAUSER_ROLE` | `pause`, `unpause` |
+| `ENFORCER_ROLE` | `setAddressFrozen`, `batchSetAddressFrozen` |
+
+### What Light Does NOT Include
+
+The following features are intentionally absent to keep the contract lean:
+
+| Feature | Missing module | Notes |
+|---|---|---|
+| Partial balance freeze | `ERC20EnforcementModule` | No `freezePartialTokens` / `unfreezePartialTokens` / `setFrozenTokens`. Address-level freeze only. |
+| Forced transfer to third party | `ERC20EnforcementModule` | Only `forcedBurn` is available. Cannot move tokens from a frozen account to another address. |
+| On-chain token metadata | `ExtraInformationModule` | No `tokenId`, `terms`, `information` fields. |
+| On-chain document management | `DocumentERC1643Module` | No ERC-1643 document storage. |
+| Snapshot engine support | `SnapshotEngineModule` | No `setSnapshotEngine` / `operateOnTransfer` hook. |
+| Gasless meta-transactions | `ERC2771Module` | No trusted forwarder / ERC-2771 support. |
+| Signature-based approvals | `ERC20PermitUpgradeable` | No ERC-2612 `permit`. |
+| Batch operations in one call | `MulticallUpgradeable` | No ERC-6357 `multicall`. |
+| Transfer rule engine | `ValidationModuleRuleEngine` | No external RuleEngine. Transfer restrictions are pause + address freeze only. |
+| ERC-1404 restricted transfer | `ValidationModuleERC1404` | No `detectTransferRestriction` / `messageForTransferRestriction`. |
+| Cross-chain mint/burn | `ERC20CrossChainModule` | No ERC-7802, no CCIP. |
+| ERC-1363 token callbacks | — | No `transferAndCall` / `approveAndCall`. |
+
+### Transfer Restriction Logic
+
+Every token movement is validated by `ValidationModule._canTransferGenericByModuleAndRevert`. The checks applied are:
+
+1. Contract must not be paused (`PauseModule.paused()` is false).
+2. Sender / spender must not be frozen (`EnforcementModule.isFrozen(account)` is false).
+3. Recipient must not be frozen.
+
+No external RuleEngine is consulted. There is no allowlist module in Light.
+
+### Blacklisting Pattern
+
+The standard stablecoin blacklisting pattern on Light:
+
+1. Call `setAddressFrozen(blacklistedAddress, true)` with `ENFORCER_ROLE`.
+2. The address is now blocked from sending and receiving tokens.
+3. To recover tokens held by the blacklisted address, call `forcedBurn(blacklistedAddress, amount, data)` with `DEFAULT_ADMIN_ROLE` (the account must be frozen before `forcedBurn` can be called).
+
+If you need to *move* the tokens to another address instead of burning them, Light cannot do this — use the Standard variant, which provides `forcedTransfer`.
+
+### Deactivation
+
+`deactivateContract()` (requires `DEFAULT_ADMIN_ROLE`, contract must be paused first) permanently disables the contract. After deactivation:
+- All state-changing operations revert.
+- Read functions still work.
+- The state is irreversible.
+
+---
+
+## When to Use the Standard Variant Instead
+
+Use **CMTAT Standard** (`CMTATStandardStandalone` / `CMTATStandardUpgradeable`) when any of the following is required:
+
+### Forced Transfer to Another Address
+
+Standard adds `ERC20EnforcementModule`, which provides:
+
+```solidity
+function forcedTransfer(address from, address to, uint256 value) external; // DEFAULT_ADMIN_ROLE
+```
+
+This moves tokens from any address to any other address without the sender's consent. Unlike `forcedBurn`, the source address does not need to be frozen, and the tokens are not destroyed — they are transferred. Frozen tokens are automatically unfrozen as needed to cover the amount.
+
+Use this for regulatory seizure scenarios where tokens must be redirected rather than destroyed.
+
+### Partial Balance Freeze
+
+Standard also adds partial-freeze functions via `ERC20EnforcementModule`:
+
+```solidity
+function freezePartialTokens(address account, uint256 value) external; // ERC20ENFORCER_ROLE
+function unfreezePartialTokens(address account, uint256 value) external; // ERC20ENFORCER_ROLE
+function setFrozenTokens(address account, uint256 value) external; // ERC20ENFORCER_ROLE
+function getFrozenTokens(address account) external view returns (uint256);
+```
+
+These lock a specific token amount on an address while leaving the rest transferable, as opposed to the full freeze in Light that blocks all transfers.
+
+### On-Chain Document Attachment
+
+Standard includes `DocumentERC1643Module` (via `CMTATBaseDocument` → `CMTATBaseAccessControl`), which stores regulatory documents directly in the token contract:
+
+```solidity
+function setDocument(bytes32 name, string calldata uri, bytes32 documentHash) external; // DOCUMENT_ROLE
+function removeDocument(bytes32 name) external; // DOCUMENT_ROLE
+function getDocument(bytes32 name) external view returns (Document memory);
+function getAllDocuments() external view returns (bytes32[] memory);
+```
+
+Useful when the regulator requires on-chain reference to a legal document (e.g., prospectus, terms of issuance).
+
+### On-Chain Token Metadata
+
+Standard includes `ExtraInformationModule`, which exposes:
+
+```solidity
+function setTokenId(string calldata tokenId_) external; // EXTRA_INFORMATION_ROLE
+function setTerms(IERC1643CMTAT.DocumentInfo calldata terms_) external; // EXTRA_INFORMATION_ROLE
+function setInformation(string calldata information_) external; // EXTRA_INFORMATION_ROLE
+```
+
+Useful for associating an ISIN or other identifier with the token, or for storing a reference to the tokenization terms.
+
+### Gasless Transactions (ERC-2771)
+
+Standard includes `ERC2771Module`, which allows a trusted forwarder to relay transactions on behalf of users. The forwarder address is set at construction time and is irrevocable.
+
+Use this if you want to sponsor gas fees for your users (e.g., the issuer pays gas for user redemptions).
+
+Note: the forwarder is fixed at deployment. Verify that the chosen forwarder is trustworthy before deploying, as it can submit arbitrary calls on behalf of any user.
+
+### External Transfer Rules (RuleEngine)
+
+Standard includes `ValidationModuleRuleEngine`, which allows plugging in an external `RuleEngine` contract:
+
+```solidity
+function setRuleEngine(IRuleEngine ruleEngine_) external; // DEFAULT_ADMIN_ROLE
+```
+
+Use this when transfer restrictions beyond pause/freeze are needed — for example, jurisdiction-based restrictions, velocity limits, or identity checks — without modifying the token contract.
+
+### Cross-Chain Transfers (ERC-7802 / Chainlink CCIP)
+
+Standard includes `ERC20CrossChainModule`, which provides:
+
+```solidity
+function crosschainMint(address to, uint256 value) external; // CROSS_CHAIN_ROLE
+function crosschainBurn(address from, uint256 value) external; // CROSS_CHAIN_ROLE
+```
+
+These functions implement the [ERC-7802](https://eips.ethereum.org/EIPS/eip-7802) interface, making CMTAT compatible with any bridge or messaging layer that expects the standardized `crosschainMint` / `crosschainBurn` entry points (such as Chainlink CCIP's Burn-and-Mint token pool, or Optimism's Superchain token standard). The `CCIPModule` additionally exposes `getCCIPAdmin()` for Chainlink CCIP registry compatibility.
+
+Note: this differs from USDC's approach. USDC cross-chain is handled by CCTP (Circle's own Cross-Chain Transfer Protocol), which reuses the standard `mint` / `burn` functions behind a privileged minter role — there are no dedicated `crosschainMint` / `crosschainBurn` entry points on the USDC contract, and USDC does not implement ERC-7802.
+
+---
+
+## When to Use the Permit Variant Instead
+
+Use **CMTAT Permit** (`CMTATStandalonePermit` / `CMTATUpgradeablePermit`) when ERC-2612 `permit` or ERC-6357 `multicall` are required. Permit shares the same module set as Standard (including ERC-7802 cross-chain) but replaces ERC-2771 with `permit` and `multicall` — see the trade-off note below.
+
+### ERC-2612 Permit — Gasless Approvals
+
+```solidity
+function permit(
+ address owner,
+ address spender,
+ uint256 value,
+ uint256 deadline,
+ uint8 v, bytes32 r, bytes32 s
+) external;
+```
+
+Allows a token owner to set an allowance using an off-chain signature instead of an on-chain `approve` transaction. The spender (or any relayer) can then submit the signature to the chain. This enables:
+
+- Gas-sponsored approval flows (spender pays gas, not the owner).
+- Single-step approve-and-use UX without a separate approve transaction.
+
+The same compliance checks that apply to `approve` also apply to `permit`: the contract must not be paused, and neither `owner` nor `spender` may be frozen.
+
+### ERC-6357 Multicall — Batched Operations
+
+```solidity
+function multicall(bytes[] calldata data) external returns (bytes[] memory results);
+```
+
+Executes multiple function calls on the token contract atomically in a single transaction. Useful for:
+
+- Minting to several addresses in one call when `batchMint` is insufficient.
+- Combining `permit` + `transferFrom` in a single transaction for a DeFi integration.
+- Performing administrative actions (pause + update metadata + batch freeze) atomically.
+
+### Trade-Off: No ERC-2771
+
+The Permit variant does **not** include `ERC2771Module`. The two features are mutually exclusive in the current deployment variants: Permit uses `MulticallUpgradeable` and `ERC20PermitUpgradeable`, which occupy contract size that ERC-2771 support would exceed.
+
+If you need both meta-transactions and permit-style approvals, consider an external gasless relayer architecture that calls `permit` directly.
+
+---
+
+## Variant Comparison
+
+| Feature | Light | Standard | Permit |
+|---|---|---|---|
+| `mint` / `batchMint` | ✓ | ✓ | ✓ |
+| `burn` / `batchBurn` | ✓ | ✓ | ✓ |
+| `batchTransfer` (minter) | ✓ | ✓ | ✓ |
+| `burnAndMint` | ✓ | ✓ | ✓ |
+| `pause` / `unpause` / `deactivateContract` | ✓ | ✓ | ✓ |
+| Address freeze (`setAddressFrozen`) | ✓ | ✓ | ✓ |
+| `canSend` / `canReceive` / `canTransfer` | ✓ | ✓ | ✓ |
+| `forcedBurn` (burn from frozen address) | ✓ | — | — |
+| `forcedTransfer` (move tokens to third party) | — | ✓ | ✓ |
+| Partial balance freeze (`freezePartialTokens`) | — | ✓ | ✓ |
+| `ExtraInformationModule` (tokenId, terms, info) | — | ✓ | ✓ |
+| `DocumentERC1643Module` (ERC-1643 documents) | — | ✓ | ✓ |
+| ERC-2771 meta-transactions (gasless) | — | ✓ | — |
+| ERC-7802 / CCIP cross-chain | — | ✓ | ✓ |
+| External RuleEngine | — | ✓ | ✓ |
+| ERC-1404 (`restrictedTransferOf`) | — | ✓ | ✓ |
+| ERC-2612 `permit` | — | — | ✓ |
+| ERC-6357 `multicall` | — | — | ✓ |
+| Deployed bytecode | 11.298 KiB | 22.243 KiB | 23.268 KiB |
+
+---
+
+## Decision Guide
+
+```
+Do you need on-chain documents, tokenId/terms,
+partial freeze, or forcedTransfer?
+ ├── No → Do you need permit or multicall?
+ │ ├── No → CMTAT Light
+ │ └── Yes → CMTAT Permit (no ERC-2771)
+ └── Yes → Do you need permit or multicall?
+ ├── No → Do you need gasless (ERC-2771)?
+ │ ├── Yes → CMTAT Standard
+ │ └── No → CMTAT Standard or Permit
+ └── Yes → CMTAT Permit (no ERC-2771)
+```
+
+---
+
+## Comparison with USDC and USDT
+
+The table below maps common stablecoin features to the relevant CMTAT variant. Sources: [USDC implementation (Ethereum)](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code), [USDT (Ethereum)](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7).
+
+### Standards
+
+| Feature | USDC | USDT | CMTAT Light | CMTAT Standard | CMTAT Permit |
+|---|---|---|---|---|---|
+| [ERC-20](https://eips.ethereum.org/EIPS/eip-20) | ✓ | ✓ | ✓ | ✓ | ✓ |
+| [ERC-2612 Permit](https://eips.ethereum.org/EIPS/eip-2612) | ✓ | — | — | — | ✓ |
+| [ERC-3009](https://eips.ethereum.org/EIPS/eip-3009) (Transfer With Authorization) | ✓ | — | — | — | — |
+| [ERC-2771](https://eips.ethereum.org/EIPS/eip-2771) (meta-transactions / gasless) | — | — | — | ✓ | — |
+| [ERC-7802](https://eips.ethereum.org/EIPS/eip-7802) (cross-chain) | — | — | — | ✓ | ✓ |
+
+### ERC-20 Operations
+
+| Feature | USDC | USDT | CMTAT Light | CMTAT Standard | CMTAT Permit |
+|---|---|---|---|---|---|
+| Mint to any address | Partial² | ✓ | ✓ | ✓ | ✓ |
+| Mint with dedicated allowance (`mintFrom`) | ✓ | — | — | — | — |
+| `batchMint` | — | — | ✓ | ✓ | ✓ |
+| `batchTransfer` (minter path) | — | — | ✓ | ✓ | ✓ |
+| Burn / redeem | ✓ | ✓ (`redeem`) | ✓ | ✓ | ✓ |
+| Set `name` after deployment | — | — | ✓ | ✓ | ✓ |
+| Set `symbol` after deployment | — | — | ✓ | ✓ | ✓ |
+
+² USDC minters hold a per-minter allowance (`minterAllowance`) that is decremented on each mint call, rather than minting directly to any address without a cap.
+
+### Compliance and Regulatory
+
+| Feature | USDC | USDT | CMTAT Light | CMTAT Standard | CMTAT Permit |
+|---|---|---|---|---|---|
+| Blacklist inside token contract | ✓ | ✓ | ✓ (`setAddressFrozen`) | ✓ | ✓ |
+| External / shared blacklist | — | — | — | ✓ (via RuleEngine) | ✓ (via RuleEngine) |
+| Forced transfer to a third party | ✓ (`instantTransfer`) | ✓ (`size`) | — | ✓ | ✓ |
+| Forced burn from blacklisted address | — | ✓ (`destroyBlackFunds`) | ✓ (`forcedBurn`) | — | — |
+| Partial balance freeze | — | — | — | ✓ | ✓ |
+| Pause all transfers | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Deactivation (permanent, irreversible) | — | — | ✓ | ✓ | ✓ |
+| Fee on transfer | — | ✓ (set at 0) | — | — | — |
+| Restriction on `transferFrom` spender | ✓³ | — | ✓ (frozen spender) | ✓ (frozen spender; or full ban via RuleEngine) | ✓ (frozen spender; or full ban via RuleEngine) |
+| External rule engine (custom policies) | — | — | — | ✓ | ✓ |
+
+³ USDC allows the contract owner to disable third-party (`transferFrom`) transfers entirely via `disableERC20ThirdPartyTransfer` / `enableERC20ThirdPartyTransfer`. All CMTAT variants block `transferFrom` when the spender is frozen. Standard and Permit can additionally enforce a complete third-party transfer ban by deploying a RuleEngine rule that rejects calls where `spender != from && spender != address(0) && from != address(0) && to != address(0)` (i.e., a genuine delegated transfer, excluding mints and burns).
+
+### Access Control
+
+| Feature | USDC | USDT | CMTAT Light | CMTAT Standard | CMTAT Permit |
+|---|---|---|---|---|---|
+| Single-owner model | ✓ | ✓ | — | — | — |
+| Role-based access control (RBAC) | ✓ (Minter + Blacklister roles) | — | ✓ (5 roles) | ✓ (10+ roles) | ✓ (10+ roles) |
+| Granular per-function roles | Partial | — | ✓ | ✓ | ✓ |
+
+CMTAT replaces the single-owner pattern with `DEFAULT_ADMIN_ROLE`, which holds all roles by default. Each operational permission (mint, burn, pause, freeze, …) can be delegated to a separate address independently.
+
+### Upgradeability
+
+| Feature | USDC | USDT | CMTAT Light | CMTAT Standard | CMTAT Permit |
+|---|---|---|---|---|---|
+| Standalone (immutable) | — | ✓ | ✓ | ✓ | ✓ |
+| Upgradeable — Transparent / Beacon proxy | ✓ | — | ✓ | ✓ | ✓ |
+| Upgradeable — UUPS | — | — | — | ✓ (dedicated variant) | ✓ (dedicated variant) |
+| Migrate function (balance carry-over) | — | ✓⁴ | — | — | — |
+
+⁴ USDT includes a `migrate` function because it predates the proxy upgrade pattern and needed a manual balance migration path. CMTAT upgradeable variants use ERC-7201 namespaced storage, which makes migrations transparent.
+
+### Key Differences Versus USDC
+
+**Features USDC has that CMTAT Light lacks:**
+- **ERC-2612 Permit** — available via CMTAT Permit variant.
+- **ERC-3009 Transfer With Authorization** — not implemented in any CMTAT variant.
+- **Mint allowance per minter** (`minterAllowance`) — CMTAT uses an uncapped `MINTER_ROLE` instead. A per-minter cap could be implemented via a custom RuleEngine or a minter proxy.
+- **Forced transfer** (`instantTransfer`) — available in CMTAT Standard and Permit via `forcedTransfer`.
+
+**Features CMTAT Light has that USDC lacks:**
+- **`forcedBurn`** — burns tokens directly from a frozen (blacklisted) address in a single call. USDC has no equivalent; CMTAT Standard and Permit also lack this function (they provide `forcedTransfer` to move tokens instead).
+- **`batchMint` / `batchTransfer`** — batch operations not available in USDC.
+- **`setName` / `setSymbol`** — USDC name and symbol are fixed at deployment.
+- **Deactivation** — CMTAT provides an irreversible shutdown mechanism; USDC does not.
+- **RBAC granularity** — CMTAT separates minting, burning, pausing, and freezing into independent roles; USDC has Minter and Blacklister but the owner holds broader control.
+
+**Additional CMTAT features (Standard / Permit) that USDC also lacks:**
+- **ERC-7802 cross-chain interface** — USDC does not implement ERC-7802. USDC cross-chain transfers use CCTP (Circle's own Cross-Chain Transfer Protocol), which reuses the existing `mint` / `burn` functions behind a privileged minter role (the `TokenMinter` contract). There are no dedicated `crosschainMint` / `crosschainBurn` entry points on the USDC contract. CMTAT Standard and Permit expose these as proper ERC-7802 functions callable by `CROSS_CHAIN_ROLE`.
+- **ERC-2771 meta-transactions** — available in CMTAT Standard (not in Permit).
+- **External RuleEngine** — available in CMTAT Standard and Permit for custom per-transfer compliance logic.
+
+### Key Differences Versus USDT
+
+**Features USDT has that CMTAT lacks (across all variants):**
+- **Fee on transfer** — USDT has a configurable fee (currently 0). CMTAT has no fee mechanism.
+
+**Features USDT has that CMTAT Light lacks:**
+- **Forced transfer** (`size`) — available in CMTAT Standard and Permit via `forcedTransfer`.
+
+**Functional equivalences:**
+- **`destroyBlackFunds`** — USDT can burn from a blacklisted address without explicit role separation. CMTAT Light provides equivalent functionality via `forcedBurn` (`DEFAULT_ADMIN_ROLE`; the account must be frozen first via `setAddressFrozen`).
+- **Migrate function** — USDT includes a manual balance migration function from a previous contract. CMTAT upgradeable variants use ERC-7201 namespaced storage, making storage layout compatible across upgrades without a manual migration step.
+
+**Features CMTAT Light has that USDT lacks:**
+- **RBAC** — USDT uses a single-owner model; CMTAT provides granular role separation.
+- **Transparent proxy upgradeability** — USDT is immutable; CMTAT upgradeable variants support Transparent, Beacon, and UUPS proxies.
+- **`batchMint` / `batchBurn`** — batch operations not available in USDT.
+- **`setName` / `setSymbol`** — USDT name and symbol are fixed at deployment.
+- **Deactivation** — irreversible shutdown mechanism not present in USDT.
+
+**Additional CMTAT features (Standard / Permit) that USDT also lacks:**
+- **ERC-2612 Permit** — available via CMTAT Permit variant.
+- **ERC-2771 meta-transactions** — available in CMTAT Standard.
+- **External RuleEngine** — available in CMTAT Standard and Permit for configurable transfer policies.
+
+---
+
+## See Also
+
+- [`doc/technical/deployment.md`](./deployment.md) — full deployment model reference
+- [`doc/technical/access-control.md`](./access-control.md) — role list and function-to-role table
+- [`doc/technical/permit-multicall.md`](./permit-multicall.md) — ERC-2612 and ERC-6357 details
+- [`doc/modules/base/0_CMTATBaseCore.md`](../modules/base/0_CMTATBaseCore.md) — CMTATBaseCore module reference
diff --git a/test/common/DocumentModule/DocumentModuleCommon.js b/test/common/DocumentModule/DocumentModuleCommon.js
index 24317a33..3e50204e 100644
--- a/test/common/DocumentModule/DocumentModuleCommon.js
+++ b/test/common/DocumentModule/DocumentModuleCommon.js
@@ -1,10 +1,11 @@
const { expect } = require('chai')
-const { ZERO_ADDRESS, DOCUMENT_ROLE } = require('../../utils')
+const { ZERO_ADDRESS, DOCUMENT_ROLE, DOCUMENT_ENGINE_ROLE } = require('../../utils')
function DocumentModuleCommon () {
context('Document Module Test', function () {
beforeEach(async function () {
const hasDocumentEngine = this.cmtat.interface.hasFunction('documentEngine()')
+ this.documentRole = hasDocumentEngine ? DOCUMENT_ENGINE_ROLE : DOCUMENT_ROLE
if (hasDocumentEngine && !this.definedAtDeployment) {
this.documentEngineMock = await ethers.deployContract(
'DocumentEngineMock'
@@ -50,7 +51,7 @@ function DocumentModuleCommon () {
this.cmtat,
'AccessControlUnauthorizedAccount'
)
- .withArgs(this.address1.address, DOCUMENT_ROLE)
+ .withArgs(this.address1.address, this.documentRole)
})
it('testCanUpdateADocument', async function () {
@@ -105,7 +106,7 @@ function DocumentModuleCommon () {
this.cmtat,
'AccessControlUnauthorizedAccount'
)
- .withArgs(this.address1.address, DOCUMENT_ROLE)
+ .withArgs(this.address1.address, this.documentRole)
})
it('testCanReturnAllDocumentNames', async function () {
diff --git a/test/common/DocumentModule/DocumentModuleSetDocumentEngineCommon.js b/test/common/DocumentModule/DocumentModuleSetDocumentEngineCommon.js
index 1edbacc8..c0d36592 100644
--- a/test/common/DocumentModule/DocumentModuleSetDocumentEngineCommon.js
+++ b/test/common/DocumentModule/DocumentModuleSetDocumentEngineCommon.js
@@ -1,5 +1,5 @@
const { expect } = require('chai')
-const { DOCUMENT_ROLE, ZERO_ADDRESS } = require('../../utils.js')
+const { DOCUMENT_ENGINE_ROLE, ZERO_ADDRESS } = require('../../utils.js')
const { ethers, upgrades } = require('hardhat')
function DocumentModuleSetDocumentEngineCommon () {
@@ -129,7 +129,7 @@ function DocumentModuleSetDocumentEngineCommon () {
this.cmtat,
'AccessControlUnauthorizedAccount'
)
- .withArgs(this.address1.address, DOCUMENT_ROLE)
+ .withArgs(this.address1.address, DOCUMENT_ENGINE_ROLE)
})
it('testGetEmptyDocumentsIfNoDocumentEngine', async function () {
diff --git a/test/utils.js b/test/utils.js
index fc831f4e..93489d3b 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -23,6 +23,8 @@ module.exports = {
'0x0000000000000000000000000000000000000000000000000000000000000000',
DOCUMENT_ROLE:
'0xdd7c9aafbb91d54fb2041db1d5b172ea665309b32f5fffdbddf452802a1e3b20',
+ DOCUMENT_ENGINE_ROLE:
+ '0x2d6f031e2eef5fafe7386c46356b86b5ed6513f04e6bcb34d4e0ff161e332117', // keccak256("DOCUMENT_ENGINE_ROLE")
CROSS_CHAIN_ROLE:
'0x620d362b92b6ef580d4e86c5675d679fe08d31dff47b72f281959a4eecdd036a',
PROXY_UPGRADE_ROLE: