diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1919e379..593ab46a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -13,10 +13,10 @@ jobs:
steps:
- uses: actions/checkout@v3
- - name: Setup NodeJS 16
+ - name: Setup NodeJS 20.5.0
uses: actions/setup-node@v3
with:
- node-version: 16.17.0
+ node-version: 20.5.0
- name: Show NodeJS version
run: npm --version
diff --git a/.gitignore b/.gitignore
index 4ec8db2f..5ed6cc2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@ bin/*
.vscode/
#PlantUML
out
+docOut
#drawio
*.bkp
*.dtmp
@@ -17,4 +18,4 @@ cache
#manticore
mcore_*
#secrets
-.env
\ No newline at end of file
+.env
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 803535f3..55abf03d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,83 @@
Please follow conventions.
+## 2.4.0
+
+The modifications between the version v2.3.0 and this version are not audited !!!
+
+- Improve tests & update the code
+- `ERC20SnapshotInternal` inherits from `ICMTATSnapshot`
+
+
+
+## 2.4.0-rc.1 - 20240319
+
+The modifications between the version v2.3.0 and this version are not audited !!!
+
+**snapshotModule**
+
+- Create an interface `ICMTATSnapshot` with the main public functions for the SnapshotModule to make easier the calls to a contract including a snapshotModule, useful e.g. for debt payment.
+- Replace `getSnapshotInfoBatch` by `SnapshotInfo`. This function gets a user's balance specified in parameter and the total supply.
+- Add a new function `SnapshotInfoBatch` to get several user's balances and the total supply.
+
+**ERC20BaseModule**
+Add a function `balanceInfo` to get the balance for a list of addresses and the total supply
+ Useful to perform transfer restriction based on the user's balance (e.g vesting rule or partial lock).
+
+**ValidationModule**
+Create an internal function ` _validateTransferByModule` which performs check with others module (PauseModule & EnforcementModule)
+
+**Other**
+
+- Upgrade OpenZeppelin to the version [v5.0.2](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.0.2)
+- Upgrade Solidity to the version [0.8.22](https://soliditylang.org/blog/2023/10/25/solidity-0.8.22-release-announcement/) in the truffle and hardhat config files.
+
+## 2.4.0-rc.0 - 20240129
+
The modifications between the version v2.3.0 and this version are not audited !!!
+**New architecture for the RuleEngine** [#250](https://github.com/CMTA/CMTAT/pull/250)
+
+- A new function `operateOnTransfer` is added and use inside the ValidationModule.
+- Contrary to `validateTransfer`, this function has to be protected by an access control (if not implemented as view or pure)
+- This function can be used to perform operation which modifies the state of the blockchain (storage) by the RuleEngine.
+- The RuleEngine inherits now from *IRuleEngine* wich contains in its interface the function `operateOnTransfer` + IERC-1404
+- The function `validateTransfer` is still available to verify a transfer without performing operation. The behavior is the same than with the previous CMTAT version.
+
+**snapshotModule** [#256](https://github.com/CMTA/CMTAT/pull/256)
+
+- Split the snapshotModuleInternal in two parts : one with the inheritance with ERC-20 and the other part with the base function and does not inherit from ERC-20.
+ Thus, if we want to build a snapshotModule with the RuleEngine, we can use the base contract to avoid the inheritance with ERC-20.
+- Add a function `getSnapshotInfoBatch` to avoid multiple calls when computing debt payment
+
+**AuthorizationEngine** [#254](https://github.com/CMTA/CMTAT/pull/254)
+
+- Add the AuthorizationEngine. With that, it is possible to add supplementary check on the functions `grantRole` and `revokeRole`without modifying the CMTAT.
+
+**BurnModule**
+
+- rename `forceBurn` and `forceBurnBatch` in `burn` and `burnBatch`
+- Add a function `burnFrom` with a specific role (useful for bridge) for compatibility with CCIP [Ccip #260](https://github.com/CMTA/CMTAT/pull/260)
+- Add a function `burnAndMint` to perform a burn/mint operation atomically.
+
+**Gas optimization**
+
+- Add factory contract for deployment with Transparent and beacon proxy [Contract factory #259](https://github.com/CMTA/CMTAT/pull/259)
+- Remove useless init function in internal modules (Done) [remove init functions in wrapper modules #237](https://github.com/CMTA/CMTAT/pull/237)
+
+**Other**
+
+- Remove custom approval function [Remove custom function allowance #225](https://github.com/CMTA/CMTAT/issues/225) (Done)
+- upgrade some JS libraries
+- Upgrade OpenZeppelin to the version [v5.0.1](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.0.1)
+
+## 2.3.1
+
This version contains breaking changes with the version v2.3.0.
- Remove useless functions init in wrapper modules [#230](https://github.com/CMTA/CMTAT/issues/230)
- Add missing tests in EnforcementModule [#239](https://github.com/CMTA/CMTAT/issues/239)
-- Use calldate instead of memory [#224](https://github.com/CMTA/CMTAT/issues/224)
+- Use calldata instead of memory [#224](https://github.com/CMTA/CMTAT/issues/224)
- Upgrade OpenZeppelin to the version [v.5.0.0](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.0.0)
## 2.3.1-rc.0 - 20230925
diff --git a/FAQ.md b/FAQ.md
index 78a1134f..0bc33b66 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -100,5 +100,5 @@ Normally, you can run the test suite and generate a code coverage report with `n
Please clone the repository and open the file inside your browser.
-You will find a summary of all automatic tests in
+You will find a list of automatic tests in
[test.pdf](https://github.com/CMTA/CMTAT/blob/master/doc/general/test/test.pdf).
diff --git a/README.md b/README.md
index 08b39597..c1ae38e1 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,24 @@ Please see the OpenZeppelin [Upgrades plugins](https://docs.openzeppelin.com/upg
Note that deployment via a proxy is not mandatory, but is recommended by CMTA.
+
+#### Factory
+
+Factory contracts are available to deploy the CMTAT with a beacon proxy or a transparent proxy.
+
+[CMTAT_BEACON_FACTORY.sol](./contracts/deployment/CMTAT_BEACON_FACTORY.sol)
+
+[CMTAT_TRANSPARENT_FACTORY.sol](./contracts/deployment/CMTAT_TRANSPARENT_FACTORY.sol)
+
+Beacon Proxy factory: the factory will use the same beacon for each beacon proxy. This beacon provides the address of the implementation contract, a CMTAT_PROXY contract. If you upgrade the beacon to point to a new implementation, it will change the implementation contract for all beacon proxy.
+
+![factory-Beacon Factory.drawio](./doc/schema/drawio/factory-BeaconFactory.drawio.png)
+
+Transparent Proxy factory: the factory will use the same implementation for each transparent proxy deployed. Each transparent proxy has its owned proxy admin, deployed inside the constructor of the transparent proxy. Each transparent proxy can upgrade their implementation to a new one independently and without impact on other proxies.
+
+![factory-Transparent Factory.drawio](./doc/schema/drawio/factory-TransparentFactory.drawio.png)
+
+
### Gasless support
The CMTAT supports client-side gasless transactions using the [Gas Station Network](https://docs.opengsn.org/#the-problem) (GSN) pattern, the main open standard for transfering fee payment to another account than that of the transaction issuer. The contract uses the OpenZeppelin contract `ERC2771ContextUpgradeable`, which allows a contract to get the original client with `_msgSender()` instead of the fee payer given by `msg.sender` while allowing upgrades on the main contract (see *Deployment via a proxy* above).
@@ -109,27 +127,56 @@ Generally, these modules are not required to be compliant with the CMTA specific
| Name | Documentation | Main File |
| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| MetaTxModule | [metatx.md](doc/modules/presentation/extensions/metatx.md) | [MetaTxModule.sol](./contracts/modules/wrapper/extensions/MetaTxModule.sol) |
-| SnapshotModule* | [snapshot.md](doc/modules/presentation/extensions/snapshot.md) | [SnapshotModule.sol](./contracts/modules/wrapper/extensions/SnapshotModule.sol) |
+| SnapshotModule | [snapshot.md](doc/modules/presentation/extensions/snapshot.md) | [SnapshotModule.sol](./contracts/modules/wrapper/extensions/SnapshotModule.sol) |
| creditEventModule | [creditEvents.md](doc/modules/presentation/extensions/Debt/creditEvents.md) | [CreditEventsModule.sol](./contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol) |
| DebtBaseModule | [debtBase.md](doc/modules/presentation/extensions/Debt/debtBase.md) | [DebtBaseModule.sol](./contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol) |
-*not imported by default
-
### Security
| Name | Documentation | Main File |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| AuthorizationModule | [authorization.md](./doc/modules/presentation/security/authorization.md) | [AuthorizationModule.sol](./contracts/modules/security/AuthorizationModule.sol) |
+## Engine
+
+### RuleEngine
+The `RuleEngine` is an external contract used to apply transfer restriction to the CMTAT through whitelisting, blacklisting,...
-### SnapshotModule
+This contract is defined in the `ValidationModule`.
-This module designed for future support of dividend and interest distribution, was not covered by the audit made by ABDK and it is no longer imported by default inside the CMTAT.
+An example of RuleEngine is also available on [Github](https://github.com/CMTA/RuleEngine).
-If you want to add this module, you have to uncomment the specific lines "SnapshotModule" in [CMTAT_BASE.sol](./contracts/modules/CMTAT_BASE.sol).
+Here is the list of the different version available for each CMTAT version.
-A CMTAT version inheriting from the SnapshotModule and used for **testing** purpose is available in [CMTATSnapshotStandaloneTest.sol](./contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol) and [CMTATSnapshotProxyTest.sol](./contracts/test/CMTATSnapshot/CMTATSnapshotProxyTest.sol).
+| Name | RuleEngine |
+| ----------------------- | ------------------------------------------------------------ |
+| CMTAT 2.4.0 (unaudited) | Still in development |
+| CMTAT 2.3.0 | [RuleEngine v1.0.2](https://github.com/CMTA/RuleEngine/releases/tag/v1.0.2) |
+| CMTAT 2.0 (unaudited) | [RuleEngine 1.0](https://github.com/CMTA/RuleEngine/releases/tag/1.0) (unaudited) |
+| CMTAT 1.0 | No ruleEngine available |
+
+This contract acts as a controller and can call different contract rule to apply rule on each transfer.
+
+A possible rule is a whitelist rule where only the address inside the whitelist can perform a transfer
+
+Since the version 2.4.0, the requirement to use a RuleEngine are the following:
+
+The `RuleEngine` has to import an implement the interface `IRuleEngine` which declares the function `operateOnTransfer`.
+
+This interface can be found in [./contracts/interfaces/engine/IRuleEngine.sol](./contracts/interfaces/engine/IRuleEngine.sol).
+
+Before each transfer, the CMTAT calls the function `operateOnTransfer` which is the entrypoint for the RuleEngine.
+
+### AuthorizationEngine
+
+The `AuthorizationEngine` is an external contract to add supplementary check on the functions `grantRole` and `revokeRole`from the CMTAT.
+
+This contract is managed in the `AuthorizationModule`.
+
+The `AuthorizationEngine` has to import an implement the interface `IAuthorizationEngine` which declares the functions `operateOnGrantRole` and `operateOnRevokeRole`
+
+This interface can be found in [./contracts/interfaces/engine/IAuthorizationEngine.sol](./contracts/interfaces/engine/IAuthorizationEngine.sol).
## Security
@@ -172,18 +219,16 @@ The report is available in [ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf](doc/audits/ABDK
You will find the report produced by [Slither](https://github.com/crytic/slither) in
-| Version | File |
-| ------- | ------------------------------------------------------------ |
-| v2.3.0 | [v2.3.0-slither-report.md](doc/audits/tools/v2.3.0-slither-report.md) |
-| v2.3.1 | [v2.3.1-slither-report.md](doc/audits/tools/v2.3.1-slither-report.md) |
+| Version | File |
+| ------------ | ------------------------------------------------------------ |
+| Last version | [slither-report.md](doc/audits/tools/slither-report.md) |
+| v2.3.0 | [v2.3.0-slither-report.md](doc/audits/tools/v2.3.0-slither-report.md) |
+| v2.3.1 | [v2.3.1-slither-report.md](doc/audits/tools/v2.3.1-slither-report.md) |
### Test
-- A summary of automatic tests is available in [test.pdf](doc/general/test/test.pdf).
-- A code coverage is available in [index.html](doc/general/test/coverage/index.html).
-
-> Note that we do not perform tests on the internal functions `init` of the different modules.
+A code coverage is available in [index.html](doc/general/test/coverage/index.html).
### Remarks
@@ -215,4 +260,4 @@ CMTA providers further documentation describing the CMTAT framework in a platfor
## Intellectual property
-The code is copyright (c) Capital Market and Technology Association, 2018-2023, and is released under [Mozilla Public License 2.0](./LICENSE.md).
+The code is copyright (c) Capital Market and Technology Association, 2018-2024, and is released under [Mozilla Public License 2.0](./LICENSE.md).
diff --git a/contracts/CMTAT_STANDALONE.sol b/contracts/CMTAT_STANDALONE.sol
index 29fbed43..dfe10c6d 100644
--- a/contracts/CMTAT_STANDALONE.sol
+++ b/contracts/CMTAT_STANDALONE.sol
@@ -9,6 +9,7 @@ contract CMTAT_STANDALONE is CMTAT_BASE {
* @notice Contract version for standalone deployment
* @param forwarderIrrevocable address of the forwarder, required for the gasless support
* @param admin address of the admin of contract (Access Control)
+ * @param authorizationEngineIrrevocable
* @param nameIrrevocable name of the token
* @param symbolIrrevocable name of the symbol
* @param decimalsIrrevocable number of decimals used to get its user representation, should be 0 to be compliant with the CMTAT specifications.
@@ -22,13 +23,13 @@ contract CMTAT_STANDALONE is CMTAT_BASE {
constructor(
address forwarderIrrevocable,
address admin,
- uint48 initialDelay,
+ IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
string memory tokenId_,
string memory terms_,
- IERC1404Wrapper ruleEngine_,
+ IRuleEngine ruleEngine_,
string memory information_,
uint256 flag_
) MetaTxModule(forwarderIrrevocable) {
@@ -36,7 +37,7 @@ contract CMTAT_STANDALONE is CMTAT_BASE {
// Warning : do not initialize the proxy
initialize(
admin,
- initialDelay,
+ authorizationEngineIrrevocable,
nameIrrevocable,
symbolIrrevocable,
decimalsIrrevocable,
diff --git a/contracts/deployment/CMTAT_BEACON_FACTORY.sol b/contracts/deployment/CMTAT_BEACON_FACTORY.sol
new file mode 100644
index 00000000..c8c60d13
--- /dev/null
+++ b/contracts/deployment/CMTAT_BEACON_FACTORY.sol
@@ -0,0 +1,102 @@
+//SPDX-License-Identifier: MPL-2.0
+pragma solidity ^0.8.20;
+
+import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
+
+import '@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol';
+import "../CMTAT_PROXY.sol";
+import "../modules/CMTAT_BASE.sol";
+import "../libraries/FactoryErrors.sol";
+import '@openzeppelin/contracts/access/AccessControl.sol';
+
+/**
+* @notice Factory to deploy beacon proxy
+*
+*/
+contract CMTAT_BEACON_FACTORY is AccessControl {
+ // Private
+ mapping(uint256 => address) private cmtats;
+ uint256 private cmtatCounterId;
+ // public
+ /// @dev Role to deploy CMTAT
+ bytes32 public constant CMTAT_DEPLOYER_ROLE = keccak256("CMTAT_DEPLOYER_ROLE");
+ UpgradeableBeacon public immutable beacon;
+ address[] public cmtatsList;
+ event CMTAT(address indexed CMTAT, uint256 id);
+
+ /**
+ * @param implementation_ contract implementation
+ * @param factoryAdmin admin
+ * @param beaconOwner owner
+ */
+ constructor(address implementation_, address factoryAdmin, address beaconOwner) {
+ if(factoryAdmin == address(0)){
+ revert FactoryErrors.CMTAT_Factory_AddressZeroNotAllowedForFactoryAdmin();
+ }
+ if(beaconOwner == address(0)){
+ revert FactoryErrors.CMTAT_Factory_AddressZeroNotAllowedForBeaconOwner();
+ }
+ if(implementation_ == address(0)){
+ revert FactoryErrors.CMTAT_Factory_AddressZeroNotAllowedForLogicContract();
+ }
+ beacon = new UpgradeableBeacon(implementation_, beaconOwner);
+ _grantRole(DEFAULT_ADMIN_ROLE, factoryAdmin);
+ _grantRole(CMTAT_DEPLOYER_ROLE, factoryAdmin);
+ }
+
+ /**
+ * @notice deploy CMTAT with a beacon proxy
+ *
+ */
+ function deployCMTAT(
+ // CMTAT function initialize
+ address admin,
+ IAuthorizationEngine authorizationEngineIrrevocable,
+ string memory nameIrrevocable,
+ string memory symbolIrrevocable,
+ uint8 decimalsIrrevocable,
+ string memory tokenId_,
+ string memory terms_,
+ IRuleEngine ruleEngine_,
+ string memory information_,
+ uint256 flag_
+ ) public onlyRole(CMTAT_DEPLOYER_ROLE) returns(BeaconProxy cmtat) {
+ cmtat = new BeaconProxy(
+ address(beacon),
+ abi.encodeWithSelector(
+ CMTAT_PROXY(address(0)).initialize.selector,
+ admin,
+ authorizationEngineIrrevocable,
+ nameIrrevocable,
+ symbolIrrevocable,
+ decimalsIrrevocable,
+ tokenId_,
+ terms_,
+ ruleEngine_,
+ information_,
+ flag_
+ )
+ );
+ cmtats[cmtatCounterId] = address(cmtat);
+ emit CMTAT(address(cmtat), cmtatCounterId);
+ cmtatCounterId++;
+ cmtatsList.push(address(cmtat));
+ return cmtat;
+ }
+
+ /**
+ * @notice get CMTAT proxy address
+ *
+ */
+ function getAddress(uint256 cmtatID_) external view returns (address) {
+ return cmtats[cmtatID_];
+ }
+
+ /**
+ * @notice get the implementation address from the beacon
+ * @return implementation address
+ */
+ function implementation() public view returns (address) {
+ return beacon.implementation();
+ }
+}
\ No newline at end of file
diff --git a/contracts/deployment/CMTAT_TP_FACTORY.sol b/contracts/deployment/CMTAT_TP_FACTORY.sol
new file mode 100644
index 00000000..785747c2
--- /dev/null
+++ b/contracts/deployment/CMTAT_TP_FACTORY.sol
@@ -0,0 +1,86 @@
+//SPDX-License-Identifier: MPL-2.0
+pragma solidity ^0.8.20;
+
+import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
+import "../CMTAT_PROXY.sol";
+import "../libraries/FactoryErrors.sol";
+import '@openzeppelin/contracts/access/AccessControl.sol';
+/**
+* @notice Factory to deploy transparent proxy
+*
+*/
+contract CMTAT_TP_FACTORY is AccessControl {
+ // Private
+ mapping(uint256 => address) private cmtats;
+ uint256 private cmtatID;
+ event CMTAT(address indexed CMTAT, uint256 id);
+ // Public
+ /// @dev Role to deploy CMTAT
+ bytes32 public constant CMTAT_DEPLOYER_ROLE = keccak256("CMTAT_DEPLOYER_ROLE");
+ address public immutable logic;
+ address[] public cmtatsList;
+
+
+
+ /**
+ * @param logic_ contract implementation
+ * @param factoryAdmin admin
+ */
+ constructor(address logic_, address factoryAdmin) {
+ if(logic_ == address(0)){
+ revert FactoryErrors.CMTAT_Factory_AddressZeroNotAllowedForLogicContract();
+ }
+ if(factoryAdmin == address(0)){
+ revert FactoryErrors.CMTAT_Factory_AddressZeroNotAllowedForFactoryAdmin();
+ }
+ logic = logic_;
+ _grantRole(DEFAULT_ADMIN_ROLE, factoryAdmin);
+ _grantRole(CMTAT_DEPLOYER_ROLE, factoryAdmin);
+ }
+
+ function deployCMTAT(
+ address proxyAdminOwner,
+ // CMTAT function initialize
+ address admin,
+ IAuthorizationEngine authorizationEngineIrrevocable,
+ string memory nameIrrevocable,
+ string memory symbolIrrevocable,
+ uint8 decimalsIrrevocable,
+ string memory tokenId_,
+ string memory terms_,
+ IRuleEngine ruleEngine_,
+ string memory information_,
+ uint256 flag_
+ ) public onlyRole(CMTAT_DEPLOYER_ROLE) returns(TransparentUpgradeableProxy cmtat) {
+ cmtat = new TransparentUpgradeableProxy(
+ logic,
+ proxyAdminOwner,
+ abi.encodeWithSelector(
+ CMTAT_PROXY(address(0)).initialize.selector,
+ admin,
+ authorizationEngineIrrevocable,
+ nameIrrevocable,
+ symbolIrrevocable,
+ decimalsIrrevocable,
+ tokenId_,
+ terms_,
+ ruleEngine_,
+ information_,
+ flag_
+ )
+ );
+ cmtats[cmtatID] = address(cmtat);
+ emit CMTAT(address(cmtat), cmtatID);
+ cmtatID++;
+ cmtatsList.push(address(cmtat));
+ return cmtat;
+ }
+
+ /**
+ * @notice get CMTAT proxy address
+ *
+ */
+ function getAddress(uint256 cmtatID_) external view returns (address) {
+ return cmtats[cmtatID_];
+ }
+}
\ No newline at end of file
diff --git a/contracts/interfaces/ICCIPToken.sol b/contracts/interfaces/ICCIPToken.sol
new file mode 100644
index 00000000..256b1c96
--- /dev/null
+++ b/contracts/interfaces/ICCIPToken.sol
@@ -0,0 +1,25 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+/**
+* @notice CCIP Pool with mint
+*/
+interface ICCIPMintERC20 {
+ /// @notice Mints new tokens for a given address.
+ /// @param account The address to mint the new tokens to.
+ /// @param value The number of tokens to be minted.
+ /// @dev this function increases the total supply.
+ function mint(address account, uint256 value) external;
+}
+
+/**
+* @notice CCIP Pool with burnFrom
+*/
+interface ICCIPBurnFromERC20 {
+ /// @notice Burns tokens from a given address..
+ /// @param account The address to burn tokens from.
+ /// @param value The number of tokens to be burned.
+ /// @dev this function decreases the total supply.
+ function burnFrom(address account, uint256 value) external;
+}
\ No newline at end of file
diff --git a/contracts/interfaces/ICMTATSnapshot.sol b/contracts/interfaces/ICMTATSnapshot.sol
new file mode 100644
index 00000000..4cbd4d38
--- /dev/null
+++ b/contracts/interfaces/ICMTATSnapshot.sol
@@ -0,0 +1,38 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+/**
+* @notice interface to represent a CMTAT with snapshot
+*/
+interface ICMTATSnapshot {
+ /**
+ * @notice Return the number of tokens owned by the given owner at the time when the snapshot with the given time was created.
+ * @return value stored in the snapshot, or the actual balance if no snapshot
+ */
+ function snapshotBalanceOf(uint256 time,address owner) external view returns (uint256);
+ /**
+ * @dev See {OpenZeppelin - ERC20Snapshot}
+ * Retrieves the total supply at the specified time.
+ * @return value stored in the snapshot, or the actual totalSupply if no snapshot
+ */
+ function snapshotTotalSupply(uint256 time) external view returns (uint256);
+ /**
+ * @notice Return snapshotBalanceOf and snapshotTotalSupply to avoid multiple calls
+ * @return ownerBalance , totalSupply - see snapshotBalanceOf and snapshotTotalSupply
+ */
+ function snapshotInfo(uint256 time, address owner) external view returns (uint256 ownerBalance, uint256 totalSupply);
+ /**
+ * @notice Return snapshotBalanceOf for each address in the array and the total supply
+ * @return ownerBalances array with the balance of each address, the total supply
+ */
+ function snapshotInfoBatch(uint256 time, address[] calldata addresses) external view returns (uint256[] memory ownerBalances, uint256 totalSupply);
+
+ /**
+ * @notice Return snapshotBalanceOf for each address in the array and the total supply
+ * @return ownerBalances array with the balance of each address, the total supply
+ */
+ function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses) external view returns (uint256[][] memory ownerBalances, uint256[] memory totalSupply);
+
+
+}
diff --git a/contracts/interfaces/IDebtGlobal.sol b/contracts/interfaces/IDebtGlobal.sol
index 4f0fb116..cb743d6d 100644
--- a/contracts/interfaces/IDebtGlobal.sol
+++ b/contracts/interfaces/IDebtGlobal.sol
@@ -1,7 +1,10 @@
//SPDX-License-Identifier: MPL-2.0
-pragma solidity ^0.8.0;
+pragma solidity ^0.8.20;
+/**
+* @notice interface to represent debt tokens
+*/
interface IDebtGlobal {
struct DebtBase {
uint256 interestRate;
diff --git a/contracts/interfaces/draft-IERC1404/draft-IERC1404.sol b/contracts/interfaces/draft-IERC1404/draft-IERC1404.sol
index 28313c82..960856dc 100644
--- a/contracts/interfaces/draft-IERC1404/draft-IERC1404.sol
+++ b/contracts/interfaces/draft-IERC1404/draft-IERC1404.sol
@@ -1,9 +1,9 @@
//SPDX-License-Identifier: MPL-2.0
-pragma solidity ^0.8.0;
+pragma solidity ^0.8.20;
/*
-@dev Contrary to the ERC-1404, this interface does not inherit from the ERC20 interface
+* @dev Contrary to the ERC-1404, this interface does not inherit from the ERC20 interface
*/
interface IERC1404 {
/**
diff --git a/contracts/interfaces/draft-IERC1404/draft-IERC1404EnumCode.sol b/contracts/interfaces/draft-IERC1404/draft-IERC1404EnumCode.sol
new file mode 100644
index 00000000..6f1e3a6f
--- /dev/null
+++ b/contracts/interfaces/draft-IERC1404/draft-IERC1404EnumCode.sol
@@ -0,0 +1,15 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+interface IERC1404EnumCode {
+ /*
+ * @dev leave the code 4-9 free/unused for further additions in your ruleEngine implementation
+ */
+ enum REJECTED_CODE_BASE {
+ TRANSFER_OK,
+ TRANSFER_REJECTED_PAUSED,
+ TRANSFER_REJECTED_FROM_FROZEN,
+ TRANSFER_REJECTED_TO_FROZEN
+ }
+}
diff --git a/contracts/interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol b/contracts/interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol
index 30ce9d78..571b6639 100644
--- a/contracts/interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol
+++ b/contracts/interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol
@@ -1,19 +1,11 @@
//SPDX-License-Identifier: MPL-2.0
-pragma solidity ^0.8.0;
+pragma solidity ^0.8.20;
import "./draft-IERC1404.sol";
+import "./draft-IERC1404EnumCode.sol";
-interface IERC1404Wrapper is IERC1404 {
- /*
- @dev leave the code 4-9 free/unused for further additions in your ruleEngine implementation
- */
- enum REJECTED_CODE_BASE {
- TRANSFER_OK,
- TRANSFER_REJECTED_PAUSED,
- TRANSFER_REJECTED_FROM_FROZEN,
- TRANSFER_REJECTED_TO_FROZEN
- }
+interface IERC1404Wrapper is IERC1404, IERC1404EnumCode {
/**
* @dev Returns true if the transfer is valid, and false otherwise.
diff --git a/contracts/interfaces/engine/IAuthorizationEngine.sol b/contracts/interfaces/engine/IAuthorizationEngine.sol
new file mode 100644
index 00000000..000f4e0c
--- /dev/null
+++ b/contracts/interfaces/engine/IAuthorizationEngine.sol
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+interface IAuthorizationEngine {
+ /**
+ * @dev Returns true if the operation is authorized, and false otherwise.
+ */
+ function operateOnGrantRole(
+ bytes32 role, address account
+ ) external returns (bool isValid);
+
+ /**
+ * @dev Returns true if the operation is authorized, and false otherwise.
+ */
+ function operateOnRevokeRole(
+ bytes32 role, address account
+ ) external returns (bool isValid);
+
+}
diff --git a/contracts/interfaces/engine/IRuleEngine.sol b/contracts/interfaces/engine/IRuleEngine.sol
new file mode 100644
index 00000000..bd2586a1
--- /dev/null
+++ b/contracts/interfaces/engine/IRuleEngine.sol
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+import "../draft-IERC1404/draft-IERC1404Wrapper.sol";
+
+interface IRuleEngine is IERC1404Wrapper {
+ /**
+ * @dev Returns true if the operation is a success, and false otherwise.
+ */
+ function operateOnTransfer(
+ address _from,
+ address _to,
+ uint256 _amount
+ ) external returns (bool isValid);
+
+}
diff --git a/contracts/libraries/Errors.sol b/contracts/libraries/Errors.sol
index cd7801a8..28840db9 100644
--- a/contracts/libraries/Errors.sol
+++ b/contracts/libraries/Errors.sol
@@ -58,6 +58,8 @@ library Errors {
// AuthorizationModule
error CMTAT_AuthorizationModule_AddressZeroNotAllowed();
+ error CMTAT_AuthorizationModule_InvalidAuthorization();
+ error CMTAT_AuthorizationModule_AuthorizationEngineAlreadySet();
// PauseModule
error CMTAT_PauseModule_ContractIsDeactivated();
diff --git a/contracts/libraries/FactoryErrors.sol b/contracts/libraries/FactoryErrors.sol
new file mode 100644
index 00000000..a64e226b
--- /dev/null
+++ b/contracts/libraries/FactoryErrors.sol
@@ -0,0 +1,9 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+library FactoryErrors {
+ error CMTAT_Factory_AddressZeroNotAllowedForFactoryAdmin();
+ error CMTAT_Factory_AddressZeroNotAllowedForBeaconOwner();
+ error CMTAT_Factory_AddressZeroNotAllowedForLogicContract();
+}
diff --git a/contracts/mocks/AuthorizationEngineMock.sol b/contracts/mocks/AuthorizationEngineMock.sol
new file mode 100644
index 00000000..fbe72d6f
--- /dev/null
+++ b/contracts/mocks/AuthorizationEngineMock.sol
@@ -0,0 +1,60 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+import "../interfaces/engine/IAuthorizationEngine.sol";
+
+/*
+* @title a mock for testing, not suitable for production
+*/
+contract AuthorizationEngineMock is IAuthorizationEngine {
+ address nextAdmin;
+ bool revokeAdminRoleAuthorized;
+ constructor() {
+ revokeAdminRoleAuthorized = true;
+ }
+
+ /*
+ * @dev
+ * Warning: if you want to use this mock, you have to restrict the access to this function through an an access control
+ */
+ function authorizeAdminChange(address newAdmin) external {
+ nextAdmin = newAdmin;
+ }
+
+ function setRevokeAdminRoleAuthorized(bool
+ newValue) external {
+ revokeAdminRoleAuthorized = newValue;
+ }
+
+ /*
+ * @dev
+ * Warning: if you want to use this mock, you have to restrict the access to this function through an an access control
+ */
+ function operateOnGrantRole(
+ bytes32 role, address account
+ ) external returns (bool isValid){
+ if(role == 0x0 && account == nextAdmin && account != address(0x0)){
+ // Reset to 0
+ nextAdmin = address(0x0);
+ return true;
+ }else{
+ return false;
+ }
+ }
+
+ /*
+ * @dev
+ * Warning: if you want to use this mock, you have to restrict the access to this function through an an access control
+ */
+ function operateOnRevokeRole(
+ bytes32 role, address /*account*/
+ ) external view returns (bool isValid){
+ if(role == 0x0){
+ return revokeAdminRoleAuthorized;
+ } else{
+ // the tests will fail if this branch is taken
+ return !revokeAdminRoleAuthorized;
+ }
+ }
+}
diff --git a/contracts/mocks/RuleEngine/RuleEngineMock.sol b/contracts/mocks/RuleEngine/RuleEngineMock.sol
index 46473108..4dc0a065 100644
--- a/contracts/mocks/RuleEngine/RuleEngineMock.sol
+++ b/contracts/mocks/RuleEngine/RuleEngineMock.sol
@@ -3,14 +3,13 @@
pragma solidity ^0.8.20;
import "./interfaces/IRule.sol";
-import "./interfaces/IRuleEngine.sol";
+import "./interfaces/IRuleEngineMock.sol";
import "./RuleMock.sol";
-import "./CodeList.sol";
/*
-@title a mock for testing, not suitable for production
+* @title a mock for testing, not suitable for production
*/
-contract RuleEngineMock is IRuleEngine {
+contract RuleEngineMock is IRuleEngineMock {
IRule[] internal _rules;
constructor() {
@@ -18,8 +17,8 @@ contract RuleEngineMock is IRuleEngine {
}
/*
- @dev
- Warning: if you want to use this mock, you have to restrict the access to this function through an an access control
+ * @dev
+ * Warning: if you want to use this mock, you have to restrict the access to this function through an an access control
*/
function setRules(IRule[] calldata rules_) external override {
_rules = rules_;
@@ -67,9 +66,19 @@ contract RuleEngineMock is IRuleEngine {
return detectTransferRestriction(_from, _to, _amount) == 0;
}
+ /*
+ * @dev
+ * Warning: if you want to use this mock, you have to restrict the access to this function through an an access control
+ */
+ function operateOnTransfer( address _from,
+ address _to,
+ uint256 _amount) view public override returns (bool){
+ return validateTransfer(_from, _to, _amount);
+ }
+
/**
- @dev
- For all the rules, each restriction code has to be unique.
+ * @dev
+ * For all the rules, each restriction code has to be unique.
*/
function messageForTransferRestriction(
uint8 _restrictionCode
diff --git a/contracts/mocks/RuleEngine/RuleMock.sol b/contracts/mocks/RuleEngine/RuleMock.sol
index 2c9337ea..1bf71277 100644
--- a/contracts/mocks/RuleEngine/RuleMock.sol
+++ b/contracts/mocks/RuleEngine/RuleMock.sol
@@ -6,7 +6,7 @@ import "./interfaces/IRule.sol";
import "./CodeList.sol";
/*
-@title a mock for testing, not suitable for production
+* @title a mock for testing, not suitable for production
*/
contract RuleMock is IRule, CodeList {
function validateTransfer(
@@ -18,7 +18,7 @@ contract RuleMock is IRule, CodeList {
}
/**
- @dev 20 the limit of the maximum amount
+ * @dev 20 the limit of the maximum amount
*/
function detectTransferRestriction(
address /* _from */,
diff --git a/contracts/mocks/RuleEngine/interfaces/IRule.sol b/contracts/mocks/RuleEngine/interfaces/IRule.sol
index fdb52276..752ea2d3 100644
--- a/contracts/mocks/RuleEngine/interfaces/IRule.sol
+++ b/contracts/mocks/RuleEngine/interfaces/IRule.sol
@@ -1,6 +1,6 @@
//SPDX-License-Identifier: MPL-2.0
-pragma solidity ^0.8.0;
+pragma solidity ^0.8.20;
import "../../../interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol";
diff --git a/contracts/mocks/RuleEngine/interfaces/IRuleEngine.sol b/contracts/mocks/RuleEngine/interfaces/IRuleEngineMock.sol
similarity index 80%
rename from contracts/mocks/RuleEngine/interfaces/IRuleEngine.sol
rename to contracts/mocks/RuleEngine/interfaces/IRuleEngineMock.sol
index 44720657..694125c8 100644
--- a/contracts/mocks/RuleEngine/interfaces/IRuleEngine.sol
+++ b/contracts/mocks/RuleEngine/interfaces/IRuleEngineMock.sol
@@ -1,11 +1,11 @@
//SPDX-License-Identifier: MPL-2.0
-pragma solidity ^0.8.0;
+pragma solidity ^0.8.20;
import "./IRule.sol";
-import "../../../interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol";
+import "../../../interfaces/engine/IRuleEngine.sol";
-interface IRuleEngine is IERC1404Wrapper {
+interface IRuleEngineMock is IRuleEngine {
/**
* @dev define the rules, the precedent rules will be overwritten
*/
diff --git a/contracts/modules/CMTAT_BASE.sol b/contracts/modules/CMTAT_BASE.sol
index bc0c284a..57bfc1bd 100644
--- a/contracts/modules/CMTAT_BASE.sol
+++ b/contracts/modules/CMTAT_BASE.sol
@@ -13,10 +13,11 @@ import "./wrapper/core/EnforcementModule.sol";
import "./wrapper/core/ERC20BaseModule.sol";
import "./wrapper/core/PauseModule.sol";
/*
-SnapshotModule:
-Add this import in case you add the SnapshotModule
-import "./wrapper/optional/SnapshotModule.sol";
+* SnapshotModule:
+* Add this import in case you add the SnapshotModule
*/
+import "./wrapper/extensions/ERC20SnapshotModule.sol";
+
import "./wrapper/controllers/ValidationModule.sol";
import "./wrapper/extensions/MetaTxModule.sol";
import "./wrapper/extensions/DebtModule/DebtBaseModule.sol";
@@ -36,7 +37,7 @@ abstract contract CMTAT_BASE is
ValidationModule,
MetaTxModule,
ERC20BaseModule,
- // ERC20SnapshotModule,
+ ERC20SnapshotModule,
DebtBaseModule,
CreditEventsModule
{
@@ -56,19 +57,19 @@ abstract contract CMTAT_BASE is
*/
function initialize(
address admin,
- uint48 initialDelayToAcceptAdminRole,
+ IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
string memory tokenId_,
string memory terms_,
- IERC1404Wrapper ruleEngine_,
- string memory information_,
+ IRuleEngine ruleEngine_,
+ string memory information_,
uint256 flag_
) public initializer {
__CMTAT_init(
admin,
- initialDelayToAcceptAdminRole,
+ authorizationEngineIrrevocable,
nameIrrevocable,
symbolIrrevocable,
decimalsIrrevocable,
@@ -85,13 +86,13 @@ abstract contract CMTAT_BASE is
*/
function __CMTAT_init(
address admin,
- uint48 initialDelayToAcceptAdminRole,
+ IAuthorizationEngine authorizationEngineIrrevocable,
string memory nameIrrevocable,
string memory symbolIrrevocable,
uint8 decimalsIrrevocable,
string memory tokenId_,
string memory terms_,
- IERC1404Wrapper ruleEngine_,
+ IRuleEngine ruleEngine_,
string memory information_,
uint256 flag_
) internal onlyInitializing {
@@ -103,21 +104,22 @@ abstract contract CMTAT_BASE is
__ERC165_init_unchained();
// AuthorizationModule inherits from AccessControlUpgradeable
__AccessControl_init_unchained();
- __AccessControlDefaultAdminRules_init_unchained(initialDelayToAcceptAdminRole, admin);
__Pausable_init_unchained();
/* Internal Modules */
__Enforcement_init_unchained();
/*
SnapshotModule:
- Add this call in case you add the SnapshotModule
+ Add these two calls in case you add the SnapshotModule
+ */
+ __SnapshotModuleBase_init_unchained();
__ERC20Snapshot_init_unchained();
- */
+
__Validation_init_unchained(ruleEngine_);
/* Wrapper */
// AuthorizationModule_init_unchained is called firstly due to inheritance
- __AuthorizationModule_init_unchained();
+ __AuthorizationModule_init_unchained(admin, authorizationEngineIrrevocable);
__ERC20BurnModule_init_unchained();
__ERC20MintModule_init_unchained();
// EnforcementModule_init_unchained is called before ValidationModule_init_unchained due to inheritance
@@ -130,8 +132,9 @@ abstract contract CMTAT_BASE is
/*
SnapshotModule:
Add this call in case you add the SnapshotModule
- __ERC20SnasphotModule_init_unchained();
*/
+ __ERC20SnasphotModule_init_unchained();
+
/* Other modules */
__DebtBaseModule_init_unchained();
@@ -172,40 +175,63 @@ abstract contract CMTAT_BASE is
return ERC20BaseModule.transferFrom(sender, recipient, amount);
}
+ /**
+ * @notice burn and mint atomically
+ * @param from current token holder to burn tokens
+ * @param to receiver to send the new minted tokens
+ * @param amountToBurn number of tokens to burn
+ * @param amountToMint number of tokens to mint
+ * @dev
+ * - The access control is managed by the functions burn (ERC20BurnModule) and mint (ERC20MintModule)
+ * - Input validation is also managed by the functions burn and mint
+ * - You can mint more tokens than burnt
+ */
+ function burnAndMint(address from, address to, uint256 amountToBurn, uint256 amountToMint, string calldata reason) public {
+ burn(from, amountToBurn, reason);
+ mint(to, amountToMint);
+ }
+
/**
* @dev
- * SnapshotModule:
- * - override SnapshotModuleInternal if you add the SnapshotModule
- * e.g. override(ERC20SnapshotModuleInternal, ERC20Upgradeable)
- * - remove the keyword view
+ *
*/
function _update(
address from,
address to,
uint256 amount
) internal override(ERC20Upgradeable) {
- if (!ValidationModule.validateTransfer(from, to, amount)) {
+ if (!ValidationModule._operateOnTransfer(from, to, amount)) {
revert Errors.CMTAT_InvalidTransfer(from, to, amount);
}
- ERC20Upgradeable._update(from, to, amount);
- // We call the SnapshotModule only if the transfer is valid
/*
SnapshotModule:
- Add this call in case you add the SnapshotModule
- ERC20SnapshotModuleInternal._update(from, to, amount);
+ Add this in case you add the SnapshotModule
+ We call the SnapshotModule only if the transfer is valid
*/
+ ERC20SnapshotModuleInternal._snapshotUpdate(from, to);
+ ERC20Upgradeable._update(from, to, amount);
}
+ /************* MetaTx Module *************/
/**
* @dev This surcharge is not necessary if you do not use the MetaTxModule
*/
function _msgSender()
internal
view
- override(MetaTxModule, ContextUpgradeable)
+ override(ERC2771ContextUpgradeable, ContextUpgradeable)
returns (address sender)
{
- return MetaTxModule._msgSender();
+ return ERC2771ContextUpgradeable._msgSender();
+ }
+
+ /**
+ * @dev This surcharge is not necessary if you do not use the MetaTxModule
+ */
+ function _contextSuffixLength() internal view
+ override(ERC2771ContextUpgradeable, ContextUpgradeable)
+ returns (uint256) {
+ return ERC2771ContextUpgradeable._contextSuffixLength();
}
/**
@@ -214,10 +240,10 @@ abstract contract CMTAT_BASE is
function _msgData()
internal
view
- override(MetaTxModule, ContextUpgradeable)
+ override(ERC2771ContextUpgradeable, ContextUpgradeable)
returns (bytes calldata)
{
- return MetaTxModule._msgData();
+ return ERC2771ContextUpgradeable._msgData();
}
uint256[50] private __gap;
diff --git a/contracts/modules/internal/ERC20SnapshotModuleInternal.sol b/contracts/modules/internal/ERC20SnapshotModuleInternal.sol
index c868a15a..24e9e713 100644
--- a/contracts/modules/internal/ERC20SnapshotModuleInternal.sol
+++ b/contracts/modules/internal/ERC20SnapshotModuleInternal.sol
@@ -2,15 +2,12 @@
pragma solidity ^0.8.20;
-import "../../../openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol";
-import "../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../../openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import {Arrays} from '@openzeppelin/contracts/utils/Arrays.sol';
-
-import "../../libraries/Errors.sol";
-
+import "./base/SnapshotModuleBase.sol";
+import "../../interfaces/ICMTATSnapshot.sol";
/**
- * @dev Snapshot module.
+ * @dev Snapshot module internal.
*
* Useful to take a snapshot of token holder balance and total supply at a specific time
* Inspired by Openzeppelin - ERC20Snapshot but use the time as Id instead of a counter.
@@ -18,269 +15,62 @@ import "../../libraries/Errors.sol";
because overriding this function can break the contract.
*/
-abstract contract ERC20SnapshotModuleInternal is ERC20Upgradeable {
+abstract contract ERC20SnapshotModuleInternal is ICMTATSnapshot, SnapshotModuleBase, ERC20Upgradeable {
using Arrays for uint256[];
- /**
- @notice Emitted when the snapshot with the specified oldTime was scheduled or rescheduled at the specified newTime.
- */
- event SnapshotSchedule(uint256 indexed oldTime, uint256 indexed newTime);
-
- /**
- @notice Emitted when the scheduled snapshot with the specified time was cancelled.
- */
- event SnapshotUnschedule(uint256 indexed time);
-
/**
- @dev See {OpenZeppelin - ERC20Snapshot}
- Snapshotted values have arrays of ids (time) and the value corresponding to that id.
- ids is expected to be sorted in ascending order, and to contain no repeated elements
- because we use findUpperBound in the function _valueAt
- */
- struct Snapshots {
- uint256[] ids;
- uint256[] values;
- }
-
- /**
- @dev See {OpenZeppelin - ERC20Snapshot}
- */
- mapping(address => Snapshots) private _accountBalanceSnapshots;
- Snapshots private _totalSupplySnapshots;
-
- /**
- @dev time instead of a counter for OpenZeppelin
- */
- // Initialized to zero
- uint256 private _currentSnapshotTime;
- // Initialized to zero
- uint256 private _currentSnapshotIndex;
-
- /**
- @dev
- list of scheduled snapshot (time)
- This list is sorted in ascending order
+ * @dev
+ * list of scheduled snapshot (time)
+ * This list is sorted in ascending order
*/
uint256[] private _scheduledSnapshots;
- /**
- * @dev Initializes the contract
- */
- function __ERC20Snapshot_init(
- string memory name_,
- string memory symbol_
- ) internal onlyInitializing {
- __Context_init_unchained();
- __ERC20_init(name_, symbol_);
- __ERC20Snapshot_init_unchained();
- }
-
function __ERC20Snapshot_init_unchained() internal onlyInitializing {
// Nothing to do
// _currentSnapshotTime & _currentSnapshotIndex are initialized to zero
}
- /**
- @dev schedule a snapshot at the specified time
- You can only add a snapshot after the last previous
- */
- function _scheduleSnapshot(uint256 time) internal {
- // Check the time firstly to avoid an useless read of storage
- if (time <= block.timestamp) {
- revert Errors.CMTAT_SnapshotModule_SnapshotScheduledInThePast(
- time,
- block.timestamp
- );
- }
-
- if (_scheduledSnapshots.length > 0) {
- // We check the last snapshot on the list
- uint256 nextSnapshotTime = _scheduledSnapshots[
- _scheduledSnapshots.length - 1
- ];
- if (time < nextSnapshotTime) {
- revert Errors
- .CMTAT_SnapshotModule_SnapshotTimestampBeforeLastSnapshot(
- time,
- nextSnapshotTime
- );
- } else if (time == nextSnapshotTime) {
- revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyExists();
- }
- }
- _scheduledSnapshots.push(time);
- emit SnapshotSchedule(0, time);
- }
-
- /**
- @dev schedule a snapshot at the specified time
- */
- function _scheduleSnapshotNotOptimized(uint256 time) internal {
- if (time <= block.timestamp) {
- revert Errors.CMTAT_SnapshotModule_SnapshotScheduledInThePast(
- time,
- block.timestamp
- );
- }
- (bool isFound, uint256 index) = _findScheduledSnapshotIndex(time);
- // Perfect match
- if (isFound) {
- revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyExists();
- }
- // if no upper bound match found, we push the snapshot at the end of the list
- if (index == _scheduledSnapshots.length) {
- _scheduledSnapshots.push(time);
- } else {
- _scheduledSnapshots.push(
- _scheduledSnapshots[_scheduledSnapshots.length - 1]
- );
- for (uint256 i = _scheduledSnapshots.length - 2; i > index; ) {
- _scheduledSnapshots[i] = _scheduledSnapshots[i - 1];
- unchecked {
- --i;
- }
- }
- _scheduledSnapshots[index] = time;
- }
- emit SnapshotSchedule(0, time);
- }
-
- /**
- @dev reschedule a scheduled snapshot at the specified newTime
- */
- function _rescheduleSnapshot(uint256 oldTime, uint256 newTime) internal {
- // Check the time firstly to avoid an useless read of storage
- if (oldTime <= block.timestamp) {
- revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyDone();
- }
- if (newTime <= block.timestamp) {
- revert Errors.CMTAT_SnapshotModule_SnapshotScheduledInThePast(
- newTime,
- block.timestamp
- );
- }
- if (_scheduledSnapshots.length == 0) {
- revert Errors.CMTAT_SnapshotModule_NoSnapshotScheduled();
- }
- (bool foundOld, uint256 index) = _findScheduledSnapshotIndex(oldTime);
- if (!foundOld) {
- revert Errors.CMTAT_SnapshotModule_SnapshotNotFound();
- }
- if (index + 1 < _scheduledSnapshots.length) {
- uint256 nextSnapshotTime = _scheduledSnapshots[index + 1];
- if (newTime > nextSnapshotTime) {
- revert Errors
- .CMTAT_SnapshotModule_SnapshotTimestampAfterNextSnapshot(
- newTime,
- nextSnapshotTime
- );
- } else if (newTime == nextSnapshotTime) {
- revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyExists();
- }
- }
- if (index > 0) {
- if (newTime <= _scheduledSnapshots[index - 1])
- revert Errors
- .CMTAT_SnapshotModule_SnapshotTimestampBeforePreviousSnapshot(
- newTime,
- _scheduledSnapshots[index - 1]
- );
- }
- _scheduledSnapshots[index] = newTime;
-
- emit SnapshotSchedule(oldTime, newTime);
- }
-
/**
- @dev unschedule the last scheduled snapshot
+ * @notice Return snapshotBalanceOf and snapshotTotalSupply to avoid multiple calls
+ * @return ownerBalance , totalSupply - see snapshotBalanceOf and snapshotTotalSupply
*/
- function _unscheduleLastSnapshot(uint256 time) internal {
- // Check the time firstly to avoid an useless read of storage
- if (time <= block.timestamp) {
- revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyDone();
- }
- if (_scheduledSnapshots.length == 0) {
- revert Errors.CMTAT_SnapshotModule_NoSnapshotScheduled();
- }
- // All snapshot time are unique, so we do not check the indice
- if (time != _scheduledSnapshots[_scheduledSnapshots.length - 1]) {
- revert Errors.CMTAT_SnapshotModule_SnapshotNotFound();
- }
- _scheduledSnapshots.pop();
- emit SnapshotUnschedule(time);
+ function snapshotInfo(uint256 time, address owner) public view returns (uint256 ownerBalance, uint256 totalSupply) {
+ ownerBalance = snapshotBalanceOf(time, owner);
+ totalSupply = snapshotTotalSupply(time);
}
- /**
- @dev unschedule (remove) a scheduled snapshot in three steps:
- - search the snapshot in the list
- - If found, move all next snapshots one position to the left
- - Reduce the array size by deleting the last snapshot
+ /**
+ * @notice Return snapshotBalanceOf for each address in the array and the total supply
+ * @return ownerBalances array with the balance of each address, the total supply
*/
- function _unscheduleSnapshotNotOptimized(uint256 time) internal {
- if (time <= block.timestamp) {
- revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyDone();
- }
- (bool isFound, uint256 index) = _findScheduledSnapshotIndex(time);
- if (!isFound) {
- revert Errors.CMTAT_SnapshotModule_SnapshotNotFound();
- }
- for (uint256 i = index; i + 1 < _scheduledSnapshots.length; ) {
- _scheduledSnapshots[i] = _scheduledSnapshots[i + 1];
- unchecked {
- ++i;
- }
+ function snapshotInfoBatch(uint256 time, address[] calldata addresses) public view returns (uint256[] memory ownerBalances, uint256 totalSupply) {
+ ownerBalances = new uint256[](addresses.length);
+ for(uint256 i = 0; i < addresses.length; ++i){
+ ownerBalances[i] = snapshotBalanceOf(time, addresses[i]);
}
- _scheduledSnapshots.pop();
+ totalSupply = snapshotTotalSupply(time);
}
- /**
- @dev
- Get the next scheduled snapshots
- */
- function getNextSnapshots() public view returns (uint256[] memory) {
- uint256[] memory nextScheduledSnapshot = new uint256[](0);
- // no snapshot were planned
- if (_scheduledSnapshots.length > 0) {
- (
- uint256 timeLowerBound,
- uint256 indexLowerBound
- ) = _findScheduledMostRecentPastSnapshot();
- // All snapshots are situated in the futur
- if ((timeLowerBound == 0) && (_currentSnapshotTime == 0)) {
- return _scheduledSnapshots;
- } else {
- // There are snapshots situated in the futur
- if (indexLowerBound + 1 != _scheduledSnapshots.length) {
- // All next snapshots are located after the snapshot specified by indexLowerBound
- uint256 arraySize = _scheduledSnapshots.length -
- indexLowerBound -
- 1;
- nextScheduledSnapshot = new uint256[](arraySize);
- for (uint256 i; i < arraySize; ) {
- nextScheduledSnapshot[i] = _scheduledSnapshots[
- indexLowerBound + 1 + i
- ];
- unchecked {
- ++i;
- }
- }
- }
+ /**
+ * @notice Return snapshotBalanceOf for each address in the array and the total supply
+ * @return ownerBalances array with the balance of each address, the total supply
+ */
+ function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses) public view returns (uint256[][] memory ownerBalances, uint256[] memory totalSupply) {
+ ownerBalances = new uint256[][](times.length);
+ totalSupply = new uint256[](times.length);
+ for(uint256 iT = 0; iT < times.length; ++iT){
+ /*ownerBalances[iT] = new uint256[](addresses.length);
+ for(uint256 jA = 0; jA < addresses.length; ++jA){
+ ownerBalances[iT][jA] = snapshotBalanceOf(times[iT], addresses[jA]);
}
+ totalSupply[iT] = snapshotTotalSupply(times[iT]);*/
+ (ownerBalances[iT], totalSupply[iT]) = snapshotInfoBatch(times[iT],addresses);
}
- return nextScheduledSnapshot;
}
/**
- @dev
- Get all snapshots
- */
- function getAllSnapshots() public view returns (uint256[] memory) {
- return _scheduledSnapshots;
- }
-
- /**
- @notice Return the number of tokens owned by the given owner at the time when the snapshot with the given time was created.
- @return value stored in the snapshot, or the actual balance if no snapshot
+ * @notice Return the number of tokens owned by the given owner at the time when the snapshot with the given time was created.
+ * @return value stored in the snapshot, or the actual balance if no snapshot
*/
function snapshotBalanceOf(
uint256 time,
@@ -295,9 +85,9 @@ abstract contract ERC20SnapshotModuleInternal is ERC20Upgradeable {
}
/**
- @dev See {OpenZeppelin - ERC20Snapshot}
- Retrieves the total supply at the specified time.
- @return value stored in the snapshot, or the actual totalSupply if no snapshot
+ * @dev See {OpenZeppelin - ERC20Snapshot}
+ * Retrieves the total supply at the specified time.
+ * @return value stored in the snapshot, or the actual totalSupply if no snapshot
*/
function snapshotTotalSupply(uint256 time) public view returns (uint256) {
(bool snapshotted, uint256 value) = _valueAt(
@@ -307,15 +97,15 @@ abstract contract ERC20SnapshotModuleInternal is ERC20Upgradeable {
return snapshotted ? value : totalSupply();
}
+
/**
- @dev Update balance and/or total supply snapshots before the values are modified. This is implemented
- in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations.
+ * @dev Update balance and/or total supply snapshots before the values are modified. This is implemented
+ * in the _beforeTokenTransfer hook, which is executed for _mint, _burn, and _transfer operations.
*/
- function _update(
+ function _snapshotUpdate(
address from,
- address to,
- uint256 amount
- ) internal virtual override {
+ address to
+ ) internal virtual {
_setCurrentSnapshot();
if (from != address(0)) {
// for both burn and transfer
@@ -332,163 +122,21 @@ abstract contract ERC20SnapshotModuleInternal is ERC20Upgradeable {
_updateAccountSnapshot(to);
_updateTotalSupplySnapshot();
}
- ERC20Upgradeable._update(from, to, amount);
}
/**
- @dev See {OpenZeppelin - ERC20Snapshot}
- @param time where we want a snapshot
- @param snapshots the struct where are stored the snapshots
- @return snapshotExist true if a snapshot is found, false otherwise
- value 0 if no snapshot, balance value if a snapshot exists
- */
- function _valueAt(
- uint256 time,
- Snapshots storage snapshots
- ) private view returns (bool snapshotExist, uint256 value) {
- // When a valid snapshot is queried, there are three possibilities:
- // a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never
- // created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds
- // to this id is the current one.
- // b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the
- // requested id, and its value is the one to return.
- // c) More snapshots were created after the requested one, and the queried value was later modified. There will be
- // no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is
- // larger than the requested one.
- //
- // In summary, we need to find an element in an array, returning the index of the smallest value that is larger if
- // it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does
- // exactly this.
-
- uint256 index = snapshots.ids.findUpperBound(time);
-
- if (index == snapshots.ids.length) {
- return (false, 0);
- } else {
- return (true, snapshots.values[index]);
- }
- }
-
- /**
- @dev See {OpenZeppelin - ERC20Snapshot}
+ * @dev See {OpenZeppelin - ERC20Snapshot}
*/
function _updateAccountSnapshot(address account) private {
_updateSnapshot(_accountBalanceSnapshots[account], balanceOf(account));
}
/**
- @dev See {OpenZeppelin - ERC20Snapshot}
+ * @dev See {OpenZeppelin - ERC20Snapshot}
*/
function _updateTotalSupplySnapshot() private {
_updateSnapshot(_totalSupplySnapshots, totalSupply());
}
- /**
- @dev
- Inside a struct Snapshots:
- - Update the array ids to the current Snapshot time if this one is greater than the snapshot times stored in ids.
- - Update the value to the corresponding value.
- */
- function _updateSnapshot(
- Snapshots storage snapshots,
- uint256 currentValue
- ) private {
- uint256 current = _currentSnapshotTime;
- if (_lastSnapshot(snapshots.ids) < current) {
- snapshots.ids.push(current);
- snapshots.values.push(currentValue);
- }
- }
-
- /**
- @dev
- Set the currentSnapshotTime by retrieving the most recent snapshot
- if a snapshot exists, clear all past scheduled snapshot
- */
- function _setCurrentSnapshot() internal {
- (
- uint256 scheduleSnapshotTime,
- uint256 scheduleSnapshotIndex
- ) = _findScheduledMostRecentPastSnapshot();
- if (scheduleSnapshotTime > 0) {
- _currentSnapshotTime = scheduleSnapshotTime;
- _currentSnapshotIndex = scheduleSnapshotIndex;
- }
- }
-
- /**
- @return the last snapshot time inside a snapshot ids array
- */
- function _lastSnapshot(
- uint256[] storage ids
- ) private view returns (uint256) {
- if (ids.length == 0) {
- return 0;
- } else {
- return ids[ids.length - 1];
- }
- }
-
- /**
- @dev Find the snapshot index at the specified time
- @return (true, index) if the snapshot exists, (false, 0) otherwise
- */
- function _findScheduledSnapshotIndex(
- uint256 time
- ) private view returns (bool, uint256) {
- uint256 indexFound = _scheduledSnapshots.findUpperBound(time);
- uint256 _scheduledSnapshotsLength = _scheduledSnapshots.length;
- // Exact match
- if (
- indexFound != _scheduledSnapshotsLength &&
- _scheduledSnapshots[indexFound] == time
- ) {
- return (true, indexFound);
- }
- // Upper bound match
- else if (indexFound != _scheduledSnapshotsLength) {
- return (false, indexFound);
- }
- // no match
- else {
- return (false, _scheduledSnapshotsLength);
- }
- }
-
- /**
- @dev find the most recent past snapshot
- The complexity of this function is O(N) because we go through the whole list
- */
- function _findScheduledMostRecentPastSnapshot()
- private
- view
- returns (uint256 time, uint256 index)
- {
- uint256 currentArraySize = _scheduledSnapshots.length;
- // no snapshot or the current snapshot already points on the last snapshot
- if (
- currentArraySize == 0 ||
- ((_currentSnapshotIndex + 1 == currentArraySize) && (time != 0))
- ) {
- return (0, currentArraySize);
- }
- // mostRecent is initialized in the loop
- uint256 mostRecent;
- index = currentArraySize;
- for (uint256 i = _currentSnapshotIndex; i < currentArraySize; ) {
- if (_scheduledSnapshots[i] <= block.timestamp) {
- mostRecent = _scheduledSnapshots[i];
- index = i;
- } else {
- // All snapshot are planned in the futur
- break;
- }
- unchecked {
- ++i;
- }
- }
- return (mostRecent, index);
- }
-
uint256[50] private __gap;
}
diff --git a/contracts/modules/internal/EnforcementModuleInternal.sol b/contracts/modules/internal/EnforcementModuleInternal.sol
index 173db731..63230bfb 100644
--- a/contracts/modules/internal/EnforcementModuleInternal.sol
+++ b/contracts/modules/internal/EnforcementModuleInternal.sol
@@ -37,14 +37,6 @@ abstract contract EnforcementModuleInternal is
mapping(address => bool) private _frozen;
- /**
- * @dev Initializes the contract
- */
- function __Enforcement_init() internal onlyInitializing {
- __Context_init_unchained();
- __Enforcement_init_unchained();
- }
-
function __Enforcement_init_unchained() internal onlyInitializing {
// no variable to initialize
}
diff --git a/contracts/modules/internal/ValidationModuleInternal.sol b/contracts/modules/internal/ValidationModuleInternal.sol
index 12c03c3e..6219f105 100644
--- a/contracts/modules/internal/ValidationModuleInternal.sol
+++ b/contracts/modules/internal/ValidationModuleInternal.sol
@@ -4,8 +4,7 @@ pragma solidity ^0.8.20;
import "../../../openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol";
import "../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
-import "../../interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol";
-
+import "../../interfaces/engine/IRuleEngine.sol";
/**
* @dev Validation module.
*
@@ -18,22 +17,12 @@ abstract contract ValidationModuleInternal is
/**
* @dev Emitted when a rule engine is set.
*/
- event RuleEngine(IERC1404Wrapper indexed newRuleEngine);
-
- IERC1404Wrapper public ruleEngine;
+ event RuleEngine(IRuleEngine indexed newRuleEngine);
- /**
- * @dev Initializes the contract with rule engine.
- */
- function __Validation_init(
- IERC1404Wrapper ruleEngine_
- ) internal onlyInitializing {
- __Context_init_unchained();
- __Validation_init_unchained(ruleEngine_);
- }
+ IRuleEngine public ruleEngine;
function __Validation_init_unchained(
- IERC1404Wrapper ruleEngine_
+ IRuleEngine ruleEngine_
) internal onlyInitializing {
if (address(ruleEngine_) != address(0)) {
ruleEngine = ruleEngine_;
@@ -42,7 +31,7 @@ abstract contract ValidationModuleInternal is
}
/**
- @dev before making a call to this function, you have to check if a ruleEngine is set.
+ * @dev before making a call to this function, you have to check if a ruleEngine is set.
*/
function _validateTransfer(
address from,
@@ -53,7 +42,7 @@ abstract contract ValidationModuleInternal is
}
/**
- @dev before making a call to this function, you have to check if a ruleEngine is set.
+ * @dev before making a call to this function, you have to check if a ruleEngine is set.
*/
function _messageForTransferRestriction(
uint8 restrictionCode
@@ -62,7 +51,7 @@ abstract contract ValidationModuleInternal is
}
/**
- @dev before making a call to this function, you have to check if a ruleEngine is set.
+ * @dev before making a call to this function, you have to check if a ruleEngine is set.
*/
function _detectTransferRestriction(
address from,
@@ -72,5 +61,9 @@ abstract contract ValidationModuleInternal is
return ruleEngine.detectTransferRestriction(from, to, amount);
}
+ function _operateOnTransfer(address from, address to, uint256 amount) virtual internal returns (bool) {
+ return ruleEngine.operateOnTransfer(from, to, amount);
+ }
+
uint256[50] private __gap;
}
diff --git a/contracts/modules/internal/base/SnapshotModuleBase.sol b/contracts/modules/internal/base/SnapshotModuleBase.sol
new file mode 100644
index 00000000..1290cbd2
--- /dev/null
+++ b/contracts/modules/internal/base/SnapshotModuleBase.sol
@@ -0,0 +1,405 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
+import {Arrays} from '@openzeppelin/contracts/utils/Arrays.sol';
+
+import "../../../libraries/Errors.sol";
+
+/**
+ * @dev Base for the Snapshot module
+ *
+ * Useful to take a snapshot of token holder balance and total supply at a specific time
+ * Inspired by Openzeppelin - ERC20Snapshot but use the time as Id instead of a counter.
+ * Contrary to OpenZeppelin, the function _getCurrentSnapshotId is not available
+ because overriding this function can break the contract.
+ */
+
+abstract contract SnapshotModuleBase is Initializable {
+ using Arrays for uint256[];
+
+ /**
+ @notice Emitted when the snapshot with the specified oldTime was scheduled or rescheduled at the specified newTime.
+ */
+ event SnapshotSchedule(uint256 indexed oldTime, uint256 indexed newTime);
+
+ /**
+ * @notice Emitted when the scheduled snapshot with the specified time was cancelled.
+ */
+ event SnapshotUnschedule(uint256 indexed time);
+
+ /**
+ * @dev See {OpenZeppelin - ERC20Snapshot}
+ * Snapshotted values have arrays of ids (time) and the value corresponding to that id.
+ * ids is expected to be sorted in ascending order, and to contain no repeated elements
+ * because we use findUpperBound in the function _valueAt
+ */
+ struct Snapshots {
+ uint256[] ids;
+ uint256[] values;
+ }
+
+ /**
+ * @dev See {OpenZeppelin - ERC20Snapshot}
+ */
+ mapping(address => Snapshots) internal _accountBalanceSnapshots;
+ Snapshots internal _totalSupplySnapshots;
+
+ /**
+ * @dev time instead of a counter for OpenZeppelin
+ */
+ // Initialized to zero
+ uint256 private _currentSnapshotTime;
+ // Initialized to zero
+ uint256 private _currentSnapshotIndex;
+
+ /**
+ * @dev
+ * list of scheduled snapshot (time)
+ * This list is sorted in ascending order
+ */
+ uint256[] private _scheduledSnapshots;
+
+ function __SnapshotModuleBase_init_unchained() internal onlyInitializing {
+ // Nothing to do
+ // _currentSnapshotTime & _currentSnapshotIndex are initialized to zero
+ }
+
+
+ /**
+ *
+ * @notice Get all snapshots
+ */
+ function getAllSnapshots() public view returns (uint256[] memory) {
+ return _scheduledSnapshots;
+ }
+
+ /**
+ * @dev
+ * Get the next scheduled snapshots
+ */
+ function getNextSnapshots() public view returns (uint256[] memory) {
+ uint256[] memory nextScheduledSnapshot = new uint256[](0);
+ // no snapshot were planned
+ if (_scheduledSnapshots.length > 0) {
+ (
+ uint256 timeLowerBound,
+ uint256 indexLowerBound
+ ) = _findScheduledMostRecentPastSnapshot();
+ // All snapshots are situated in the futur
+ if ((timeLowerBound == 0) && (_currentSnapshotTime == 0)) {
+ return _scheduledSnapshots;
+ } else {
+ // There are snapshots situated in the futur
+ if (indexLowerBound + 1 != _scheduledSnapshots.length) {
+ // All next snapshots are located after the snapshot specified by indexLowerBound
+ uint256 arraySize = _scheduledSnapshots.length -
+ indexLowerBound -
+ 1;
+ nextScheduledSnapshot = new uint256[](arraySize);
+ // No need of unchecked block since Soliditiy 0.8.22
+ for (uint256 i; i < arraySize; ++i) {
+ nextScheduledSnapshot[i] = _scheduledSnapshots[
+ indexLowerBound + 1 + i
+ ];
+ }
+ }
+ }
+ }
+ return nextScheduledSnapshot;
+ }
+
+
+ /**
+ * @dev schedule a snapshot at the specified time
+ * You can only add a snapshot after the last previous
+ */
+ function _scheduleSnapshot(uint256 time) internal {
+ // Check the time firstly to avoid an useless read of storage
+ if (time <= block.timestamp) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotScheduledInThePast(
+ time,
+ block.timestamp
+ );
+ }
+
+ if (_scheduledSnapshots.length > 0) {
+ // We check the last snapshot on the list
+ uint256 nextSnapshotTime = _scheduledSnapshots[
+ _scheduledSnapshots.length - 1
+ ];
+ if (time < nextSnapshotTime) {
+ revert Errors
+ .CMTAT_SnapshotModule_SnapshotTimestampBeforeLastSnapshot(
+ time,
+ nextSnapshotTime
+ );
+ } else if (time == nextSnapshotTime) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyExists();
+ }
+ }
+ _scheduledSnapshots.push(time);
+ emit SnapshotSchedule(0, time);
+ }
+
+ /**
+ * @dev schedule a snapshot at the specified time
+ */
+ function _scheduleSnapshotNotOptimized(uint256 time) internal {
+ if (time <= block.timestamp) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotScheduledInThePast(
+ time,
+ block.timestamp
+ );
+ }
+ (bool isFound, uint256 index) = _findScheduledSnapshotIndex(time);
+ // Perfect match
+ if (isFound) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyExists();
+ }
+ // if no upper bound match found, we push the snapshot at the end of the list
+ if (index == _scheduledSnapshots.length) {
+ _scheduledSnapshots.push(time);
+ } else {
+ _scheduledSnapshots.push(
+ _scheduledSnapshots[_scheduledSnapshots.length - 1]
+ );
+ for (uint256 i = _scheduledSnapshots.length - 2; i > index; ) {
+ _scheduledSnapshots[i] = _scheduledSnapshots[i - 1];
+ unchecked {
+ --i;
+ }
+ }
+ _scheduledSnapshots[index] = time;
+ }
+ emit SnapshotSchedule(0, time);
+ }
+
+ /**
+ * @dev reschedule a scheduled snapshot at the specified newTime
+ */
+ function _rescheduleSnapshot(uint256 oldTime, uint256 newTime) internal {
+ // Check the time firstly to avoid an useless read of storage
+ if (oldTime <= block.timestamp) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyDone();
+ }
+ if (newTime <= block.timestamp) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotScheduledInThePast(
+ newTime,
+ block.timestamp
+ );
+ }
+ if (_scheduledSnapshots.length == 0) {
+ revert Errors.CMTAT_SnapshotModule_NoSnapshotScheduled();
+ }
+ (bool foundOld, uint256 index) = _findScheduledSnapshotIndex(oldTime);
+ if (!foundOld) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotNotFound();
+ }
+ if (index + 1 < _scheduledSnapshots.length) {
+ uint256 nextSnapshotTime = _scheduledSnapshots[index + 1];
+ if (newTime > nextSnapshotTime) {
+ revert Errors
+ .CMTAT_SnapshotModule_SnapshotTimestampAfterNextSnapshot(
+ newTime,
+ nextSnapshotTime
+ );
+ } else if (newTime == nextSnapshotTime) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyExists();
+ }
+ }
+ if (index > 0) {
+ if (newTime <= _scheduledSnapshots[index - 1])
+ revert Errors
+ .CMTAT_SnapshotModule_SnapshotTimestampBeforePreviousSnapshot(
+ newTime,
+ _scheduledSnapshots[index - 1]
+ );
+ }
+ _scheduledSnapshots[index] = newTime;
+
+ emit SnapshotSchedule(oldTime, newTime);
+ }
+
+ /**
+ * @dev unschedule the last scheduled snapshot
+ */
+ function _unscheduleLastSnapshot(uint256 time) internal {
+ // Check the time firstly to avoid an useless read of storage
+ if (time <= block.timestamp) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyDone();
+ }
+ if (_scheduledSnapshots.length == 0) {
+ revert Errors.CMTAT_SnapshotModule_NoSnapshotScheduled();
+ }
+ // All snapshot time are unique, so we do not check the indice
+ if (time != _scheduledSnapshots[_scheduledSnapshots.length - 1]) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotNotFound();
+ }
+ _scheduledSnapshots.pop();
+ emit SnapshotUnschedule(time);
+ }
+
+ /**
+ * @dev unschedule (remove) a scheduled snapshot in three steps:
+ * - search the snapshot in the list
+ * - If found, move all next snapshots one position to the left
+ * - Reduce the array size by deleting the last snapshot
+ */
+ function _unscheduleSnapshotNotOptimized(uint256 time) internal {
+ if (time <= block.timestamp) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotAlreadyDone();
+ }
+ (bool isFound, uint256 index) = _findScheduledSnapshotIndex(time);
+ if (!isFound) {
+ revert Errors.CMTAT_SnapshotModule_SnapshotNotFound();
+ }
+ // No need of unchecked block since Soliditiy 0.8.22
+ for (uint256 i = index; i + 1 < _scheduledSnapshots.length; ++i ) {
+ _scheduledSnapshots[i] = _scheduledSnapshots[i + 1];
+ }
+ _scheduledSnapshots.pop();
+ }
+
+ /**
+ * @dev See {OpenZeppelin - ERC20Snapshot}
+ * @param time where we want a snapshot
+ * @param snapshots the struct where are stored the snapshots
+ * @return snapshotExist true if a snapshot is found, false otherwise
+ * value 0 if no snapshot, balance value if a snapshot exists
+ */
+ function _valueAt(
+ uint256 time,
+ Snapshots storage snapshots
+ ) internal view returns (bool snapshotExist, uint256 value) {
+ // When a valid snapshot is queried, there are three possibilities:
+ // a) The queried value was not modified after the snapshot was taken. Therefore, a snapshot entry was never
+ // created for this id, and all stored snapshot ids are smaller than the requested one. The value that corresponds
+ // to this id is the current one.
+ // b) The queried value was modified after the snapshot was taken. Therefore, there will be an entry with the
+ // requested id, and its value is the one to return.
+ // c) More snapshots were created after the requested one, and the queried value was later modified. There will be
+ // no entry for the requested id: the value that corresponds to it is that of the smallest snapshot id that is
+ // larger than the requested one.
+ //
+ // In summary, we need to find an element in an array, returning the index of the smallest value that is larger if
+ // it is not found, unless said value doesn't exist (e.g. when all values are smaller). Arrays.findUpperBound does
+ // exactly this.
+
+ uint256 index = snapshots.ids.findUpperBound(time);
+
+ if (index == snapshots.ids.length) {
+ return (false, 0);
+ } else {
+ return (true, snapshots.values[index]);
+ }
+ }
+
+ /**
+ * @dev
+ * Inside a struct Snapshots:
+ * - Update the array ids to the current Snapshot time if this one is greater than the snapshot times stored in ids.
+ * - Update the value to the corresponding value.
+ */
+ function _updateSnapshot(
+ Snapshots storage snapshots,
+ uint256 currentValue
+ ) internal {
+ uint256 current = _currentSnapshotTime;
+ if (_lastSnapshot(snapshots.ids) < current) {
+ snapshots.ids.push(current);
+ snapshots.values.push(currentValue);
+ }
+ }
+
+ /**
+ * @dev
+ * Set the currentSnapshotTime by retrieving the most recent snapshot
+ * if a snapshot exists, clear all past scheduled snapshot
+ */
+ function _setCurrentSnapshot() internal {
+ (
+ uint256 scheduleSnapshotTime,
+ uint256 scheduleSnapshotIndex
+ ) = _findScheduledMostRecentPastSnapshot();
+ if (scheduleSnapshotTime > 0) {
+ _currentSnapshotTime = scheduleSnapshotTime;
+ _currentSnapshotIndex = scheduleSnapshotIndex;
+ }
+ }
+
+ /**
+ * @return the last snapshot time inside a snapshot ids array
+ */
+ function _lastSnapshot(
+ uint256[] storage ids
+ ) private view returns (uint256) {
+ if (ids.length == 0) {
+ return 0;
+ } else {
+ return ids[ids.length - 1];
+ }
+ }
+
+ /**
+ * @dev Find the snapshot index at the specified time
+ * @return (true, index) if the snapshot exists, (false, 0) otherwise
+ */
+ function _findScheduledSnapshotIndex(
+ uint256 time
+ ) private view returns (bool, uint256) {
+ uint256 indexFound = _scheduledSnapshots.findUpperBound(time);
+ uint256 _scheduledSnapshotsLength = _scheduledSnapshots.length;
+ // Exact match
+ if (
+ indexFound != _scheduledSnapshotsLength &&
+ _scheduledSnapshots[indexFound] == time
+ ) {
+ return (true, indexFound);
+ }
+ // Upper bound match
+ else if (indexFound != _scheduledSnapshotsLength) {
+ return (false, indexFound);
+ }
+ // no match
+ else {
+ return (false, _scheduledSnapshotsLength);
+ }
+ }
+
+ /**
+ * @dev find the most recent past snapshot
+ * The complexity of this function is O(N) because we go through the whole list
+ */
+ function _findScheduledMostRecentPastSnapshot()
+ private
+ view
+ returns (uint256 time, uint256 index)
+ {
+ uint256 currentArraySize = _scheduledSnapshots.length;
+ // no snapshot or the current snapshot already points on the last snapshot
+ if (
+ currentArraySize == 0 ||
+ ((_currentSnapshotIndex + 1 == currentArraySize) && (time != 0))
+ ) {
+ return (0, currentArraySize);
+ }
+ // mostRecent is initialized in the loop
+ uint256 mostRecent;
+ index = currentArraySize;
+ // No need of unchecked block since Soliditiy 0.8.22
+ for (uint256 i = _currentSnapshotIndex; i < currentArraySize; ++i ) {
+ if (_scheduledSnapshots[i] <= block.timestamp) {
+ mostRecent = _scheduledSnapshots[i];
+ index = i;
+ } else {
+ // All snapshot are planned in the futur
+ break;
+ }
+ }
+ return (mostRecent, index);
+ }
+
+ uint256[50] private __gap;
+}
diff --git a/contracts/modules/security/AuthorizationModule.sol b/contracts/modules/security/AuthorizationModule.sol
index 43dbf630..7870b318 100644
--- a/contracts/modules/security/AuthorizationModule.sol
+++ b/contracts/modules/security/AuthorizationModule.sol
@@ -2,45 +2,16 @@
pragma solidity ^0.8.20;
-import "../../../openzeppelin-contracts-upgradeable/contracts/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol";
-import "../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
-
+import "../../../openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol";
import "../../libraries/Errors.sol";
+import "../../interfaces/engine/IAuthorizationEngine.sol";
-abstract contract AuthorizationModule is AccessControlDefaultAdminRulesUpgradeable {
- // BurnModule
- bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
- // CreditEvents
- bytes32 public constant DEBT_CREDIT_EVENT_ROLE =
- keccak256("DEBT_CREDIT_EVENT_ROLE");
- // DebtModule
- bytes32 public constant DEBT_ROLE = keccak256("DEBT_ROLE");
- // EnforcementModule
- bytes32 public constant ENFORCER_ROLE = keccak256("ENFORCER_ROLE");
- // MintModule
- bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
- // PauseModule
- bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
- // SnapshotModule
- bytes32 public constant SNAPSHOOTER_ROLE = keccak256("SNAPSHOOTER_ROLE");
-
-
-
- function __AuthorizationModule_init(
- address admin,
- uint48 initialDelay
- ) internal onlyInitializing {
- /* OpenZeppelin */
- __Context_init_unchained();
- // AccessControlUpgradeable inherits from ERC165Upgradeable
- __ERC165_init_unchained();
- __AccessControl_init_unchained();
- __AccessControlDefaultAdminRules_init_unchained(initialDelay, admin);
-
- /* own function */
- __AuthorizationModule_init_unchained();
- }
-
+abstract contract AuthorizationModule is AccessControlUpgradeable {
+ IAuthorizationEngine private authorizationEngine;
+ /**
+ * @dev Emitted when a rule engine is set.
+ */
+ event AuthorizationEngine(IAuthorizationEngine indexed newAuthorizationEngine);
/**
* @dev
*
@@ -48,17 +19,62 @@ abstract contract AuthorizationModule is AccessControlDefaultAdminRulesUpgradeab
* - The control of the zero address is done by AccessControlDefaultAdminRules
*
*/
- function __AuthorizationModule_init_unchained(
- ) internal view onlyInitializing {
+ function __AuthorizationModule_init_unchained(address admin, IAuthorizationEngine authorizationEngine_)
+ internal onlyInitializing {
+ if(admin == address(0)){
+ revert Errors.CMTAT_AuthorizationModule_AddressZeroNotAllowed();
+ }
+ _grantRole(DEFAULT_ADMIN_ROLE, admin);
+ if (address(authorizationEngine) != address (0)) {
+ authorizationEngine = authorizationEngine_;
+ }
+
+
}
/*
+ * @notice set an authorizationEngine if not already set
+ * @dev once an AuthorizationEngine is set, it is not possible to unset it
+ */
+ function setAuthorizationEngine(
+ IAuthorizationEngine authorizationEngine_
+ ) external onlyRole(DEFAULT_ADMIN_ROLE) {
+ if (address(authorizationEngine) != address (0)){
+ revert Errors.CMTAT_AuthorizationModule_AuthorizationEngineAlreadySet();
+ }
+ authorizationEngine = authorizationEngine_;
+ emit AuthorizationEngine(authorizationEngine_);
+ }
+
+ function grantRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
+ if (address(authorizationEngine) != address (0)) {
+ bool result = authorizationEngine.operateOnGrantRole(role, account);
+ if(!result) {
+ // Operation rejected by the authorizationEngine
+ revert Errors.CMTAT_AuthorizationModule_InvalidAuthorization();
+ }
+ }
+ return AccessControlUpgradeable.grantRole(role, account);
+ }
+
+ function revokeRole(bytes32 role, address account) public override onlyRole(getRoleAdmin(role)) {
+ if (address(authorizationEngine) != address (0)) {
+ bool result = authorizationEngine.operateOnRevokeRole(role, account);
+ if(!result) {
+ // Operation rejected by the authorizationEngine
+ revert Errors.CMTAT_AuthorizationModule_InvalidAuthorization();
+ }
+ }
+ return AccessControlUpgradeable.revokeRole(role, account);
+ }
+
+ /**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(
bytes32 role,
address account
- ) public view virtual override( IAccessControl, AccessControlUpgradeable) returns (bool) {
+ ) public view virtual override(AccessControlUpgradeable) returns (bool) {
// The Default Admin has all roles
if (AccessControlUpgradeable.hasRole(DEFAULT_ADMIN_ROLE, account)) {
return true;
@@ -66,16 +82,5 @@ abstract contract AuthorizationModule is AccessControlDefaultAdminRulesUpgradeab
return AccessControlUpgradeable.hasRole(role, account);
}
- /**
- @notice
- Warning: this function should be called only in case of necessity (e.g private key leak)
- Its goal is to transfer the adminship of the contract to a new admin, whithout delay.
- The prefer way is to use the workflow of AccessControlDefaultAdminRulesUpgradeable
- */
- function transferAdminshipDirectly(address newAdmin) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
- // we revoke first the admin since we can only have one admin
- _revokeRole(DEFAULT_ADMIN_ROLE, _msgSender());
- _grantRole(DEFAULT_ADMIN_ROLE, newAdmin);
- }
uint256[50] private __gap;
}
diff --git a/contracts/modules/wrapper/controllers/ValidationModule.sol b/contracts/modules/wrapper/controllers/ValidationModule.sol
index d7b0ed86..63fe5183 100644
--- a/contracts/modules/wrapper/controllers/ValidationModule.sol
+++ b/contracts/modules/wrapper/controllers/ValidationModule.sol
@@ -2,7 +2,6 @@
pragma solidity ^0.8.20;
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../security/AuthorizationModule.sol";
import "../../internal/ValidationModuleInternal.sol";
import "../core/PauseModule.sol";
@@ -29,44 +28,20 @@ abstract contract ValidationModule is
}
/*
- @notice set a RuleEngine
- @param ruleEngine_ the call will be reverted if the new value of ruleEngine is the same as the current one
+ * @notice set a RuleEngine
+ * @param ruleEngine_ the call will be reverted if the new value of ruleEngine is the same as the current one
*/
function setRuleEngine(
- IERC1404Wrapper ruleEngine_
+ IRuleEngine ruleEngine_
) external onlyRole(DEFAULT_ADMIN_ROLE) {
- if (ruleEngine == ruleEngine_)
- revert Errors.CMTAT_ValidationModule_SameValue();
+ if (ruleEngine == ruleEngine_){
+ revert Errors.CMTAT_ValidationModule_SameValue();
+ }
ruleEngine = ruleEngine_;
emit RuleEngine(ruleEngine_);
}
- /**
- * @dev ERC1404 check if _value token can be transferred from _from to _to
- * @param from address The address which you want to send tokens from
- * @param to address The address which you want to transfer to
- * @param amount uint256 the amount of tokens to be transferred
- * @return code of the rejection reason
- */
- function detectTransferRestriction(
- address from,
- address to,
- uint256 amount
- ) public view override returns (uint8 code) {
- if (paused()) {
- return uint8(REJECTED_CODE_BASE.TRANSFER_REJECTED_PAUSED);
- } else if (frozen(from)) {
- return uint8(REJECTED_CODE_BASE.TRANSFER_REJECTED_FROM_FROZEN);
- } else if (frozen(to)) {
- return uint8(REJECTED_CODE_BASE.TRANSFER_REJECTED_TO_FROZEN);
- } else if (address(ruleEngine) != address(0)) {
- return _detectTransferRestriction(from, to, amount);
- } else {
- return uint8(REJECTED_CODE_BASE.TRANSFER_OK);
- }
- }
-
- /**
+ /**
* @dev ERC1404 returns the human readable explaination corresponding to the error code returned by detectTransferRestriction
* @param restrictionCode The error code returned by detectTransferRestriction
* @return message The human readable explaination corresponding to the error code returned by detectTransferRestriction
@@ -97,13 +72,38 @@ abstract contract ValidationModule is
return TEXT_UNKNOWN_CODE;
}
}
+
+ /**
+ * @dev ERC1404 check if _value token can be transferred from _from to _to
+ * @param from address The address which you want to send tokens from
+ * @param to address The address which you want to transfer to
+ * @param amount uint256 the amount of tokens to be transferred
+ * @return code of the rejection reason
+ */
+ function detectTransferRestriction(
+ address from,
+ address to,
+ uint256 amount
+ ) public view override returns (uint8 code) {
+ if (paused()) {
+ return uint8(REJECTED_CODE_BASE.TRANSFER_REJECTED_PAUSED);
+ } else if (frozen(from)) {
+ return uint8(REJECTED_CODE_BASE.TRANSFER_REJECTED_FROM_FROZEN);
+ } else if (frozen(to)) {
+ return uint8(REJECTED_CODE_BASE.TRANSFER_REJECTED_TO_FROZEN);
+ } else if (address(ruleEngine) != address(0)) {
+ return _detectTransferRestriction(from, to, amount);
+ } else {
+ return uint8(REJECTED_CODE_BASE.TRANSFER_OK);
+ }
+ }
function validateTransfer(
address from,
address to,
uint256 amount
) public view override returns (bool) {
- if (paused() || frozen(from) || frozen(to)) {
+ if (!_validateTransferByModule(from, to, amount)) {
return false;
}
if (address(ruleEngine) != address(0)) {
@@ -112,5 +112,26 @@ abstract contract ValidationModule is
return true;
}
+ function _validateTransferByModule(
+ address from,
+ address to,
+ uint256 /*amount*/
+ ) internal view returns (bool) {
+ if (paused() || frozen(from) || frozen(to)) {
+ return false;
+ }
+ return true;
+ }
+
+ function _operateOnTransfer(address from, address to, uint256 amount) override internal returns (bool){
+ if (!_validateTransferByModule(from, to, amount)){
+ return false;
+ }
+ if (address(ruleEngine) != address(0)) {
+ return ValidationModuleInternal._operateOnTransfer(from, to, amount);
+ }
+ return true;
+ }
+
uint256[50] private __gap;
}
diff --git a/contracts/modules/wrapper/core/BaseModule.sol b/contracts/modules/wrapper/core/BaseModule.sol
index 7fc46e74..1b2a3e86 100644
--- a/contracts/modules/wrapper/core/BaseModule.sol
+++ b/contracts/modules/wrapper/core/BaseModule.sol
@@ -3,16 +3,15 @@
pragma solidity ^0.8.20;
// required OZ imports here
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../security/AuthorizationModule.sol";
import "../../../libraries/Errors.sol";
abstract contract BaseModule is AuthorizationModule {
- /*
- @notice
- Get the current version of the smart contract
+ /**
+ * @notice
+ * Get the current version of the smart contract
*/
- string public constant VERSION = "2.3.1";
+ string public constant VERSION = "2.4.0";
/* Events */
event Term(string indexed newTermIndexed, string newTerm);
event TokenId(string indexed newTokenIdIndexed, string newTokenId);
@@ -26,6 +25,7 @@ abstract contract BaseModule is AuthorizationModule {
string public tokenId;
string public terms;
string public information;
+ // additional attribute to store information as an uint256
uint256 public flag;
@@ -50,8 +50,8 @@ abstract contract BaseModule is AuthorizationModule {
}
/* Methods */
- /*
- @notice the tokenId will be changed even if the new value is the same as the current one
+ /**
+ * @notice the tokenId will be changed even if the new value is the same as the current one
*/
function setTokenId(
string calldata tokenId_
@@ -60,8 +60,8 @@ abstract contract BaseModule is AuthorizationModule {
emit TokenId(tokenId_, tokenId_);
}
- /*
- @notice The terms will be changed even if the new value is the same as the current one
+ /**
+ * @notice The terms will be changed even if the new value is the same as the current one
*/
function setTerms(
string calldata terms_
@@ -70,8 +70,8 @@ abstract contract BaseModule is AuthorizationModule {
emit Term(terms_, terms_);
}
- /*
- @notice The information will be changed even if the new value is the same as the current one
+ /**
+ * @notice The information will be changed even if the new value is the same as the current one
*/
function setInformation(
string calldata information_
@@ -80,8 +80,8 @@ abstract contract BaseModule is AuthorizationModule {
emit Information(information_, information_);
}
- /*
- @notice The call will be reverted if the new value of flag is the same as the current one
+ /**
+ * @notice The call will be reverted if the new value of flag is the same as the current one
*/
function setFlag(uint256 flag_) public onlyRole(DEFAULT_ADMIN_ROLE) {
if (flag == flag_) {
diff --git a/contracts/modules/wrapper/core/ERC20BaseModule.sol b/contracts/modules/wrapper/core/ERC20BaseModule.sol
index 0d36177d..ad0ee813 100644
--- a/contracts/modules/wrapper/core/ERC20BaseModule.sol
+++ b/contracts/modules/wrapper/core/ERC20BaseModule.sol
@@ -3,7 +3,6 @@
pragma solidity ^0.8.20;
// required OZ imports here
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../../../openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import "../../../libraries/Errors.sol";
@@ -11,6 +10,7 @@ abstract contract ERC20BaseModule is ERC20Upgradeable {
/* Events */
/**
* @notice Emitted when the specified `spender` spends the specified `value` tokens owned by the specified `owner` reducing the corresponding allowance.
+ * @dev The allowance can be also "spend" with the function BurnFrom, but in this case, the emitted event is BurnFrom.
*/
event Spend(address indexed owner, address indexed spender, uint256 value);
@@ -65,14 +65,11 @@ abstract contract ERC20BaseModule is ERC20Upgradeable {
if (bool(tos.length != values.length)) {
revert Errors.CMTAT_ERC20BaseModule_TosValueslengthMismatch();
}
-
- for (uint256 i = 0; i < tos.length; ) {
+ // No need of unchecked block since Soliditiy 0.8.22
+ for (uint256 i = 0; i < tos.length; ++i) {
// We call directly the internal function transfer
// The reason is that the public function adds only the owner address recovery
ERC20Upgradeable._transfer(_msgSender(), tos[i], values[i]);
- unchecked {
- ++i;
- }
}
// not really useful
// Here only to keep the same behaviour as transfer
@@ -101,27 +98,16 @@ abstract contract ERC20BaseModule is ERC20Upgradeable {
}
/**
- * @notice Allows `spender` to withdraw from your account multiple times, up to the `value` amount
- * @dev see {OpenZeppelin ERC20 - approve}
- */
- function approve(
- address spender,
- uint256 value,
- uint256 currentAllowance
- ) public virtual returns (bool) {
- address owner = _msgSender();
- uint256 currentAllowanceFromSmartContract = allowance(owner, spender);
- if (currentAllowanceFromSmartContract != currentAllowance) {
- revert Errors.CMTAT_ERC20BaseModule_WrongAllowance(
- spender,
- currentAllowanceFromSmartContract,
- currentAllowance
- );
+ * @param addresses list of address to know their balance
+ * @return balances ,totalSupply array with balance for each address, totalSupply
+ * @dev useful for the snapshot rule
+ */
+ function balanceInfo(address[] calldata addresses) public view returns(uint256[] memory balances , uint256 totalSupply) {
+ balances = new uint256[](addresses.length);
+ for(uint256 i = 0; i < addresses.length; ++i){
+ balances[i] = ERC20Upgradeable.balanceOf(addresses[i]);
}
- // We call directly the internal function _approve
- // The reason is that the public function adds only the owner address recovery
- ERC20Upgradeable._approve(owner, spender, value);
- return true;
+ totalSupply = ERC20Upgradeable.totalSupply();
}
uint256[50] private __gap;
diff --git a/contracts/modules/wrapper/core/ERC20BurnModule.sol b/contracts/modules/wrapper/core/ERC20BurnModule.sol
index ce4e36a6..b1482e9c 100644
--- a/contracts/modules/wrapper/core/ERC20BurnModule.sol
+++ b/contracts/modules/wrapper/core/ERC20BurnModule.sol
@@ -3,15 +3,19 @@
pragma solidity ^0.8.20;
import "../../../../openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../security/AuthorizationModule.sol";
-
-abstract contract ERC20BurnModule is ERC20Upgradeable, AuthorizationModule {
+import "../../../interfaces/ICCIPToken.sol";
+abstract contract ERC20BurnModule is ERC20Upgradeable, ICCIPBurnFromERC20, AuthorizationModule {
+ bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
+ bytes32 public constant BURNER_FROM_ROLE = keccak256("BURNER_FROM_ROLE");
/**
- * @notice Emitted when the specified `value` amount of tokens owned by `owner`are destroyed with the given `reason`
- */
+ * @notice Emitted when the specified `value` amount of tokens owned by `owner`are destroyed with the given `reason`
+ */
event Burn(address indexed owner, uint256 value, string reason);
-
+ /**
+ * @notice Emitted when the specified `spender` burns the specified `value` tokens owned by the specified `owner` reducing the corresponding allowance.
+ */
+ event BurnFrom(address indexed owner, address indexed spender, uint256 value);
function __ERC20BurnModule_init_unchained() internal onlyInitializing {
// no variable to initialize
}
@@ -25,7 +29,7 @@ abstract contract ERC20BurnModule is ERC20Upgradeable, AuthorizationModule {
* Requirements:
* - the caller must have the `BURNER_ROLE`.
*/
- function forceBurn(
+ function burn(
address account,
uint256 value,
string calldata reason
@@ -34,9 +38,10 @@ abstract contract ERC20BurnModule is ERC20Upgradeable, AuthorizationModule {
emit Burn(account, value, reason);
}
+
/**
*
- * @notice batch version of {forceBurn}.
+ * @notice batch version of {burn}.
* @dev
* See {ERC20-_burn} and {OpenZeppelin ERC1155_burnBatch}.
*
@@ -48,7 +53,7 @@ abstract contract ERC20BurnModule is ERC20Upgradeable, AuthorizationModule {
* - `accounts` and `values` must have the same length
* - the caller must have the `BURNER_ROLE`.
*/
- function forceBurnBatch(
+ function burnBatch(
address[] calldata accounts,
uint256[] calldata values,
string calldata reason
@@ -61,15 +66,49 @@ abstract contract ERC20BurnModule is ERC20Upgradeable, AuthorizationModule {
if (bool(accounts.length != values.length)) {
revert Errors.CMTAT_BurnModule_AccountsValueslengthMismatch();
}
-
- for (uint256 i = 0; i < accounts.length; ) {
+ // No need of unchecked block since Soliditiy 0.8.22
+ for (uint256 i = 0; i < accounts.length; ++i ) {
_burn(accounts[i], values[i]);
emit Burn(accounts[i], values[i], reason);
- unchecked {
- ++i;
- }
}
}
+ /**
+ * @notice Destroys `amount` tokens from `account`, deducting from the caller's
+ * allowance.
+ * @dev
+ * Can be used to authorize a bridge (e.g. CCIP) to burn token owned by the bridge
+ * No string parameter reason to be compatible with Bridge, e.g. CCIP
+ *
+ * See {ERC20-_burn} and {ERC20-allowance}.
+ *
+ * Requirements:
+ *
+ * - the caller must have allowance for ``accounts``'s tokens of at least
+ * `value`.
+ */
+ function burnFrom(address account, uint256 value)
+ public
+ onlyRole(BURNER_FROM_ROLE)
+ {
+ // Allowance check
+ address sender = _msgSender();
+ uint256 currentAllowance = allowance(account, sender);
+ if(currentAllowance < value){
+ // ERC-6093
+ revert ERC20InsufficientAllowance(sender, currentAllowance, value);
+ }
+ // Update allowance
+ unchecked {
+ _approve(account, sender, currentAllowance - value);
+ }
+ // burn
+ _burn(account, value);
+ // We also emit a burn event since its a burn operation
+ emit Burn(account, value, "burnFrom");
+ // Specific event for the operation
+ emit BurnFrom(account, sender, value);
+ }
+
uint256[50] private __gap;
}
diff --git a/contracts/modules/wrapper/core/ERC20MintModule.sol b/contracts/modules/wrapper/core/ERC20MintModule.sol
index 3744a4f3..3fdafc82 100644
--- a/contracts/modules/wrapper/core/ERC20MintModule.sol
+++ b/contracts/modules/wrapper/core/ERC20MintModule.sol
@@ -3,10 +3,11 @@
pragma solidity ^0.8.20;
import "../../../../openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../security/AuthorizationModule.sol";
-
-abstract contract ERC20MintModule is ERC20Upgradeable, AuthorizationModule {
+import "../../../interfaces/ICCIPToken.sol";
+abstract contract ERC20MintModule is ERC20Upgradeable, ICCIPMintERC20, AuthorizationModule {
+ // MintModule
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
/**
* @notice Emitted when the specified `value` amount of new tokens are created and
* allocated to the specified `account`.
@@ -19,6 +20,8 @@ abstract contract ERC20MintModule is ERC20Upgradeable, AuthorizationModule {
/**
* @notice Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0)
+ * @param account token receiver
+ * @param value amount of tokens
* @dev
* See {OpenZeppelin ERC20-_mint}.
* Emits a {Mint} event.
@@ -60,13 +63,10 @@ abstract contract ERC20MintModule is ERC20Upgradeable, AuthorizationModule {
if (bool(accounts.length != values.length)) {
revert Errors.CMTAT_MintModule_AccountsValueslengthMismatch();
}
-
- for (uint256 i = 0; i < accounts.length; ) {
+ // No need of unchecked block since Soliditiy 0.8.22
+ for (uint256 i = 0; i < accounts.length; ++i ) {
_mint(accounts[i], values[i]);
emit Mint(accounts[i], values[i]);
- unchecked {
- ++i;
- }
}
}
diff --git a/contracts/modules/wrapper/core/EnforcementModule.sol b/contracts/modules/wrapper/core/EnforcementModule.sol
index 739cadf4..80562be5 100644
--- a/contracts/modules/wrapper/core/EnforcementModule.sol
+++ b/contracts/modules/wrapper/core/EnforcementModule.sol
@@ -14,11 +14,13 @@ abstract contract EnforcementModule is
EnforcementModuleInternal,
AuthorizationModule
{
+ // EnforcementModule
+ bytes32 public constant ENFORCER_ROLE = keccak256("ENFORCER_ROLE");
string internal constant TEXT_TRANSFER_REJECTED_FROM_FROZEN =
- "The address FROM is frozen";
+ "Address FROM is frozen";
string internal constant TEXT_TRANSFER_REJECTED_TO_FROZEN =
- "The address TO is frozen";
+ "Address TO is frozen";
function __EnforcementModule_init_unchained() internal onlyInitializing {
// no variable to initialize
diff --git a/contracts/modules/wrapper/core/PauseModule.sol b/contracts/modules/wrapper/core/PauseModule.sol
index 4d999163..b94b9973 100644
--- a/contracts/modules/wrapper/core/PauseModule.sol
+++ b/contracts/modules/wrapper/core/PauseModule.sol
@@ -3,7 +3,6 @@
pragma solidity ^0.8.20;
import "../../../../openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol";
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../security/AuthorizationModule.sol";
/**
@@ -17,6 +16,8 @@ import "../../security/AuthorizationModule.sol";
* event of a large bug.
*/
abstract contract PauseModule is PausableUpgradeable, AuthorizationModule {
+ // PauseModule
+ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
string internal constant TEXT_TRANSFER_REJECTED_PAUSED =
"All transfers paused";
bool private isDeactivated;
diff --git a/contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol b/contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol
index 6b332a8e..1c878936 100644
--- a/contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol
+++ b/contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol
@@ -15,6 +15,9 @@ abstract contract CreditEventsModule is
ContextUpgradeable,
AuthorizationModule
{
+ // CreditEvents
+ bytes32 public constant DEBT_CREDIT_EVENT_ROLE =
+ keccak256("DEBT_CREDIT_EVENT_ROLE");
CreditEvents public creditEvents;
/* Events */
diff --git a/contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol b/contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol
index 1fbfd8d8..62117776 100644
--- a/contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol
+++ b/contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol
@@ -15,6 +15,8 @@ abstract contract DebtBaseModule is
ContextUpgradeable,
AuthorizationModule
{
+ // DebtModule
+ bytes32 public constant DEBT_ROLE = keccak256("DEBT_ROLE");
DebtBase public debt;
/* Events */
@@ -59,9 +61,9 @@ abstract contract DebtBaseModule is
// no variable to initialize
}
- /*
- @notice Set all attributes of debt
- The values of all attributes will be changed even if the new values are the same as the current ones
+ /**
+ * @notice Set all attributes of debt
+ * The values of all attributes will be changed even if the new values are the same as the current ones
*/
function setDebt(DebtBase calldata debt_) public onlyRole(DEBT_ROLE) {
debt = debt_;
@@ -96,8 +98,8 @@ abstract contract DebtBaseModule is
emit CouponFrequency(debt_.couponFrequency, debt_.couponFrequency);
}
- /*
- @notice The call will be reverted if the new value of interestRate is the same as the current one
+ /**
+ * @notice The call will be reverted if the new value of interestRate is the same as the current one
*/
function setInterestRate(uint256 interestRate_) public onlyRole(DEBT_ROLE) {
if (interestRate_ == debt.interestRate) {
@@ -107,8 +109,8 @@ abstract contract DebtBaseModule is
emit InterestRate(interestRate_);
}
- /*
- @notice The call will be reverted if the new value of parValue is the same as the current one
+ /**
+ * @notice The call will be reverted if the new value of parValue is the same as the current one
*/
function setParValue(uint256 parValue_) public onlyRole(DEBT_ROLE) {
if (parValue_ == debt.parValue) {
@@ -118,8 +120,8 @@ abstract contract DebtBaseModule is
emit ParValue(parValue_);
}
- /*
- @notice The Guarantor will be changed even if the new value is the same as the current one
+ /**
+ * @notice The Guarantor will be changed even if the new value is the same as the current one
*/
function setGuarantor(
string calldata guarantor_
@@ -128,8 +130,8 @@ abstract contract DebtBaseModule is
emit Guarantor(guarantor_, guarantor_);
}
- /*
- @notice The bonHolder will be changed even if the new value is the same as the current one
+ /**
+ * @notice The bonHolder will be changed even if the new value is the same as the current one
*/
function setBondHolder(
string calldata bondHolder_
@@ -138,8 +140,8 @@ abstract contract DebtBaseModule is
emit BondHolder(bondHolder_, bondHolder_);
}
- /*
- @notice The maturityDate will be changed even if the new value is the same as the current one
+ /**
+ * @notice The maturityDate will be changed even if the new value is the same as the current one
*/
function setMaturityDate(
string calldata maturityDate_
@@ -148,8 +150,8 @@ abstract contract DebtBaseModule is
emit MaturityDate(maturityDate_, maturityDate_);
}
- /*
- @notice The interestScheduleFormat will be changed even if the new value is the same as the current one
+ /**
+ * @notice The interestScheduleFormat will be changed even if the new value is the same as the current one
*/
function setInterestScheduleFormat(
string calldata interestScheduleFormat_
@@ -161,8 +163,8 @@ abstract contract DebtBaseModule is
);
}
- /*
- @notice The interestPaymentDate will be changed even if the new value is the same as the current one
+ /**
+ * @notice The interestPaymentDate will be changed even if the new value is the same as the current one
*/
function setInterestPaymentDate(
string calldata interestPaymentDate_
@@ -171,8 +173,8 @@ abstract contract DebtBaseModule is
emit InterestPaymentDate(interestPaymentDate_, interestPaymentDate_);
}
- /*
- @notice The dayCountConvention will be changed even if the new value is the same as the current one
+ /**
+ * @notice The dayCountConvention will be changed even if the new value is the same as the current one
*/
function setDayCountConvention(
string calldata dayCountConvention_
@@ -181,8 +183,8 @@ abstract contract DebtBaseModule is
emit DayCountConvention(dayCountConvention_, dayCountConvention_);
}
- /*
- @notice The businessDayConvention will be changed even if the new value is the same as the current one
+ /**
+ * @notice The businessDayConvention will be changed even if the new value is the same as the current one
*/
function setBusinessDayConvention(
string calldata businessDayConvention_
@@ -194,8 +196,8 @@ abstract contract DebtBaseModule is
);
}
- /*
- @notice The publicHolidayCalendar will be changed even if the new value is the same as the current one
+ /**
+ * @notice The publicHolidayCalendar will be changed even if the new value is the same as the current one
*/
function setPublicHolidaysCalendar(
string calldata publicHolidaysCalendar_
@@ -207,8 +209,8 @@ abstract contract DebtBaseModule is
);
}
- /*
- @notice The issuanceDate will be changed even if the new value is the same as the current one
+ /**
+ * @notice The issuanceDate will be changed even if the new value is the same as the current one
*/
function setIssuanceDate(
string calldata issuanceDate_
@@ -217,8 +219,8 @@ abstract contract DebtBaseModule is
emit IssuanceDate(issuanceDate_, issuanceDate_);
}
- /*
- @notice The couponFrequency will be changed even if the new value is the same as the current one
+ /**
+ * @notice The couponFrequency will be changed even if the new value is the same as the current one
*/
function setCouponFrequency(
string calldata couponFrequency_
diff --git a/contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol b/contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol
index 9c167f35..b7142fb5 100644
--- a/contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol
+++ b/contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol
@@ -2,7 +2,6 @@
pragma solidity ^0.8.20;
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "../../security/AuthorizationModule.sol";
import "../../internal/ERC20SnapshotModuleInternal.sol";
@@ -16,6 +15,8 @@ abstract contract ERC20SnapshotModule is
ERC20SnapshotModuleInternal,
AuthorizationModule
{
+ // SnapshotModule
+ bytes32 public constant SNAPSHOOTER_ROLE = keccak256("SNAPSHOOTER_ROLE");
function __ERC20SnasphotModule_init_unchained() internal onlyInitializing {
// no variable to initialize
}
diff --git a/contracts/modules/wrapper/extensions/MetaTxModule.sol b/contracts/modules/wrapper/extensions/MetaTxModule.sol
index 1ddd0471..3df45f74 100644
--- a/contracts/modules/wrapper/extensions/MetaTxModule.sol
+++ b/contracts/modules/wrapper/extensions/MetaTxModule.sol
@@ -3,7 +3,6 @@
pragma solidity ^0.8.20;
import "../../../../openzeppelin-contracts-upgradeable/contracts/metatx/ERC2771ContextUpgradeable.sol";
-import "../../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
/**
* @dev Meta transaction (gasless) module.
@@ -20,25 +19,5 @@ abstract contract MetaTxModule is ERC2771ContextUpgradeable {
// Nothing to do
}
- function _msgSender()
- internal
- view
- virtual
- override
- returns (address sender)
- {
- return ERC2771ContextUpgradeable._msgSender();
- }
-
- function _msgData()
- internal
- view
- virtual
- override
- returns (bytes calldata)
- {
- return ERC2771ContextUpgradeable._msgData();
- }
-
uint256[50] private __gap;
}
diff --git a/contracts/test/CMTATSnapshot/CMTATSnapshotProxyTest.sol b/contracts/test/CMTATSnapshot/CMTATSnapshotProxyTest.sol
deleted file mode 100644
index 75525862..00000000
--- a/contracts/test/CMTATSnapshot/CMTATSnapshotProxyTest.sol
+++ /dev/null
@@ -1,21 +0,0 @@
-//SPDX-License-Identifier: MPL-2.0
-
-pragma solidity ^0.8.20;
-
-import "./CMTAT_BASE_SnapshotTest.sol";
-
-contract CMTATSnapshotProxyTest is CMTAT_BASE_SnapshotTest {
- /**
- @notice Contract version for the deployment with a proxy
- @param forwarderIrrevocable address of the forwarder, required for the gasless support
- */
- /// @custom:oz-upgrades-unsafe-allow constructor
- constructor(
- address forwarderIrrevocable
- ) MetaTxModule(forwarderIrrevocable) {
- // Disable the possibility to initialize the implementation
- _disableInitializers();
- }
-
- uint256[50] private __gap;
-}
diff --git a/contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol b/contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol
deleted file mode 100644
index 654c6b5d..00000000
--- a/contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol
+++ /dev/null
@@ -1,51 +0,0 @@
-//SPDX-License-Identifier: MPL-2.0
-
-pragma solidity ^0.8.20;
-
-import "./CMTAT_BASE_SnapshotTest.sol";
-
-contract CMTATSnapshotStandaloneTest is CMTAT_BASE_SnapshotTest {
- /**
- @notice Contract version for standalone deployment
- @param forwarderIrrevocable address of the forwarder, required for the gasless support
- @param admin address of the admin of contract (Access Control)
- @param nameIrrevocable name of the token
- @param symbolIrrevocable name of the symbol
- @param tokenId name of the tokenId
- @param terms terms associated with the token
- @param ruleEngine address of the ruleEngine to apply rules to transfers
- @param information additional information to describe the token
- @param flag add information under the form of bit(0, 1)
- */
- /// @custom:oz-upgrades-unsafe-allow constructor
- constructor(
- address forwarderIrrevocable,
- address admin,
- uint48 initialDelayToAcceptAdminRole,
- string memory nameIrrevocable,
- string memory symbolIrrevocable,
- uint8 decimalsIrrevocable,
- string memory tokenId_,
- string memory terms_,
- IERC1404Wrapper ruleEngine_,
- string memory information_,
- uint256 flag_
- ) MetaTxModule(forwarderIrrevocable) {
- // Initialize the contract to avoid front-running
- // Warning : do not initialize the proxy
- initialize(
- admin,
- initialDelayToAcceptAdminRole,
- nameIrrevocable,
- symbolIrrevocable,
- decimalsIrrevocable,
- tokenId_,
- terms_,
- ruleEngine_,
- information_,
- flag_
- );
- }
-
- // No storage gap because the contract is deployed in standalone mode
-}
diff --git a/contracts/test/CMTATSnapshot/CMTAT_BASE_SnapshotTest.sol b/contracts/test/CMTATSnapshot/CMTAT_BASE_SnapshotTest.sol
deleted file mode 100644
index ab588cfc..00000000
--- a/contracts/test/CMTATSnapshot/CMTAT_BASE_SnapshotTest.sol
+++ /dev/null
@@ -1,217 +0,0 @@
-//SPDX-License-Identifier: MPL-2.0
-
-pragma solidity ^0.8.20;
-
-// required OZ imports here
-import "../../../openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
-import "../../../openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol";
-
-import "../../modules/wrapper/core/BaseModule.sol";
-import "../../modules/wrapper/core/ERC20BurnModule.sol";
-import "../../modules/wrapper/core/ERC20MintModule.sol";
-import "../../modules/wrapper/core/EnforcementModule.sol";
-import "../../modules/wrapper/core/ERC20BaseModule.sol";
-import "../../modules/wrapper/core/PauseModule.sol";
-/*
-SnapshotModule:
-Add this import in case you add the SnapshotModule
-*/
-import "../../modules/wrapper/controllers/ValidationModule.sol";
-import "../../modules/wrapper/extensions/ERC20SnapshotModule.sol";
-import "../../modules/wrapper/extensions/MetaTxModule.sol";
-import "../../modules/wrapper/extensions/DebtModule/DebtBaseModule.sol";
-import "../../modules/wrapper/extensions/DebtModule/CreditEventsModule.sol";
-import "../../modules/security/AuthorizationModule.sol";
-
-import "../../libraries/Errors.sol";
-
-abstract contract CMTAT_BASE_SnapshotTest is
- Initializable,
- ContextUpgradeable,
- BaseModule,
- PauseModule,
- ERC20MintModule,
- ERC20BurnModule,
- EnforcementModule,
- ValidationModule,
- MetaTxModule,
- ERC20BaseModule,
- ERC20SnapshotModule,
- DebtBaseModule,
- CreditEventsModule
-{
- /**
- @notice
- initialize the proxy contract
- The calls to this function will revert if the contract was deployed without a proxy
- */
- function initialize(
- address admin,
- uint48 initialDelayToAcceptAdminRole,
- string memory nameIrrevocable,
- string memory symbolIrrevocable,
- uint8 decimalsIrrevocable,
- string memory tokenId_,
- string memory terms_,
- IERC1404Wrapper ruleEngine_,
- string memory information_,
- uint256 flag_
- ) public initializer {
- __CMTAT_init(
- admin,
- initialDelayToAcceptAdminRole,
- nameIrrevocable,
- symbolIrrevocable,
- decimalsIrrevocable,
- tokenId_,
- terms_,
- ruleEngine_,
- information_,
- flag_
- );
- }
-
- /**
- @dev calls the different initialize functions from the different modules
- */
- function __CMTAT_init(
- address admin,
- uint48 initialDelayToAcceptAdminRole,
- string memory nameIrrevocable,
- string memory symbolIrrevocable,
- uint8 decimalsIrrevocable,
- string memory tokenId_,
- string memory terms_,
- IERC1404Wrapper ruleEngine_,
- string memory information_,
- uint256 flag_
- ) internal onlyInitializing {
- /* OpenZeppelin library */
- // OZ init_unchained functions are called firstly due to inheritance
- __Context_init_unchained();
- __ERC20_init_unchained(nameIrrevocable, symbolIrrevocable);
- // AccessControlUpgradeable inherits from ERC165Upgradeable
- __ERC165_init_unchained();
- // AuthorizationModule inherits from AccessControlUpgradeable
- __AccessControl_init_unchained();
- __Pausable_init_unchained();
-
- /* Internal Modules */
- __Enforcement_init_unchained();
- /*
- SnapshotModule:
- Add this call in case you add the SnapshotModule
- */
- __ERC20Snapshot_init_unchained();
-
- __Validation_init_unchained(ruleEngine_);
-
- /* Wrapper */
- // AuthorizationModule_init_unchained is called firstly due to inheritance
- __AuthorizationModule_init_unchained();
- __AccessControlDefaultAdminRules_init_unchained(initialDelayToAcceptAdminRole, admin);
- __ERC20BurnModule_init_unchained();
- __ERC20MintModule_init_unchained();
- // EnforcementModule_init_unchained is called before ValidationModule_init_unchained due to inheritance
- __EnforcementModule_init_unchained();
- __ERC20BaseModule_init_unchained(decimalsIrrevocable);
- // PauseModule_init_unchained is called before ValidationModule_init_unchained due to inheritance
- __PauseModule_init_unchained();
- __ValidationModule_init_unchained();
-
- /*
- SnapshotModule:
- Add this call in case you add the SnapshotModule
- */
- __ERC20SnasphotModule_init_unchained();
-
- /* Other modules */
- __DebtBaseModule_init_unchained();
- __CreditEvents_init_unchained();
- __Base_init_unchained(tokenId_, terms_, information_, flag_);
-
- /* own function */
- __CMTAT_init_unchained();
- }
-
- function __CMTAT_init_unchained() internal onlyInitializing {
- // no variable to initialize
- }
-
- /**
- @notice Returns the number of decimals used to get its user representation.
- */
- function decimals()
- public
- view
- virtual
- override(ERC20Upgradeable, ERC20BaseModule)
- returns (uint8)
- {
- return ERC20BaseModule.decimals();
- }
-
- function transferFrom(
- address sender,
- address recipient,
- uint256 amount
- )
- public
- virtual
- override(ERC20Upgradeable, ERC20BaseModule)
- returns (bool)
- {
- return ERC20BaseModule.transferFrom(sender, recipient, amount);
- }
-
- /*
- @dev
- SnapshotModule:
- - override SnapshotModuleInternal if you add the SnapshotModule
- e.g. override(SnapshotModuleInternal, ERC20Upgradeable)
- - remove the keyword view
- */
- function _update(
- address from,
- address to,
- uint256 amount
- ) internal override(ERC20SnapshotModuleInternal, ERC20Upgradeable) {
- // We call the SnapshotModule only if the transfer is valid
- if (!ValidationModule.validateTransfer(from, to, amount))
- revert Errors.CMTAT_InvalidTransfer(from, to, amount);
- /*
- We do not call ERC20Upgradeable._update(from, to, amount) here because it is called inside the SnapshotModule
- */
- /*
- SnapshotModule:
- Add this call in case you add the SnapshotModule
- */
- ERC20SnapshotModuleInternal._update(from, to, amount);
- }
-
- /**
- @dev This surcharge is not necessary if you do not use the MetaTxModule
- */
- function _msgSender()
- internal
- view
- override(MetaTxModule, ContextUpgradeable)
- returns (address sender)
- {
- return MetaTxModule._msgSender();
- }
-
- /**
- @dev This surcharge is not necessary if you do not use the MetaTxModule
- */
- function _msgData()
- internal
- view
- override(MetaTxModule, ContextUpgradeable)
- returns (bytes calldata)
- {
- return MetaTxModule._msgData();
- }
-
- uint256[50] private __gap;
-}
diff --git a/doc/TOOLCHAIN.md b/doc/TOOLCHAIN.md
index d58ab52e..8d829fab 100644
--- a/doc/TOOLCHAIN.md
+++ b/doc/TOOLCHAIN.md
@@ -176,9 +176,7 @@ Warning:
From the version v2.3.0, this command is not working and generates the following error
-> Failed to convert dot to SVG. Error: lost 31 26 edge
-
-
+> RangeError: Maximum call stack size exceeded
| Description | Command |
| ------------------------------------------------------------ | -------------------------------------- |
@@ -221,6 +219,6 @@ npm run-script coverage
Slither is a Solidity static analysis framework written in Python3
```bash
-slither . --checklist --filter-paths "openzeppelin-contracts-upgradeable|test" > slither-report.md
+slither . --checklist --filter-paths "openzeppelin-contracts-upgradeable|openzeppelin-contracts|@openzeppelin|test" > slither-report.md
```
diff --git a/doc/USAGE.md b/doc/USAGE.md
index b598e578..b0404e66 100644
--- a/doc/USAGE.md
+++ b/doc/USAGE.md
@@ -7,15 +7,15 @@ The instructions below have been tested on Ubuntu 20.04.5 LTS.
The toolchain includes the following components, where the versions
are the latest ones that we tested:
-- npm 8.19.2
+- npm 10.2.5
- Hardhat-web3 2.0.0
- *Truffle 5.9.3 [depreciated]*
-- Solidity 0.8.17 (via solc-js)
-- Node 16.17.0
+- Solidity 0.8.22 (via solc-js)
+- Node 20.5.0
- Web3.js 1.9.0
- OpenZeppelin
- - OpenZeppelin Contracts Upgradeable (submodule) [v5.0.0](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.0.0)
- - OpenZeppelin Contracts (Node.js module) [v5.0.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.0)
+ - OpenZeppelin Contracts Upgradeable (submodule) [v5.0.2](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/releases/tag/v5.0.2)
+ - OpenZeppelin Contracts (Node.js module) [v5.0.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.2)
- Reason n°1: libraries and interfaces are no longer available inside the upgradeable version since the version v5.0.0.
- Reason n°2: It is not installed as a github submodule because it will create conflicts with the imports inside OpenZeppelin which use the Node.js version.
@@ -30,7 +30,7 @@ Clone the git repository, with the option `--recurse-submodules` to fetch the su
- Node.js version
-We recommend to install the [Node Version Manager `nvm`](https://github.com/nvm-sh/nvm) to manage multiple versions of Node.js on your machine. You can then, for example, install the version 16.17.0 of Node.js with the following command: `nvm install 16.17.0`
+We recommend to install the [Node Version Manager `nvm`](https://github.com/nvm-sh/nvm) to manage multiple versions of Node.js on your machine. You can then, for example, install the version 20.5.0 of Node.js with the following command: `nvm install 20.5.0`
The file [.nvmrc](../.nvmrc) at the root of the project set the Node.js version. `nvm use`will automatically use this version if no version is supplied on the command line.
diff --git a/doc/audits/tools/slither-report.md b/doc/audits/tools/slither-report.md
new file mode 100644
index 00000000..ffa903c3
--- /dev/null
+++ b/doc/audits/tools/slither-report.md
@@ -0,0 +1,709 @@
+**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results.
+Summary
+ - [shadowing-state](#shadowing-state) (1 results) (High)
+ - [reentrancy-no-eth](#reentrancy-no-eth) (1 results) (Medium)
+ - [uninitialized-local](#uninitialized-local) (1 results) (Medium)
+ - [missing-zero-check](#missing-zero-check) (2 results) (Low)
+ - [calls-loop](#calls-loop) (4 results) (Low)
+ - [reentrancy-benign](#reentrancy-benign) (3 results) (Low)
+ - [reentrancy-events](#reentrancy-events) (2 results) (Low)
+ - [timestamp](#timestamp) (6 results) (Low)
+ - [costly-loop](#costly-loop) (2 results) (Informational)
+ - [dead-code](#dead-code) (1 results) (Informational)
+ - [solc-version](#solc-version) (1 results) (Informational)
+ - [naming-convention](#naming-convention) (57 results) (Informational)
+## shadowing-state
+Impact: High
+Confidence: High
+ - [ ] ID-0
+ [ERC20SnapshotModuleInternal._scheduledSnapshots](contracts/modules/internal/ERC20SnapshotModuleInternal.sol#L25) shadows:
+ - [SnapshotModuleBase._scheduledSnapshots](contracts/modules/internal/base/SnapshotModuleBase.sol#L62)
+
+contracts/modules/internal/ERC20SnapshotModuleInternal.sol#L25
+
+
+## reentrancy-no-eth
+Impact: Medium
+Confidence: Medium
+ - [ ] ID-1
+ Reentrancy in [CMTAT_BASE.burnAndMint(address,address,uint256,uint256,string)](contracts/modules/CMTAT_BASE.sol#L189-L192):
+ External calls:
+ - [burn(from,amountToBurn,reason)](contracts/modules/CMTAT_BASE.sol#L190)
+ - [ruleEngine.operateOnTransfer(from,to,amount)](contracts/modules/internal/ValidationModuleInternal.sol#L65)
+ - [mint(to,amountToMint)](contracts/modules/CMTAT_BASE.sol#L191)
+ - [ruleEngine.operateOnTransfer(from,to,amount)](contracts/modules/internal/ValidationModuleInternal.sol#L65)
+ State variables written after the call(s):
+ - [mint(to,amountToMint)](contracts/modules/CMTAT_BASE.sol#L191)
+ - [_currentSnapshotIndex = scheduleSnapshotIndex](contracts/modules/internal/base/SnapshotModuleBase.sol#L328)
+ [SnapshotModuleBase._currentSnapshotIndex](contracts/modules/internal/base/SnapshotModuleBase.sol#L55) can be used in cross function reentrancies:
+ - [SnapshotModuleBase._findScheduledMostRecentPastSnapshot()](contracts/modules/internal/base/SnapshotModuleBase.sol#L375-L402)
+ - [SnapshotModuleBase._setCurrentSnapshot()](contracts/modules/internal/base/SnapshotModuleBase.sol#L321-L330)
+ - [mint(to,amountToMint)](contracts/modules/CMTAT_BASE.sol#L191)
+ - [_currentSnapshotTime = scheduleSnapshotTime](contracts/modules/internal/base/SnapshotModuleBase.sol#L327)
+ [SnapshotModuleBase._currentSnapshotTime](contracts/modules/internal/base/SnapshotModuleBase.sol#L53) can be used in cross function reentrancies:
+ - [SnapshotModuleBase._setCurrentSnapshot()](contracts/modules/internal/base/SnapshotModuleBase.sol#L321-L330)
+ - [SnapshotModuleBase._updateSnapshot(SnapshotModuleBase.Snapshots,uint256)](contracts/modules/internal/base/SnapshotModuleBase.sol#L305-L314)
+ - [SnapshotModuleBase.getNextSnapshots()](contracts/modules/internal/base/SnapshotModuleBase.sol#L82-L111)
+
+contracts/modules/CMTAT_BASE.sol#L189-L192
+
+## uninitialized-local
+
+> The concerned variable local `mostRecent` is initialized in the loop
+
+Impact: Medium
+Confidence: Medium
+
+ - [ ] ID-2
+[SnapshotModuleBase._findScheduledMostRecentPastSnapshot().mostRecent](contracts/modules/internal/base/SnapshotModuleBase.sol#L389) is a local variable never initialized
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L389
+
+## missing-zero-check
+
+> Mock: not intended to be used in production
+
+Impact: Low
+Confidence: Medium
+
+
+ - [ ] ID-4
+ [AuthorizationEngineMock.authorizeAdminChange(address).newAdmin](contracts/mocks/AuthorizationEngineMock.sol#L21) lacks a zero-check on :
+ - [nextAdmin = newAdmin](contracts/mocks/AuthorizationEngineMock.sol#L22)
+
+contracts/mocks/AuthorizationEngineMock.sol#L21
+
+## calls-loop
+
+>Mock: not intended to be used in production
+>ValidationModuleInternal: the loop happens only for batch function. A relevant alternative could be the creation of a batch function for the RuleEngine, but for the moment we don't have an implemented solution.
+
+Impact: Low
+Confidence: Medium
+
+ - [ ] ID-5
+[RuleEngineMock.messageForTransferRestriction(uint8)](contracts/mocks/RuleEngine/RuleEngineMock.sol#L83-L97) has external calls inside a loop: [_rules[i].canReturnTransferRestrictionCode(_restrictionCode)](contracts/mocks/RuleEngine/RuleEngineMock.sol#L88)
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L83-L97
+
+
+ - [ ] ID-6
+[RuleEngineMock.messageForTransferRestriction(uint8)](contracts/mocks/RuleEngine/RuleEngineMock.sol#L83-L97) has external calls inside a loop: [_rules[i].messageForTransferRestriction(_restrictionCode)](contracts/mocks/RuleEngine/RuleEngineMock.sol#L89-L90)
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L83-L97
+
+
+ - [ ] ID-7
+[ValidationModuleInternal._operateOnTransfer(address,address,uint256)](contracts/modules/internal/ValidationModuleInternal.sol#L64-L66) has external calls inside a loop: [ruleEngine.operateOnTransfer(from,to,amount)](contracts/modules/internal/ValidationModuleInternal.sol#L65)
+
+contracts/modules/internal/ValidationModuleInternal.sol#L64-L66
+
+
+ - [ ] ID-8
+[RuleEngineMock.detectTransferRestriction(address,address,uint256)](contracts/mocks/RuleEngine/RuleEngineMock.sol#L39-L59) has external calls inside a loop: [restriction = _rules[i].detectTransferRestriction(_from,_to,_amount)](contracts/mocks/RuleEngine/RuleEngineMock.sol#L46-L50)
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L39-L59
+
+## reentrancy-benign
+
+> Factory contract : It is not a security issue since only authorized user can call the function
+> CMTAT_BASE._update: the contract called is a trusted contract (RuleEngine)
+
+
+
+Impact: Low
+Confidence: Medium
+ - [ ] ID-9
+ Reentrancy in [CMTAT_BEACON_FACTORY.deployCMTAT(address,IAuthorizationEngine,string,string,uint8,string,string,IRuleEngine,string,uint256)](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L41-L75):
+ External calls:
+ - [cmtat = new BeaconProxy(address(beacon),abi.encodeWithSelector(CMTAT_PROXY(address(0)).initialize.selector,admin,authorizationEngineIrrevocable,nameIrrevocable,symbolIrrevocable,decimalsIrrevocable,tokenId_,terms_,ruleEngine_,information_,flag_))](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L54-L69)
+ State variables written after the call(s):
+ - [cmtatCounterId ++](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L72)
+ - [cmtats[cmtatCounterId] = address(cmtat)](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L70)
+ - [cmtatsList.push(address(cmtat))](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L73)
+
+contracts/deployment/CMTAT_BEACON_FACTORY.sol#L41-L75
+
+
+ - [ ] ID-10
+ Reentrancy in [CMTAT_TP_FACTORY.deployCMTAT(address,address,IAuthorizationEngine,string,string,uint8,string,string,IRuleEngine,string,uint256)](contracts/deployment/CMTAT_TP_FACTORY.sol#L32-L68):
+ External calls:
+ - [cmtat = new TransparentUpgradeableProxy(logic,proxyAdminOwner,abi.encodeWithSelector(CMTAT_PROXY(address(0)).initialize.selector,admin,authorizationEngineIrrevocable,nameIrrevocable,symbolIrrevocable,decimalsIrrevocable,tokenId_,terms_,ruleEngine_,information_,flag_))](contracts/deployment/CMTAT_TP_FACTORY.sol#L46-L62)
+ State variables written after the call(s):
+ - [cmtatID ++](contracts/deployment/CMTAT_TP_FACTORY.sol#L65)
+ - [cmtats[cmtatID] = address(cmtat)](contracts/deployment/CMTAT_TP_FACTORY.sol#L63)
+ - [cmtatsList.push(address(cmtat))](contracts/deployment/CMTAT_TP_FACTORY.sol#L66)
+
+contracts/deployment/CMTAT_TP_FACTORY.sol#L32-L68
+
+
+ - [ ] ID-11
+ Reentrancy in [CMTAT_BASE._update(address,address,uint256)](contracts/modules/CMTAT_BASE.sol#L198-L213):
+ External calls:
+ - [! ValidationModule._operateOnTransfer(from,to,amount)](contracts/modules/CMTAT_BASE.sol#L203)
+ - [ruleEngine.operateOnTransfer(from,to,amount)](contracts/modules/internal/ValidationModuleInternal.sol#L65)
+ State variables written after the call(s):
+ - [ERC20SnapshotModuleInternal._snapshotUpdate(from,to)](contracts/modules/CMTAT_BASE.sol#L211)
+ - [_currentSnapshotIndex = scheduleSnapshotIndex](contracts/modules/internal/base/SnapshotModuleBase.sol#L328)
+ - [ERC20SnapshotModuleInternal._snapshotUpdate(from,to)](contracts/modules/CMTAT_BASE.sol#L211)
+ - [_currentSnapshotTime = scheduleSnapshotTime](contracts/modules/internal/base/SnapshotModuleBase.sol#L327)
+
+contracts/modules/CMTAT_BASE.sol#L198-L213
+
+## reentrancy-events
+
+> It is not a security issue since only authorized user can call the function
+
+Impact: Low
+Confidence: Medium
+ - [ ] ID-12
+ Reentrancy in [CMTAT_TP_FACTORY.deployCMTAT(address,address,IAuthorizationEngine,string,string,uint8,string,string,IRuleEngine,string,uint256)](contracts/deployment/CMTAT_TP_FACTORY.sol#L32-L68):
+ External calls:
+ - [cmtat = new TransparentUpgradeableProxy(logic,proxyAdminOwner,abi.encodeWithSelector(CMTAT_PROXY(address(0)).initialize.selector,admin,authorizationEngineIrrevocable,nameIrrevocable,symbolIrrevocable,decimalsIrrevocable,tokenId_,terms_,ruleEngine_,information_,flag_))](contracts/deployment/CMTAT_TP_FACTORY.sol#L46-L62)
+ Event emitted after the call(s):
+ - [CMTAT(address(cmtat),cmtatID)](contracts/deployment/CMTAT_TP_FACTORY.sol#L64)
+
+contracts/deployment/CMTAT_TP_FACTORY.sol#L32-L68
+
+
+ - [ ] ID-13
+ Reentrancy in [CMTAT_BEACON_FACTORY.deployCMTAT(address,IAuthorizationEngine,string,string,uint8,string,string,IRuleEngine,string,uint256)](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L41-L75):
+ External calls:
+ - [cmtat = new BeaconProxy(address(beacon),abi.encodeWithSelector(CMTAT_PROXY(address(0)).initialize.selector,admin,authorizationEngineIrrevocable,nameIrrevocable,symbolIrrevocable,decimalsIrrevocable,tokenId_,terms_,ruleEngine_,information_,flag_))](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L54-L69)
+ Event emitted after the call(s):
+ - [CMTAT(address(cmtat),cmtatCounterId)](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L71)
+
+contracts/deployment/CMTAT_BEACON_FACTORY.sol#L41-L75
+
+## timestamp
+
+> With the Proof of Work, it was possible for a miner to modify the timestamp in a range of about 15 seconds
+>
+> With the Proof Of Stake, a new block is created every 12 seconds
+>
+> In all cases, we are not looking for such precision
+
+Impact: Low
+Confidence: Medium
+ - [ ] ID-14
+ [SnapshotModuleBase._scheduleSnapshotNotOptimized(uint256)](contracts/modules/internal/base/SnapshotModuleBase.sol#L149-L177) uses timestamp for comparisons
+ Dangerous comparisons:
+ - [time <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L150)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L149-L177
+
+
+ - [ ] ID-15
+ [SnapshotModuleBase._rescheduleSnapshot(uint256,uint256)](contracts/modules/internal/base/SnapshotModuleBase.sol#L182-L223) uses timestamp for comparisons
+ Dangerous comparisons:
+ - [oldTime <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L184)
+ - [newTime <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L187)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L182-L223
+
+
+ - [ ] ID-16
+ [SnapshotModuleBase._unscheduleSnapshotNotOptimized(uint256)](contracts/modules/internal/base/SnapshotModuleBase.sol#L250-L263) uses timestamp for comparisons
+ Dangerous comparisons:
+ - [time <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L251)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L250-L263
+
+
+ - [ ] ID-17
+ [SnapshotModuleBase._scheduleSnapshot(uint256)](contracts/modules/internal/base/SnapshotModuleBase.sol#L118-L144) uses timestamp for comparisons
+ Dangerous comparisons:
+ - [time <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L120)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L118-L144
+
+
+ - [ ] ID-18
+ [SnapshotModuleBase._unscheduleLastSnapshot(uint256)](contracts/modules/internal/base/SnapshotModuleBase.sol#L228-L242) uses timestamp for comparisons
+ Dangerous comparisons:
+ - [time <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L230)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L228-L242
+
+
+ - [ ] ID-19
+ [SnapshotModuleBase._findScheduledMostRecentPastSnapshot()](contracts/modules/internal/base/SnapshotModuleBase.sol#L375-L402) uses timestamp for comparisons
+ Dangerous comparisons:
+ - [_scheduledSnapshots[i] <= block.timestamp](contracts/modules/internal/base/SnapshotModuleBase.sol#L393)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L375-L402
+
+## costly-loop
+
+> Inside the function, these two operations are not performed inside a loop.
+>
+> It seems that the only loops which calls`setCurrentSnapshot`are inside the batch functions(mintBatch, burnBatch, ...) through a call to the function update.
+> At the moment, there is no trivial solution to resolve this.
+
+Impact: Informational
+Confidence: Medium
+ - [ ] ID-20
+ [SnapshotModuleBase._setCurrentSnapshot()](contracts/modules/internal/base/SnapshotModuleBase.sol#L321-L330) has costly operations inside a loop:
+ - [_currentSnapshotTime = scheduleSnapshotTime](contracts/modules/internal/base/SnapshotModuleBase.sol#L327)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L321-L330
+
+
+ - [ ] ID-21
+ [SnapshotModuleBase._setCurrentSnapshot()](contracts/modules/internal/base/SnapshotModuleBase.sol#L321-L330) has costly operations inside a loop:
+ - [_currentSnapshotIndex = scheduleSnapshotIndex](contracts/modules/internal/base/SnapshotModuleBase.sol#L328)
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L321-L330
+
+## dead-code
+
+> - Implemented to be gasless compatible (see MetaTxModule)
+>
+> - If we remove this function, we will have the following error:
+>
+> "Derived contract must override function "_msgData". Two or more base classes define function with same name and parameter types."
+
+Impact: Informational
+Confidence: Medium
+
+ - [ ] ID-22
+[CMTAT_BASE._msgData()](contracts/modules/CMTAT_BASE.sol#L240-L247) is never used and should be removed
+
+contracts/modules/CMTAT_BASE.sol#L240-L247
+
+## solc-version
+
+> The version set in the config file is 0.8.22
+
+Impact: Informational
+Confidence: High
+ - [ ] ID-23
+ Version constraint ^0.8.20 contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)
+ - VerbatimInvalidDeduplication
+ - FullInlinerNonExpressionSplitArgumentEvaluationOrder
+ - MissingSideEffectsOnSelectorAccess.
+ It is used by:
+ - node_modules/@openzeppelin/contracts/access/AccessControl.sol#4
+ - node_modules/@openzeppelin/contracts/access/IAccessControl.sol#4
+ - node_modules/@openzeppelin/contracts/access/Ownable.sol#4
+ - node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol#4
+ - node_modules/@openzeppelin/contracts/interfaces/IERC5267.sol#4
+ - node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol#3
+ - node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/Proxy.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol#4
+ - node_modules/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol#4
+ - node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol#4
+ - node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol#4
+ - node_modules/@openzeppelin/contracts/utils/Address.sol#4
+ - node_modules/@openzeppelin/contracts/utils/Arrays.sol#4
+ - node_modules/@openzeppelin/contracts/utils/Context.sol#4
+ - node_modules/@openzeppelin/contracts/utils/StorageSlot.sol#5
+ - node_modules/@openzeppelin/contracts/utils/Strings.sol#4
+ - node_modules/@openzeppelin/contracts/utils/cryptography/ECDSA.sol#4
+ - node_modules/@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol#4
+ - node_modules/@openzeppelin/contracts/utils/introspection/ERC165.sol#4
+ - node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol#4
+ - node_modules/@openzeppelin/contracts/utils/math/Math.sol#4
+ - node_modules/@openzeppelin/contracts/utils/math/SignedMath.sol#4
+ - contracts/CMTAT_PROXY.sol#3
+ - contracts/CMTAT_STANDALONE.sol#3
+ - contracts/deployment/CMTAT_BEACON_FACTORY.sol#2
+ - contracts/deployment/CMTAT_TP_FACTORY.sol#2
+ - contracts/interfaces/ICCIPToken.sol#3
+ - contracts/interfaces/ICMTATSnapshot.sol#3
+ - contracts/interfaces/IDebtGlobal.sol#3
+ - contracts/interfaces/draft-IERC1404/draft-IERC1404.sol#3
+ - contracts/interfaces/draft-IERC1404/draft-IERC1404EnumCode.sol#3
+ - contracts/interfaces/draft-IERC1404/draft-IERC1404Wrapper.sol#3
+ - contracts/interfaces/engine/IAuthorizationEngine.sol#3
+ - contracts/interfaces/engine/IRuleEngine.sol#3
+ - contracts/libraries/Errors.sol#3
+ - contracts/mocks/AuthorizationEngineMock.sol#3
+ - contracts/mocks/MinimalForwarderMock.sol#3
+ - contracts/mocks/RuleEngine/CodeList.sol#3
+ - contracts/mocks/RuleEngine/RuleEngineMock.sol#3
+ - contracts/mocks/RuleEngine/RuleMock.sol#3
+ - contracts/mocks/RuleEngine/interfaces/IRule.sol#3
+ - contracts/mocks/RuleEngine/interfaces/IRuleEngineMock.sol#3
+ - contracts/modules/CMTAT_BASE.sol#3
+ - contracts/modules/internal/ERC20SnapshotModuleInternal.sol#3
+ - contracts/modules/internal/EnforcementModuleInternal.sol#3
+ - contracts/modules/internal/ValidationModuleInternal.sol#3
+ - contracts/modules/internal/base/SnapshotModuleBase.sol#3
+ - contracts/modules/security/AuthorizationModule.sol#3
+ - contracts/modules/wrapper/controllers/ValidationModule.sol#3
+ - contracts/modules/wrapper/core/BaseModule.sol#3
+ - contracts/modules/wrapper/core/ERC20BaseModule.sol#3
+ - contracts/modules/wrapper/core/ERC20BurnModule.sol#3
+ - contracts/modules/wrapper/core/ERC20MintModule.sol#3
+ - contracts/modules/wrapper/core/EnforcementModule.sol#3
+ - contracts/modules/wrapper/core/PauseModule.sol#3
+ - contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol#3
+ - contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol#3
+ - contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol#3
+ - contracts/modules/wrapper/extensions/MetaTxModule.sol#3
+ - contracts/test/proxy/CMTAT_PROXY.sol#3
+ - openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/metatx/ERC2771ContextUpgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/metatx/ERC2771ForwarderUpgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/utils/NoncesUpgradeable.sol#3
+ - openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol#4
+ - openzeppelin-contracts-upgradeable/contracts/utils/introspection/ERC165Upgradeable.sol#4
+
+## naming-convention
+
+> It is not really necessary to rename all the variables. It will generate a lot of work for a minor improvement.
+
+Impact: Informational
+Confidence: High
+
+ - [ ] ID-24
+Enum [IERC1404EnumCode.REJECTED_CODE_BASE](contracts/interfaces/draft-IERC1404/draft-IERC1404EnumCode.sol#L9-L14) is not in CapWords
+
+contracts/interfaces/draft-IERC1404/draft-IERC1404EnumCode.sol#L9-L14
+
+
+ - [ ] ID-25
+Variable [CreditEventsModule.__gap](contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol#L83) is not in mixedCase
+
+contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol#L83
+
+
+ - [ ] ID-26
+Parameter [RuleEngineMock.detectTransferRestriction(address,address,uint256)._amount](contracts/mocks/RuleEngine/RuleEngineMock.sol#L42) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L42
+
+
+ - [ ] ID-27
+Contract [CMTAT_PROXY](contracts/CMTAT_PROXY.sol#L7-L21) is not in CapWords
+
+contracts/CMTAT_PROXY.sol#L7-L21
+
+
+ - [ ] ID-28
+Function [PauseModule.__PauseModule_init_unchained()](contracts/modules/wrapper/core/PauseModule.sol#L26-L28) is not in mixedCase
+
+contracts/modules/wrapper/core/PauseModule.sol#L26-L28
+
+
+ - [ ] ID-29
+Function [CreditEventsModule.__CreditEvents_init_unchained()](contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol#L28-L30) is not in mixedCase
+
+contracts/modules/wrapper/extensions/DebtModule/CreditEventsModule.sol#L28-L30
+
+
+ - [ ] ID-30
+Variable [CMTAT_BASE.__gap](contracts/modules/CMTAT_BASE.sol#L249) is not in mixedCase
+
+contracts/modules/CMTAT_BASE.sol#L249
+
+
+ - [ ] ID-31
+Function [EnforcementModuleInternal.__Enforcement_init_unchained()](contracts/modules/internal/EnforcementModuleInternal.sol#L40-L42) is not in mixedCase
+
+contracts/modules/internal/EnforcementModuleInternal.sol#L40-L42
+
+
+ - [ ] ID-32
+Variable [ValidationModuleInternal.__gap](contracts/modules/internal/ValidationModuleInternal.sol#L68) is not in mixedCase
+
+contracts/modules/internal/ValidationModuleInternal.sol#L68
+
+
+ - [ ] ID-33
+Function [ERC20BurnModule.__ERC20BurnModule_init_unchained()](contracts/modules/wrapper/core/ERC20BurnModule.sol#L19-L21) is not in mixedCase
+
+contracts/modules/wrapper/core/ERC20BurnModule.sol#L19-L21
+
+
+ - [ ] ID-34
+Function [ValidationModuleInternal.__Validation_init_unchained(IRuleEngine)](contracts/modules/internal/ValidationModuleInternal.sol#L24-L31) is not in mixedCase
+
+contracts/modules/internal/ValidationModuleInternal.sol#L24-L31
+
+
+ - [ ] ID-35
+Variable [BaseModule.__gap](contracts/modules/wrapper/core/BaseModule.sol#L94) is not in mixedCase
+
+contracts/modules/wrapper/core/BaseModule.sol#L94
+
+
+ - [ ] ID-36
+Variable [ValidationModule.__gap](contracts/modules/wrapper/controllers/ValidationModule.sol#L136) is not in mixedCase
+
+contracts/modules/wrapper/controllers/ValidationModule.sol#L136
+
+
+ - [ ] ID-37
+Variable [EnforcementModule.__gap](contracts/modules/wrapper/core/EnforcementModule.sol#L55) is not in mixedCase
+
+contracts/modules/wrapper/core/EnforcementModule.sol#L55
+
+
+ - [ ] ID-38
+Contract [CMTAT_STANDALONE](contracts/CMTAT_STANDALONE.sol#L7-L53) is not in CapWords
+
+contracts/CMTAT_STANDALONE.sol#L7-L53
+
+
+ - [ ] ID-39
+Variable [DebtBaseModule.__gap](contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol#L232) is not in mixedCase
+
+contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol#L232
+
+
+ - [ ] ID-40
+Variable [AuthorizationModule.__gap](contracts/modules/security/AuthorizationModule.sol#L85) is not in mixedCase
+
+contracts/modules/security/AuthorizationModule.sol#L85
+
+
+ - [ ] ID-41
+Parameter [RuleEngineMock.operateOnTransfer(address,address,uint256)._to](contracts/mocks/RuleEngine/RuleEngineMock.sol#L74) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L74
+
+
+ - [ ] ID-42
+Parameter [RuleEngineMock.validateTransfer(address,address,uint256)._amount](contracts/mocks/RuleEngine/RuleEngineMock.sol#L64) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L64
+
+
+ - [ ] ID-43
+Parameter [RuleEngineMock.operateOnTransfer(address,address,uint256)._from](contracts/mocks/RuleEngine/RuleEngineMock.sol#L73) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L73
+
+
+ - [ ] ID-44
+Parameter [RuleMock.validateTransfer(address,address,uint256)._to](contracts/mocks/RuleEngine/RuleMock.sol#L14) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleMock.sol#L14
+
+
+ - [ ] ID-45
+Parameter [RuleEngineMock.operateOnTransfer(address,address,uint256)._amount](contracts/mocks/RuleEngine/RuleEngineMock.sol#L75) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L75
+
+
+ - [ ] ID-46
+Variable [EnforcementModuleInternal.__gap](contracts/modules/internal/EnforcementModuleInternal.sol#L87) is not in mixedCase
+
+contracts/modules/internal/EnforcementModuleInternal.sol#L87
+
+
+ - [ ] ID-47
+Parameter [RuleMock.canReturnTransferRestrictionCode(uint8)._restrictionCode](contracts/mocks/RuleEngine/RuleMock.sol#L35) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleMock.sol#L35
+
+
+ - [ ] ID-48
+Variable [SnapshotModuleBase.__gap](contracts/modules/internal/base/SnapshotModuleBase.sol#L404) is not in mixedCase
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L404
+
+
+ - [ ] ID-49
+Parameter [RuleEngineMock.validateTransfer(address,address,uint256)._to](contracts/mocks/RuleEngine/RuleEngineMock.sol#L63) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L63
+
+
+ - [ ] ID-50
+Function [BaseModule.__Base_init_unchained(string,string,string,uint256)](contracts/modules/wrapper/core/BaseModule.sol#L40-L50) is not in mixedCase
+
+contracts/modules/wrapper/core/BaseModule.sol#L40-L50
+
+
+ - [ ] ID-51
+Function [AuthorizationModule.__AuthorizationModule_init_unchained(address,IAuthorizationEngine)](contracts/modules/security/AuthorizationModule.sol#L22-L33) is not in mixedCase
+
+contracts/modules/security/AuthorizationModule.sol#L22-L33
+
+
+ - [ ] ID-52
+Parameter [RuleEngineMock.validateTransfer(address,address,uint256)._from](contracts/mocks/RuleEngine/RuleEngineMock.sol#L62) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L62
+
+
+ - [ ] ID-53
+Parameter [RuleMock.validateTransfer(address,address,uint256)._amount](contracts/mocks/RuleEngine/RuleMock.sol#L15) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleMock.sol#L15
+
+
+ - [ ] ID-54
+Variable [PauseModule.__gap](contracts/modules/wrapper/core/PauseModule.sol#L83) is not in mixedCase
+
+contracts/modules/wrapper/core/PauseModule.sol#L83
+
+
+ - [ ] ID-55
+Function [CMTAT_BASE.__CMTAT_init_unchained()](contracts/modules/CMTAT_BASE.sol#L148-L150) is not in mixedCase
+
+contracts/modules/CMTAT_BASE.sol#L148-L150
+
+
+ - [ ] ID-56
+Variable [ERC20SnapshotModule.__gap](contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol#L77) is not in mixedCase
+
+contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol#L77
+
+
+ - [ ] ID-57
+Parameter [RuleMock.detectTransferRestriction(address,address,uint256)._amount](contracts/mocks/RuleEngine/RuleMock.sol#L26) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleMock.sol#L26
+
+
+ - [ ] ID-58
+Function [ERC20BaseModule.__ERC20BaseModule_init_unchained(uint8)](contracts/modules/wrapper/core/ERC20BaseModule.sol#L28-L32) is not in mixedCase
+
+contracts/modules/wrapper/core/ERC20BaseModule.sol#L28-L32
+
+
+ - [ ] ID-59
+Parameter [RuleMock.messageForTransferRestriction(uint8)._restrictionCode](contracts/mocks/RuleEngine/RuleMock.sol#L41) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleMock.sol#L41
+
+
+ - [ ] ID-60
+Function [CMTAT_BASE.__CMTAT_init(address,IAuthorizationEngine,string,string,uint8,string,string,IRuleEngine,string,uint256)](contracts/modules/CMTAT_BASE.sol#L87-L146) is not in mixedCase
+
+contracts/modules/CMTAT_BASE.sol#L87-L146
+
+
+ - [ ] ID-61
+Function [ERC20MintModule.__ERC20MintModule_init_unchained()](contracts/modules/wrapper/core/ERC20MintModule.sol#L17-L19) is not in mixedCase
+
+contracts/modules/wrapper/core/ERC20MintModule.sol#L17-L19
+
+
+ - [ ] ID-62
+Parameter [RuleEngineMock.detectTransferRestriction(address,address,uint256)._to](contracts/mocks/RuleEngine/RuleEngineMock.sol#L41) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L41
+
+
+ - [ ] ID-63
+Variable [MetaTxModule.__gap](contracts/modules/wrapper/extensions/MetaTxModule.sol#L22) is not in mixedCase
+
+contracts/modules/wrapper/extensions/MetaTxModule.sol#L22
+
+
+ - [ ] ID-64
+Function [SnapshotModuleBase.__SnapshotModuleBase_init_unchained()](contracts/modules/internal/base/SnapshotModuleBase.sol#L64-L67) is not in mixedCase
+
+contracts/modules/internal/base/SnapshotModuleBase.sol#L64-L67
+
+
+ - [ ] ID-65
+Variable [ERC20BurnModule.__gap](contracts/modules/wrapper/core/ERC20BurnModule.sol#L113) is not in mixedCase
+
+contracts/modules/wrapper/core/ERC20BurnModule.sol#L113
+
+
+ - [ ] ID-66
+Variable [ERC20BaseModule.__gap](contracts/modules/wrapper/core/ERC20BaseModule.sol#L113) is not in mixedCase
+
+contracts/modules/wrapper/core/ERC20BaseModule.sol#L113
+
+
+ - [ ] ID-67
+Contract [CMTAT_BEACON_FACTORY](contracts/deployment/CMTAT_BEACON_FACTORY.sol#L15-L93) is not in CapWords
+
+contracts/deployment/CMTAT_BEACON_FACTORY.sol#L15-L93
+
+
+ - [ ] ID-68
+Contract [CMTAT_BASE](contracts/modules/CMTAT_BASE.sol#L29-L250) is not in CapWords
+
+contracts/modules/CMTAT_BASE.sol#L29-L250
+
+
+ - [ ] ID-69
+Parameter [RuleEngineMock.messageForTransferRestriction(uint8)._restrictionCode](contracts/mocks/RuleEngine/RuleEngineMock.sol#L84) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L84
+
+
+ - [ ] ID-70
+Variable [ERC20MintModule.__gap](contracts/modules/wrapper/core/ERC20MintModule.sol#L73) is not in mixedCase
+
+contracts/modules/wrapper/core/ERC20MintModule.sol#L73
+
+
+ - [ ] ID-71
+Function [ValidationModule.__ValidationModule_init_unchained()](contracts/modules/wrapper/controllers/ValidationModule.sol#L26-L28) is not in mixedCase
+
+contracts/modules/wrapper/controllers/ValidationModule.sol#L26-L28
+
+
+ - [ ] ID-72
+Function [DebtBaseModule.__DebtBaseModule_init_unchained()](contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol#L60-L62) is not in mixedCase
+
+contracts/modules/wrapper/extensions/DebtModule/DebtBaseModule.sol#L60-L62
+
+
+ - [ ] ID-73
+Parameter [RuleEngineMock.detectTransferRestriction(address,address,uint256)._from](contracts/mocks/RuleEngine/RuleEngineMock.sol#L40) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleEngineMock.sol#L40
+
+
+ - [ ] ID-74
+Function [ERC20SnapshotModule.__ERC20SnasphotModule_init_unchained()](contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol#L20-L22) is not in mixedCase
+
+contracts/modules/wrapper/extensions/ERC20SnapshotModule.sol#L20-L22
+
+
+ - [ ] ID-75
+Variable [ERC20SnapshotModuleInternal.__gap](contracts/modules/internal/ERC20SnapshotModuleInternal.sol#L140) is not in mixedCase
+
+contracts/modules/internal/ERC20SnapshotModuleInternal.sol#L140
+
+
+ - [ ] ID-76
+Function [EnforcementModule.__EnforcementModule_init_unchained()](contracts/modules/wrapper/core/EnforcementModule.sol#L25-L27) is not in mixedCase
+
+contracts/modules/wrapper/core/EnforcementModule.sol#L25-L27
+
+
+ - [ ] ID-77
+Function [ERC20SnapshotModuleInternal.__ERC20Snapshot_init_unchained()](contracts/modules/internal/ERC20SnapshotModuleInternal.sol#L27-L30) is not in mixedCase
+
+contracts/modules/internal/ERC20SnapshotModuleInternal.sol#L27-L30
+
+
+ - [ ] ID-78
+Contract [CMTAT_TP_FACTORY](contracts/deployment/CMTAT_TP_FACTORY.sol#L11-L78) is not in CapWords
+
+contracts/deployment/CMTAT_TP_FACTORY.sol#L11-L78
+
+
+ - [ ] ID-79
+Parameter [RuleMock.validateTransfer(address,address,uint256)._from](contracts/mocks/RuleEngine/RuleMock.sol#L13) is not in mixedCase
+
+contracts/mocks/RuleEngine/RuleMock.sol#L13
+
+
+ - [ ] ID-80
+Variable [CMTAT_PROXY.__gap](contracts/CMTAT_PROXY.sol#L20) is not in mixedCase
+
+contracts/CMTAT_PROXY.sol#L20
+
diff --git a/doc/general/ARCHITECTURE.md b/doc/general/ARCHITECTURE.md
index 6e67a9a3..9dbb66de 100644
--- a/doc/general/ARCHITECTURE.md
+++ b/doc/general/ARCHITECTURE.md
@@ -14,7 +14,9 @@ These contracts inherit from the same base contract
[CMTAT_BASE.sol](../contracts/modules/CMTAT_BASE.sol), which inherits of
the different modules.
-The main schema describing the architecture can be found here: [architecture.pdf](schema/drawio/architecture.pdf)
+The main schema describing the architecture can be found here: [architecture.pdf](../schema/drawio/architecture.pdf)
+
+Small change with the PDF: the snapshotModule is now included by default.
## Schema
diff --git a/doc/general/Engine.md b/doc/general/Engine.md
new file mode 100644
index 00000000..2f1ef62f
--- /dev/null
+++ b/doc/general/Engine.md
@@ -0,0 +1,99 @@
+# Engine
+
+It is possible to add supplementary controls on the CMTAT through two Engines: the `RuleEngine`and the `AuthorizationEngine`
+
+## RuleEngine
+
+The **RuleEngine** is an external contract used to apply transfer restriction to the CMTAT. This contract is **optional** and its address can be left to zero.
+
+This contract acts as a controller and can call different contract rule to apply rule on each transfer.
+
+A possible rule is a whitelist rule where only the address inside the whitelist can perform a transfer.
+
+### Example
+
+In this example, the token holder calls the function `transfer` which triggers a call to the `RuleEngine` and the different rules associated.
+
+![RuleEngine](../../doc/schema/drawio/RuleEngine.png)
+
+
+
+
+
+
+
+### Implementation
+
+A **ruleEngine** contract has to implement the interface `IRuleEngine`, which inherits from the `IERC1404Wrapper`. These two interfaces defines mainly two functions.
+
+- `operateOnTransfer`
+
+```solidity
+function operateOnTransfer(address _from,address _to, uint256 _amount)
+external returns (bool);
+```
+
+- `validateTransfer`
+
+```solidity
+function validateTransfer(address _from,address _to,uint256 _amount)
+external returns (bool);
+```
+
+**Warning**
+
+The `RuleEngine` has to restrict the access of the function `operateOnTransfer` to only the `CMTAT contract`.
+
+### Schema
+
+![Engine-RuleEngine.drawio](../../doc/schema/drawio/Engine-RuleEngine.drawio.png)
+
+
+
+
+
+## AuthorizationEngine
+
+The `authorizationEngine` allows the possibility to add supplementary checks on AccessControl with an optional `AuthorizationEngine`, similar to the RuleEngine.
+Main advantages :
+
+- Allow CMTAT user to add their own check on access control, typically the transfer of adminship, e.g. if this operations requires a multi signature or the authorization of guardians.
+- Reduce the contract code size because theses supplementary checks are made in an external contract, which is good since the size of a contract is limited.
+
+- The` AccessControlDefaultAdminRules` introduces in CMTAT 2.3.1, has been removed because it has a large code size and introduces several restriction, good for security but can be too restrictive. With the `AuthorizationEngine`, it is possible to implement theses restrictions.
+
+### Implementation
+
+An **AuthorizationEngine** contract has to implement the interface `IAuthorizationEngine`. This interface defines mainly twos functions, one to grant a Role and the other one to revole a role.
+
+```solidity
+ /**
+ * @dev Returns true if the operation is authorized, and false otherwise.
+ */
+ function operateOnGrantRole(
+ bytes32 role, address account
+ ) external returns (bool isValid);
+
+ /**
+ * @dev Returns true if the operation is authorized, and false otherwise.
+ */
+ function operateOnRevokeRole(
+ bytes32 role, address account
+ ) external returns (bool isValid);
+
+```
+
+### Schema
+
+
+
+![Engine-AuthorizationEngine.drawio](../../doc/schema/drawio/Engine-AuthorizationEngine.drawio.png)
+
+
+
+### Warning
+
+- Once an `autorizationEngine` is set, it is not possible to update the address of the `AuthorizationEngine`. The goal is to protect against a change by the super-admin, e.g. the possibility to remove the `AuthorizationEngine`, to bypass the different restrictions.
+
+- With a deployment of proxy, the owner of the proxy contract can deactivate these controls by upgrading to a new implementation. To avoid this, the proxy owner should be different of the CMTAT super admin in case you want to use an `AuthorizationEngine`.
+- The `AuthorizationEngine` has to restrict the access of the function `operateOnAuthorization` to only the `CMTAT contract`.
\ No newline at end of file
diff --git a/doc/general/contract-size.png b/doc/general/contract-size.png
index f80d96f7..7f1d997a 100644
Binary files a/doc/general/contract-size.png and b/doc/general/contract-size.png differ
diff --git a/doc/general/test/test.odt b/doc/general/test/archive/test.odt
similarity index 100%
rename from doc/general/test/test.odt
rename to doc/general/test/archive/test.odt
diff --git a/doc/general/test/test.pdf b/doc/general/test/archive/test.pdf
similarity index 100%
rename from doc/general/test/test.pdf
rename to doc/general/test/archive/test.pdf
diff --git a/doc/general/test/coverage/contracts/CMTAT_PROXY.sol.html b/doc/general/test/coverage/contracts/CMTAT_PROXY.sol.html
index 3a25dea1..1f6a919f 100644
--- a/doc/general/test/coverage/contracts/CMTAT_PROXY.sol.html
+++ b/doc/general/test/coverage/contracts/CMTAT_PROXY.sol.html
@@ -80,7 +80,7 @@
* @notice Contract version for standalone deployment
* @param forwarderIrrevocable address of the forwarder, required for the gasless support
* @param admin address of the admin of contract (Access Control)
+ * @param authorizationEngineIrrevocable
* @param nameIrrevocable name of the token
* @param symbolIrrevocable name of the symbol
* @param decimalsIrrevocable number of decimals used to get its user representation, should be 0 to be compliant with the CMTAT specifications.
@@ -171,13 +174,13 @@
//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+/**
+* @notice CCIP Pool with mint
+*/
+interface ICCIPMintERC20 {
+ /// @notice Mints new tokens for a given address.
+ /// @param account The address to mint the new tokens to.
+ /// @param value The number of tokens to be minted.
+ /// @dev this function increases the total supply.
+ function mint(address account, uint256 value) external;
+}
+
+/**
+* @notice CCIP Pool with burnFrom
+*/
+interface ICCIPBurnFromERC20 {
+ /// @notice Burns tokens from a given address..
+ /// @param account The address to burn tokens from.
+ /// @param value The number of tokens to be burned.
+ /// @dev this function decreases the total supply.
+ function burnFrom(address account, uint256 value) external;
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/general/test/coverage/contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol.html b/doc/general/test/coverage/contracts/interfaces/ICMTATSnapshot.sol.html
similarity index 54%
rename from doc/general/test/coverage/contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol.html
rename to doc/general/test/coverage/contracts/interfaces/ICMTATSnapshot.sol.html
index 816f0938..7c285dc9 100644
--- a/doc/general/test/coverage/contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol.html
+++ b/doc/general/test/coverage/contracts/interfaces/ICMTATSnapshot.sol.html
@@ -1,14 +1,14 @@
- Code coverage report for contracts/test/CMTATSnapshot/CMTATSnapshotStandaloneTest.sol
+ Code coverage report for contracts/interfaces/ICMTATSnapshot.sol
-
-
+
+
@@ -16,13 +16,13 @@
pragma solidity ^0.8.20;
-import "./CMTAT_BASE_SnapshotTest.sol";
-
-contract CMTATSnapshotStandaloneTest is CMTAT_BASE_SnapshotTest {
+/**
+* @notice interface to represent a CMTAT with snapshot
+*/
+interface ICMTATSnapshot {
/**
- @notice Contract version for standalone deployment
- @param forwarderIrrevocable address of the forwarder, required for the gasless support
- @param admin address of the admin of contract (Access Control)
- @param nameIrrevocable name of the token
- @param symbolIrrevocable name of the symbol
- @param tokenId name of the tokenId
- @param terms terms associated with the token
- @param ruleEngine address of the ruleEngine to apply rules to transfers
- @param information additional information to describe the token
- @param flag add information under the form of bit(0, 1)
+ * @notice Return the number of tokens owned by the given owner at the time when the snapshot with the given time was created.
+ * @return value stored in the snapshot, or the actual balance if no snapshot
+ */
+ function snapshotBalanceOf(uint256 time,address owner) external view returns (uint256);
+ /**
+ * @dev See {OpenZeppelin - ERC20Snapshot}
+ * Retrieves the total supply at the specified time.
+ * @return value stored in the snapshot, or the actual totalSupply if no snapshot
*/
- /// @custom:oz-upgrades-unsafe-allow constructor
- constructor(
- address forwarderIrrevocable,
- address admin,
- uint48 initialDelayToAcceptAdminRole,
- string memory nameIrrevocable,
- string memory symbolIrrevocable,
- uint8 decimalsIrrevocable,
- string memory tokenId_,
- string memory terms_,
- IERC1404Wrapper ruleEngine_,
- string memory information_,
- uint256 flag_
- ) MetaTxModule(forwarderIrrevocable) {
- // Initialize the contract to avoid front-running
- // Warning : do not initialize the proxy
- initialize(
- admin,
- initialDelayToAcceptAdminRole,
- nameIrrevocable,
- symbolIrrevocable,
- decimalsIrrevocable,
- tokenId_,
- terms_,
- ruleEngine_,
- information_,
- flag_
- );
- }
+ function snapshotTotalSupply(uint256 time) external view returns (uint256);
+ /**
+ * @notice Return snapshotBalanceOf and snapshotTotalSupply to avoid multiple calls
+ * @return ownerBalance , totalSupply - see snapshotBalanceOf and snapshotTotalSupply
+ */
+ function snapshotInfo(uint256 time, address owner) external view returns (uint256 ownerBalance, uint256 totalSupply);
+ /**
+ * @notice Return snapshotBalanceOf for each address in the array and the total supply
+ * @return ownerBalances array with the balance of each address, the total supply
+ */
+ function snapshotInfoBatch(uint256 time, address[] calldata addresses) external view returns (uint256[] memory ownerBalances, uint256 totalSupply);
+
+ /**
+ * @notice Return snapshotBalanceOf for each address in the array and the total supply
+ * @return ownerBalances array with the balance of each address, the total supply
+ */
+ function snapshotInfoBatch(uint256[] calldata times, address[] calldata addresses) external view returns (uint256[][] memory ownerBalances, uint256[] memory totalSupply);
+
- // No storage gap because the contract is deployed in standalone mode
}