diff --git a/CHANGELOG.md b/CHANGELOG.md
index 142f30e..955ece6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -45,7 +45,7 @@ forge lint
- Update surya doc by running the 3 scripts in [./doc/script](./doc/script)
- Update changelog
-### v3.0.0-rc3 - 2026-05-06
+### v3.0.0-rc3 - 2026-05-07
### Security
@@ -54,7 +54,9 @@ forge lint
- Enforce on-chain privilege-separation for rule accounts:
- `RuleEngine.grantRole` now reverts for any role when `account` is currently in the rules set.
- `RuleEngineOwnable` and `RuleEngineOwnable2Step` now reject `transferOwnership` targets that are currently in the rules set.
-- Add T-REX compatibility path for compliance binding operations: `bindToken(token)` / `unbindToken(token)` now allow token self-calls (`msg.sender == token`) in addition to manager/owner authorization.
+- Add T-REX compatibility path for compliance binding operations with admission control:
+ - token self-calls (`msg.sender == token`) for `bindToken(token)` / `unbindToken(token)` are supported only when explicitly approved.
+ - unapproved token self-calls are rejected and still require manager/owner authorization.
### Added
@@ -69,12 +71,27 @@ forge lint
- `RuleEngine`: `DEFAULT_ADMIN_ROLE` can update cap.
- `RuleEngineOwnable` and `RuleEngineOwnable2Step`: owner can update cap.
- Add `RuleEngine_RulesManagementModule_RuleAccountCannotReceivePrivileges()` error for rule-account privilege/ownership target protection.
+- Add token self-binding approval management to `IERC3643ComplianceExtended` / `ERC3643ComplianceExtendedModule`:
+ - `setTokenSelfBindingApproval(address token, bool approved)`
+ - `isTokenSelfBindingApproved(address token)`
+ - `TokenSelfBindingApprovalSet(address indexed token, bool approved)` event.
+- Add batch self-binding approval API in `IERC3643ComplianceExtended` / `ERC3643ComplianceExtendedModule`:
+ - `setTokenSelfBindingApprovalBatch(address[] tokens, bool approved)`.
+ - `TokenSelfBindingApprovalBatchSet(address[] tokens, bool approved)` event.
### Changed
- Ownable variants now rely on OpenZeppelin `ERC165` inheritance in `RuleEngineOwnableShared` for base ERC-165 advertisement and extend it with RuleEngine + ERC-173 interface IDs.
- `supportsInterface` advertisement now explicitly includes `IERC1404` in addition to `IERC1404Extend`.
- `RuleEngineOwnable2Step.supportsInterface` now advertises the Ownable2Step-specific interface ID in addition to inherited RuleEngine/Ownable interfaces.
+- `ERC3643ComplianceModule` authorization logic now requires explicit per-token approval for token-driven self-bind/self-unbind flows.
+- Split compliance interfaces between standard and extensions:
+ - `IERC3643Compliance` now contains the base ERC-3643 compliance surface.
+ - supplementary functions are grouped in `IERC3643ComplianceExtended` and advertised through a dedicated ERC-165 extension interface ID.
+- Split compliance modules between standard and extensions:
+ - `ERC3643ComplianceModule` now contains the base ERC-3643 compliance surface.
+ - supplementary functions are grouped in `ERC3643ComplianceExtendedModule`.
+- Batch self-binding approval event emission now uses a single batch event (`TokenSelfBindingApprovalBatchSet`) per call instead of per-token `TokenSelfBindingApprovalSet` emissions.
### Testing
@@ -90,9 +107,16 @@ forge lint
- Add RBAC tests ensuring roles cannot be granted to rule accounts.
- Add ownable and ownable2step tests ensuring ownership cannot be transferred to rule accounts.
- Add compliance-binding authorization tests across RBAC/ownable/ownable2step variants for:
- - token self-bind
- - token self-unbind
+ - approved token self-bind
+ - approved token self-unbind
+ - unapproved token self-bind/self-unbind denial
- cross-token bind/unbind denial
+- Add tests for self-binding approval management across RBAC/ownable/ownable2step variants:
+ - single approval set
+ - batch approval set
+ - zero-address rejection
+ - unauthorized caller rejection.
+- Add ERC-3643 `setCompliance`-style migration test with a dedicated mock token to validate unbind(old) + bind(new) flow against RuleEngine self-binding approval controls.
### Documentation
@@ -100,6 +124,10 @@ forge lint
- data-plane = runtime compliance callbacks (`transferred`, `created`, `destroyed`)
- control-plane = governance/configuration actions (`bindToken`, `unbindToken`, role grants, ownership changes, and rule management)
- Document that token-privilege separation in multi-token setups is an operational recommendation (not enforced on-chain) to preserve integrator flexibility for token-driven control-plane extensions.
+- Document ERC-3643 `setCompliance` compatibility details in README:
+ - token self-bind/self-unbind feature support
+ - required explicit self-binding approval
+ - recommended operational sequence for compliance migration.
### v3.0.0-rc2 - 2026-04-14
diff --git a/README.md b/README.md
index 38d8b8a..3961d5b 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,41 @@ This function is defined in the extension module `ValidationModuleRuleEngine`
setCompliance(address _compliance)
```
+### Making `setCompliance` work with RuleEngine
+
+RuleEngine supports the ERC-3643/T-REX pattern where the token contract binds and unbinds itself when `setCompliance` is called.
+
+In other words, a token can call:
+
+- `bindToken(address(this))`
+- `unbindToken(address(this))`
+
+To keep this feature secure, self-bind/self-unbind is gated:
+
+- A token can call `bindToken(address(this))` and `unbindToken(address(this))` only if it was explicitly approved first.
+- Approval is set by governance/compliance admin using:
+ - `setTokenSelfBindingApproval(address token, bool approved)`
+- Approval status can be checked with:
+ - `isTokenSelfBindingApproved(address token)`
+
+This preserves compatibility with ERC-3643 tokens that do:
+
+```solidity
+if (address(_tokenCompliance) != address(0)) {
+ _tokenCompliance.unbindToken(address(this));
+}
+_tokenCompliance = IModularCompliance(_compliance);
+_tokenCompliance.bindToken(address(this));
+```
+
+while preventing arbitrary third-party contracts from self-binding.
+
+Recommended operational sequence:
+
+1. On the target RuleEngine, grant self-binding approval for the token.
+2. Call token `setCompliance(newRuleEngine)`.
+3. (Optional) Revoke self-binding approval after migration if no longer needed.
+
## How to include it
@@ -108,6 +143,7 @@ setCompliance(address _compliance)
While the RuleEngine has been designed for CMTAT and ERC-3643 tokens, it can be used with other contracts to apply transfer restrictions.
For that, the only thing to do is to import in your contract the interface `IRuleEngine`(CMTAT) or `IERC3643Compliance` (ERC-3643), which declares the corresponding functions to call by the token contract. This interface can be found [here](https://github.com/CMTA/CMTAT/blob/23a1e59f913d079d0c09d32fafbd95ab2d426093/contracts/interfaces/engine/IRuleEngine.sol).
+If you need non-standard helper functions (batch bind/unbind, self-binding approval APIs, multi-token getter), use `IERC3643ComplianceExtended`.
### Like CMTAT
@@ -214,8 +250,11 @@ external;
### ERC-3643
The [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643) compliance interface is defined in [IERC3643Compliance.sol](src/interfaces/IERC3643Compliance.sol).
+Non-standard helper functions are defined in [IERC3643ComplianceExtended.sol](src/interfaces/IERC3643ComplianceExtended.sol).
-A specific module implements this interface for the RuleEngine: [ERC3643ComplianceModule.sol](src/modules/ERC3643ComplianceModule.sol)
+The RuleEngine modules are split as follows:
+- Base ERC-3643 surface: [ERC3643ComplianceModule.sol](src/modules/ERC3643ComplianceModule.sol)
+- Non-standard extensions: [ERC3643ComplianceExtendedModule.sol](src/modules/ERC3643ComplianceExtendedModule.sol)

@@ -268,16 +307,16 @@ The table below summarizes which ERC-165 interfaces are advertised by each deplo
| Interface | Interface ID | RuleEngine (RBAC deployment) | RuleEngineOwnable deployment | RuleEngineOwnable2Step deployment |
| --- | --- | --- | --- | --- |
-| `IERC165` | `0x01ffc9a7` | Yes | Yes | Yes |
-| `IRuleEngine` | `0x20c49ce7` | Yes | Yes | Yes |
-| `IERC1404` | `0xab84a5c8` | Yes | Yes | Yes |
-| `IERC1404Extend` | `0x78a8de7d` | Yes | Yes | Yes |
-| `IERC3643Compliance` | `0x3144991c` | Yes | Yes | Yes |
-| `IERC7551Compliance` (subset) | `0x7157797f` | Yes | Yes | Yes |
-| `IERC173` | `0x7f5828d0` | No | Yes | Yes |
-| `Ownable2Step` specific (`pendingOwner()`, `acceptOwnership()`) | `0x9ab669ef` | No | No | Yes |
-| `IAccessControl` | `0x7965db0b` | Yes | No | No |
-| `IAccessControlEnumerable` | `0x5a05180f` | Yes | No | No |
+| `IERC165` | `0x01ffc9a7` | ✔ | ✔ | ✔ |
+| `IRuleEngine` | `0x20c49ce7` | ✔ | ✔ | ✔ |
+| `IERC1404` | `0xab84a5c8` | ✔ | ✔ | ✔ |
+| `IERC1404Extend` | `0x78a8de7d` | ✔ | ✔ | ✔ |
+| `IERC3643Compliance` | `0x3144991c` | ✔ | ✔ | ✔ |
+| `IERC7551Compliance` (subset) | `0x7157797f` | ✔ | ✔ | ✔ |
+| `IERC173` | `0x7f5828d0` | ✘ | ✔ | ✔ |
+| `Ownable2Step` specific (`pendingOwner()`, `acceptOwnership()`) | `0x9ab669ef` | ✘ | ✘ | ✔ |
+| `IAccessControl` | `0x7965db0b` | ✔ | ✘ | ✘ |
+| `IAccessControlEnumerable` | `0x5a05180f` | ✔ | ✘ | ✘ |
Notes:
- `RuleEngine` advertises OpenZeppelin RBAC interfaces because it inherits `AccessControlEnumerable`.
@@ -328,6 +367,11 @@ For function signatures, struct arguments are represented with their correspondi
| ERC3643ComplianceModule | | | | | |
| | `bindToken(address token)` | public | `address token` | - | COMPLIANCE_MANAGER_ROLE |
| | `unbindToken(address token)` | public | `address token` | - | COMPLIANCE_MANAGER_ROLE |
+| ERC3643ComplianceExtendedModule | | | | | |
+| | `bindTokens(address[] tokens)` | public | `address[] tokens` | - | COMPLIANCE_MANAGER_ROLE |
+| | `unbindTokens(address[] tokens)` | public | `address[] tokens` | - | COMPLIANCE_MANAGER_ROLE |
+| | `setTokenSelfBindingApproval(address token,bool approved)` | public | `address token,bool approved` | - | COMPLIANCE_MANAGER_ROLE |
+| | `setTokenSelfBindingApprovalBatch(address[] tokens,bool approved)` | public | `address[] tokens,bool approved` | - | COMPLIANCE_MANAGER_ROLE |
| RuleEngineBase | | | | | |
| | `transferred(address from,address to,uint256 value)` | public | `address from,address to, uint256 value` | - | onlyBoundToken (modifier) |
| | `transferred(address spender,address from,address to,uint256 value)` | public | `address spender,address from,address to, uint256 value` | - | onlyBoundToken (modifier) |
@@ -353,7 +397,8 @@ RuleEngineOwnable
├── RuleEngineBase (core functionality)
│ ├── VersionModule
│ ├── RulesManagementModule
-│ ├── ERC3643ComplianceModule
+│ ├── ERC3643ComplianceModule (core ERC-3643)
+│ ├── ERC3643ComplianceExtendedModule (project extensions)
│ └── IRuleEngineERC1404
└── Ownable (ERC-173 access control)
```
@@ -377,7 +422,8 @@ RuleEngineOwnable2Step
│ └── RuleEngineBase
│ ├── VersionModule
│ ├── RulesManagementModule
-│ ├── ERC3643ComplianceModule
+│ ├── ERC3643ComplianceModule (core ERC-3643)
+│ ├── ERC3643ComplianceExtendedModule (project extensions)
│ └── IRuleEngineERC1404
└── Ownable2Step (ERC-173 access control with pending owner)
```
@@ -536,7 +582,7 @@ constructor(
| :----------------: | :---------------------------: | :----------------------------------------------------------: | :------------: | :------------: |
| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** |
| | | | | |
-| **RuleEngineBase** | Implementation | VersionModule, RulesManagementModule, ERC3643ComplianceModule, RuleEngineInvariantStorage, IRuleEngine | | |
+| **RuleEngineBase** | Implementation | VersionModule, RulesManagementModule, ERC3643ComplianceExtendedModule, RuleEngineInvariantStorage, IRuleEngine | | |
| └ | transferred | Public ❗️ | 🛑 | onlyBoundToken |
| └ | transferred | Public ❗️ | 🛑 | onlyBoundToken |
| └ | created | Public ❗️ | 🛑 | onlyBoundToken |
@@ -887,8 +933,11 @@ Useful for identifying which version of the smart contract is deployed and in us
| └ | bindToken | Public ❗️ | 🛑 | onlyRole |
| └ | unbindToken | Public ❗️ | 🛑 | onlyRole |
| └ | isTokenBound | Public ❗️ | | NO❗️ |
-| └ | getTokenBound | External ❗️ | | NO❗️ |
-| └ | getTokenBounds | External ❗️ | | NO❗️ |
+| └ | getTokenBound | Public ❗️ | | NO❗️ |
+
+### ERC3643ComplianceExtendedModule
+
+`ERC3643ComplianceExtendedModule` inherits `ERC3643ComplianceModule` and contains project-specific helpers not part of the ERC-3643 base interface (`IERC3643Compliance`): batch bind/unbind, self-binding approval APIs, and `getTokenBounds()`.
| └ | _unbindToken | Internal 🔒 | 🛑 | |
| └ | _bindToken | Internal 🔒 | 🛑 | |
diff --git a/doc/ERCSpecification/erc-3643.md b/doc/ERCSpecification/erc-3643.md
new file mode 100644
index 0000000..c4a5d3a
--- /dev/null
+++ b/doc/ERCSpecification/erc-3643.md
@@ -0,0 +1,414 @@
+---
+eip: 3643
+title: T-REX - Token for Regulated EXchanges
+description: An institutional grade security token contract that provides interfaces for the management and compliant transfer of security tokens.
+author: Joachim Lebrun (@Joachim-Lebrun), Tony Malghem (@TonyMalghem), Kevin Thizy (@Nakasar), Luc Falempin (@lfalempin), Adam Boudjemaa (@Aboudjem)
+discussions-to: https://ethereum-magicians.org/t/eip-3643-proposition-of-the-t-rex-token-standard-for-securities/6844
+status: Final
+type: Standards Track
+category: ERC
+created: 2021-07-09
+requires: 20, 173
+---
+
+## Abstract
+
+The T-REX token is an institutional grade security token standard. This standard provides a library of interfaces for the management and compliant transfer of security tokens, using an automated onchain validator system leveraging onchain identities for eligibility checks.
+
+The standard defines several interfaces that are described hereunder:
+
+- Token
+- Identity Registry
+- Identity Registry Storage
+- Compliance
+- Trusted Issuers Registry
+- Claim Topics Registry
+
+## Motivation
+
+The advent of blockchain technology has brought about a new era of efficiency, accessibility, and liquidity in the world of asset transfer. This is particularly evident in the realm of cryptocurrencies, where users can transfer token ownership peer-to-peer without intermediaries. However, when it comes to tokenized securities or security tokens, the situation is more complex due to the need for compliance with securities laws. These tokens cannot be permissionless like utility tokens; they must be permissioned to track ownership and ensure that only eligible investors can hold tokens.
+
+The existing Ethereum protocol, while powerful and versatile, does not fully address the unique challenges posed by security tokens. There is a need for a standard that supports compliant issuance and management of permissioned tokens, suitable for representing a wide range of asset classes, including small businesses and real estate.
+
+The proposed [ERC-3643](./eip-3643.md) standard is motivated by this need. It aims to provide a comprehensive framework for managing the lifecycle of security tokens, from issuance to transfers between eligible investors, while enforcing compliance rules at every stage. The standard also supports additional features such as token pausing and freezing, which can be used to manage the token in response to regulatory requirements or changes in the status of the token or its holders.
+
+Moreover, the standard is designed to work in conjunction with an on-chain Identity system, allowing for the validation of the identities and credentials of investors through signed attestations issued by trusted claim issuers. This ensures compliance with legal and regulatory requirements for the trading of security tokens.
+
+In summary, the motivation behind the proposed standard is to bring the benefits of blockchain technology to the world of securities, while ensuring compliance with existing securities laws. It aims to provide a robust, flexible, and efficient framework for the issuance and management of security tokens, thereby accelerating the evolution of capital markets.
+
+## Specification
+
+The proposed standard has the following requirements:
+
+- **MUST** be [ERC-20](./eip-20.md) compatible.
+- **MUST** be used in combination with an onchain Identity system
+- **MUST** be able to apply any rule of compliance that is required by the regulator or by the token issuer (about the factors of eligibility of an identity or about the rules of the token itself)
+- **MUST** have a standard interface to pre-check if a transfer is going to pass or fail before sending it to the blockchain
+- **MUST** have a recovery system in case an investor loses access to his private key
+- **MUST** be able to freeze tokens on the wallet of investors if needed, partially or totally
+- **MUST** have the possibility to pause the token
+- **MUST** be able to mint and burn tokens
+- **MUST** define an Agent role and an Owner (token issuer) role
+- **MUST** be able to force transfers from an Agent wallet
+- **MUST** be able to issue transactions in batch (to save gas and to have all the transactions performed in the same block)
+
+While this standard is backwards compatible with ERC-20 and all ERC-20 functions can be called on an ERC-3643 token, the implementation of these functions differs due to the permissioned nature of ERC-3643. Each token transfer under this standard involves a compliance check to validate the transfer and the eligibility of the stakeholder’s identities.
+
+### Agent Role Interface
+
+The standard defines an Agent role, which is crucial for managing access to various functions of the smart contracts. The interface for the Agent role is as follows:
+
+```solidity
+interface IAgentRole {
+
+ // events
+ event AgentAdded(address indexed _agent);
+ event AgentRemoved(address indexed _agent);
+
+ // functions
+ // setters
+ function addAgent(address _agent) external;
+ function removeAgent(address _agent) external;
+
+ // getters
+ function isAgent(address _agent) external view returns (bool);
+}
+ ```
+
+The `IAgentRole` interface allows for the addition and removal of agents, as well as checking if an address is an agent. In this standard, it is the owner role, as defined by [ERC-173](./eip-173.md), that has the responsibility of appointing and removing agents. Any contract that fulfills the role of a Token contract or an Identity Registry within the context of this standard must be compatible with the `IAgentRole` interface.
+
+### Main functions
+
+#### Transfer
+
+To be able to perform a transfer on T-REX you need to fulfill several conditions :
+
+- The sender **MUST** hold enough free balance (total balance - frozen tokens, if any)
+- The receiver **MUST** be whitelisted on the Identity Registry and verified (hold the necessary claims on his onchain Identity)
+- The sender's wallet **MUST NOT** be frozen
+- The receiver's wallet **MUST NOT** be frozen
+- The token **MUST NOT** be paused
+- The transfer **MUST** respect all the rules of compliance defined in the Compliance smart contract (canTransfer needs to return TRUE)
+
+Here is an example of `transfer` function implementation :
+
+```solidity
+function transfer(address _to, uint256 _amount) public override whenNotPaused returns (bool) {
+ require(!_frozen[_to] && !_frozen[msg.sender], "ERC-3643: Frozen wallet");
+ require(_amount <= balanceOf(msg.sender) - (_frozenTokens[msg.sender]), "ERC-3643: Insufficient Balance");
+ require( _tokenIdentityRegistry.isVerified(to), "ERC-3643: Invalid identity" );
+ require( _tokenCompliance.canTransfer(from, to, amount), "ERC-3643: Compliance failure" );
+ _transfer(msg.sender, _to, _amount);
+ _tokenCompliance.transferred(msg.sender, _to, _amount);
+ return true;
+ }
+ ```
+
+The `transferFrom` function works the same way while the `mint` function and the `forcedTransfer` function only require the receiver to be whitelisted and verified on the Identity Registry (they bypass the compliance rules). The `burn` function bypasses all checks on eligibility.
+
+#### isVerified
+
+The `isVerified` function is called from within the transfer functions `transfer`, `transferFrom`, `mint` and
+`forcedTransfer` to instruct the `Identity Registry` to check if the receiver is a valid investor, i.e. if his
+wallet address is in the `Identity Registry` of the token, and if the `Identity`contract linked to his wallet
+contains the claims (see [Claim Holder](../assets/eip-3643/ONCHAINID/IERC735.sol)) required in the `Claim Topics Registry` and
+if these claims are signed by an authorized Claim Issuer as required in the `Trusted Issuers Registry`.
+If all the requirements are fulfilled, the `isVerified` function returns `TRUE`, otherwise it returns `FALSE`. An
+implementation of this function can be found on the T-REX repository of Tokeny.
+
+#### canTransfer
+
+The `canTransfer` function is also called from within transfer functions. This function checks if the transfer is compliant with global compliance rules applied to the token, in opposition with `isVerified` that only checks the eligibility of an investor to hold and receive tokens, the `canTransfer` function is looking at global compliance rules, e.g. check if the transfer is compliant in the case there is a fixed maximum number of token holders to respect (can be a limited number of holders per country as well), check if the transfer respects rules setting a maximum amount of tokens per investor, ...
+If all the requirements are fulfilled, the `canTransfer` function will return `TRUE` otherwise it will return
+`FALSE` and the transfer will not be allowed to happen. An implementation of this function can be found on the T-REX
+repository of Tokeny.
+
+#### Other functions
+
+Description of other functions of the ERC-3643 can be found in the `interfaces` folder. An implementation of the
+ERC-3643 suite of smart contracts can be found on the T-REX repository of Tokeny.
+
+### Token interface
+
+ERC-3643 permissioned tokens build upon the standard ERC-20 structure, but with additional functions to ensure compliance in the transactions of the security tokens. The functions `transfer` and `transferFrom` are implemented in a conditional way, allowing them to proceed with a transfer only if the transaction is valid. The permissioned tokens are allowed to be transferred only to validated counterparties, in order to avoid tokens being held in wallets/Identity contracts of ineligible/unauthorized investors. The ERC-3643 standard also supports the recovery of security tokens in case an investor loses access to their wallet private key. A history of recovered tokens is maintained on the blockchain for transparency reasons.
+
+ERC-3643 tokens implement a range of additional functions to enable the owner or their appointed agents to manage supply, transfer rules, lockups, and any other requirements in the management of a security. The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents. Any contract that fulfills the role of a Token contract within the context of this standard must be compatible with the `IAgentRole` interface.
+
+A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IERC3643.sol).
+
+```solidity
+interface IERC3643 is IERC20 {
+
+ // events
+ event UpdatedTokenInformation(string _newName, string _newSymbol, uint8 _newDecimals, string _newVersion, address _newOnchainID);
+ event IdentityRegistryAdded(address indexed _identityRegistry);
+ event ComplianceAdded(address indexed _compliance);
+ event RecoverySuccess(address _lostWallet, address _newWallet, address _investorOnchainID);
+ event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner);
+ event TokensFrozen(address indexed _userAddress, uint256 _amount);
+ event TokensUnfrozen(address indexed _userAddress, uint256 _amount);
+ event Paused(address _userAddress);
+ event Unpaused(address _userAddress);
+
+
+ // functions
+ // getters
+ function onchainID() external view returns (address);
+ function version() external view returns (string memory);
+ function identityRegistry() external view returns (IIdentityRegistry);
+ function compliance() external view returns (ICompliance);
+ function paused() external view returns (bool);
+ function isFrozen(address _userAddress) external view returns (bool);
+ function getFrozenTokens(address _userAddress) external view returns (uint256);
+
+ // setters
+ function setName(string calldata _name) external;
+ function setSymbol(string calldata _symbol) external;
+ function setOnchainID(address _onchainID) external;
+ function pause() external;
+ function unpause() external;
+ function setAddressFrozen(address _userAddress, bool _freeze) external;
+ function freezePartialTokens(address _userAddress, uint256 _amount) external;
+ function unfreezePartialTokens(address _userAddress, uint256 _amount) external;
+ function setIdentityRegistry(address _identityRegistry) external;
+ function setCompliance(address _compliance) external;
+
+ // transfer actions
+ function forcedTransfer(address _from, address _to, uint256 _amount) external returns (bool);
+ function mint(address _to, uint256 _amount) external;
+ function burn(address _userAddress, uint256 _amount) external;
+ function recoveryAddress(address _lostWallet, address _newWallet, address _investorOnchainID) external returns (bool);
+
+ // batch functions
+ function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external;
+ function batchForcedTransfer(address[] calldata _fromList, address[] calldata _toList, uint256[] calldata _amounts) external;
+ function batchMint(address[] calldata _toList, uint256[] calldata _amounts) external;
+ function batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external;
+ function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external;
+ function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external;
+ function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external;
+}
+
+```
+
+### Identity Registry Interface
+
+The Identity Registry is linked to storage that contains a dynamic whitelist of identities. It establishes the link between a wallet address, an Identity smart contract, and a country code corresponding to the investor's country of residence. This country code is set in accordance with the ISO-3166 standard. The Identity Registry also includes a function called `isVerified()`, which returns a status based on the validity of claims (as per the security token requirements) in the user’s Identity contract.
+
+The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents. Any contract that fulfills the role of an Identity Registry within the context of this standard must be compatible with the `IAgentRole` interface. The Identity Registry is managed by the agent wallet(s), meaning only the agent(s) can add or remove identities in the registry. Note that the agent role on the Identity Registry is set by the owner, therefore the owner could set themselves as the agent if they want to maintain full control. There is a specific identity registry for each security token.
+
+A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IIdentityRegistry.sol).
+
+Note that [`IClaimIssuer`](../assets/eip-3643/ONCHAINID/IClaimIssuer.sol) and [`IIdentity`](../assets/eip-3643/ONCHAINID/IIdentity.sol) are needed in this interface as they are required for the Identity eligibility checks.
+
+```solidity
+interface IIdentityRegistry {
+
+
+ // events
+ event ClaimTopicsRegistrySet(address indexed claimTopicsRegistry);
+ event IdentityStorageSet(address indexed identityStorage);
+ event TrustedIssuersRegistrySet(address indexed trustedIssuersRegistry);
+ event IdentityRegistered(address indexed investorAddress, IIdentity indexed identity);
+ event IdentityRemoved(address indexed investorAddress, IIdentity indexed identity);
+ event IdentityUpdated(IIdentity indexed oldIdentity, IIdentity indexed newIdentity);
+ event CountryUpdated(address indexed investorAddress, uint16 indexed country);
+
+
+ // functions
+ // identity registry getters
+ function identityStorage() external view returns (IIdentityRegistryStorage);
+ function issuersRegistry() external view returns (ITrustedIssuersRegistry);
+ function topicsRegistry() external view returns (IClaimTopicsRegistry);
+
+ //identity registry setters
+ function setIdentityRegistryStorage(address _identityRegistryStorage) external;
+ function setClaimTopicsRegistry(address _claimTopicsRegistry) external;
+ function setTrustedIssuersRegistry(address _trustedIssuersRegistry) external;
+
+ // registry actions
+ function registerIdentity(address _userAddress, IIdentity _identity, uint16 _country) external;
+ function deleteIdentity(address _userAddress) external;
+ function updateCountry(address _userAddress, uint16 _country) external;
+ function updateIdentity(address _userAddress, IIdentity _identity) external;
+ function batchRegisterIdentity(address[] calldata _userAddresses, IIdentity[] calldata _identities, uint16[] calldata _countries) external;
+
+ // registry consultation
+ function contains(address _userAddress) external view returns (bool);
+ function isVerified(address _userAddress) external view returns (bool);
+ function identity(address _userAddress) external view returns (IIdentity);
+ function investorCountry(address _userAddress) external view returns (uint16);
+}
+```
+
+### Identity Registry Storage Interface
+
+The Identity Registry Storage stores the identity addresses of all the authorized investors in the security token(s) linked to the storage contract. These are all identities of investors who have been authorized to hold the token(s) after having gone through the appropriate KYC and eligibility checks. The Identity Registry Storage can be bound to one or several Identity Registry contract(s). The goal of the Identity Registry storage is to separate the Identity Registry functions and specifications from its storage. This way, it is possible to keep one single Identity Registry contract per token, with its own Trusted Issuers Registry and Claim Topics Registry, but with a shared whitelist of investors used by the `isVerifed()` function implemented in the Identity Registries to check the eligibility of the receiver in a transfer transaction.
+
+The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents(in this case through the `bindIdentityRegistry` function). Any contract that fulfills the role of an Identity Registry Storage within the context of this standard must be compatible with the `IAgentRole` interface. The Identity Registry Storage is managed by the agent addresses (i.e. the bound Identity Registries), meaning only the agent(s) can add or remove identities in the registry. Note that the agent role on the Identity Registry Storage is set by the owner, therefore the owner could set themselves as the agent if they want to modify the storage manually. Otherwise it is the bound Identity Registries that are using the agent role to write in the Identity Registry Storage.
+
+A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IIdentityRegistryStorage.sol).
+
+```solidity
+interface IIdentityRegistryStorage {
+
+ //events
+ event IdentityStored(address indexed investorAddress, IIdentity indexed identity);
+ event IdentityUnstored(address indexed investorAddress, IIdentity indexed identity);
+ event IdentityModified(IIdentity indexed oldIdentity, IIdentity indexed newIdentity);
+ event CountryModified(address indexed investorAddress, uint16 indexed country);
+ event IdentityRegistryBound(address indexed identityRegistry);
+ event IdentityRegistryUnbound(address indexed identityRegistry);
+
+ //functions
+ // storage related functions
+ function storedIdentity(address _userAddress) external view returns (IIdentity);
+ function storedInvestorCountry(address _userAddress) external view returns (uint16);
+ function addIdentityToStorage(address _userAddress, IIdentity _identity, uint16 _country) external;
+ function removeIdentityFromStorage(address _userAddress) external;
+ function modifyStoredInvestorCountry(address _userAddress, uint16 _country) external;
+ function modifyStoredIdentity(address _userAddress, IIdentity _identity) external;
+
+ // role setter
+ function bindIdentityRegistry(address _identityRegistry) external;
+ function unbindIdentityRegistry(address _identityRegistry) external;
+
+ // getter for bound IdentityRegistry role
+ function linkedIdentityRegistries() external view returns (address[] memory);
+}
+```
+
+### Compliance Interface
+
+The Compliance contract is used to set the rules of the offering itself and ensures these rules are respected during the whole lifecycle of the token. For example, the Compliance contract will define the maximum amount of investors per country, the maximum amount of tokens per investor, and the accepted countries for the circulation of the token (using the country code corresponding to each investor in the Identity Registry). The Compliance smart contract can be either “tailor-made”, following the legal requirements of the token issuer, or can be deployed under a generic modular form, which can then add and remove external compliance `Modules` to fit the legal requirements of the token in the same way as a custom "tailor-made" contract would.
+
+This contract is triggered at every transaction by the Token and returns `TRUE` if the transaction is compliant with the rules of the offering and `FALSE` otherwise.
+
+The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of setting the Compliance parameters and binding the Compliance to a Token contract.
+
+A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/ICompliance.sol).
+
+```solidity
+interface ICompliance {
+
+ // events
+ event TokenBound(address _token);
+ event TokenUnbound(address _token);
+
+ // functions
+ // initialization of the compliance contract
+ function bindToken(address _token) external;
+ function unbindToken(address _token) external;
+
+ // check the parameters of the compliance contract
+ function isTokenBound(address _token) external view returns (bool);
+ function getTokenBound() external view returns (address);
+
+ // compliance check and state update
+ function canTransfer(address _from, address _to, uint256 _amount) external view returns (bool);
+ function transferred(address _from, address _to, uint256 _amount) external;
+ function created(address _to, uint256 _amount) external;
+ function destroyed(address _from, uint256 _amount) external;
+}
+```
+
+### Trusted Issuer's Registry Interface
+
+The Trusted Issuer's Registry stores the contract addresses ([IClaimIssuer](../assets/eip-3643/ONCHAINID/IClaimIssuer.sol)) of all the trusted claim issuers for a specific security token. The Identity contract ([IIdentity](../assets/eip-3643/ONCHAINID/IIdentity.sol)) of token owners (the investors) must have claims signed by the claim issuers stored in this smart contract in order to be able to hold the token.
+
+The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of managing this registry as per their requirements. This includes the ability to add, remove, and update the list of Trusted Issuers.
+
+A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol).
+
+```solidity
+interface ITrustedIssuersRegistry {
+
+ // events
+ event TrustedIssuerAdded(IClaimIssuer indexed trustedIssuer, uint[] claimTopics);
+ event TrustedIssuerRemoved(IClaimIssuer indexed trustedIssuer);
+ event ClaimTopicsUpdated(IClaimIssuer indexed trustedIssuer, uint[] claimTopics);
+
+ // functions
+ // setters
+ function addTrustedIssuer(IClaimIssuer _trustedIssuer, uint[] calldata _claimTopics) external;
+ function removeTrustedIssuer(IClaimIssuer _trustedIssuer) external;
+ function updateIssuerClaimTopics(IClaimIssuer _trustedIssuer, uint[] calldata _claimTopics) external;
+
+ // getters
+ function getTrustedIssuers() external view returns (IClaimIssuer[] memory);
+ function isTrustedIssuer(address _issuer) external view returns(bool);
+ function getTrustedIssuerClaimTopics(IClaimIssuer _trustedIssuer) external view returns(uint[] memory);
+ function getTrustedIssuersForClaimTopic(uint256 claimTopic) external view returns (IClaimIssuer[] memory);
+ function hasClaimTopic(address _issuer, uint _claimTopic) external view returns(bool);
+}
+```
+
+### Claim Topics Registry Interface
+
+The Claim Topics Registry stores all the trusted claim topics for the security token. The Identity contract ([IIdentity](../assets/eip-3643/ONCHAINID/IIdentity.sol)) of token owners must contain claims of the claim topics stored in this smart contract.
+
+The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of managing this registry as per their requirements. This includes the ability to add and remove required Claim Topics.
+
+A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IClaimTopicsRegistry.sol).
+
+```solidity
+interface IClaimTopicsRegistry {
+
+ // events
+ event ClaimTopicAdded(uint256 indexed claimTopic);
+ event ClaimTopicRemoved(uint256 indexed claimTopic);
+
+ // functions
+ // setters
+ function addClaimTopic(uint256 _claimTopic) external;
+ function removeClaimTopic(uint256 _claimTopic) external;
+
+ // getter
+ function getClaimTopics() external view returns (uint256[] memory);
+}
+```
+
+## Rationale
+
+### Transfer Restrictions
+
+Transfers of securities can fail for a variety of reasons. This is in direct contrast to utility tokens, which generally only require the sender to have a sufficient balance. These conditions can be related to the status of an investor’s wallet, the identity of the sender and receiver of the securities (i.e., whether they have been through a KYC process, whether they are accredited or an affiliate of the issuer) or for reasons unrelated to the specific transfer but instead set at the token level (i.e., the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor). For ERC-20 tokens, the `balanceOf` and `allowance` functions provide a way to check that a transfer is likely to succeed before executing the transfer, which can be executed both on-chain and off-chain. For tokens representing securities, the T-REX standard introduces a function `canTransfer` which provides a more general-purpose way to achieve this. I.e., when the reasons for failure are related to the compliance rules of the token and a function `isVerified` which allows checking the eligibility status of the identity of the investor. Transfers can also fail if the address of the sender and/or receiver is frozen, or if the free balance of the sender (total balance - frozen tokens) is lower than the amount to transfer. Ultimately, the transfer could be blocked if the token is `paused`.
+
+### Identity Management
+
+Security and compliance of transfers are enforced through the management of on-chain identities. These include:
+
+- Identity contract: A unique identifier for each investor, which is used to manage their identity and claims.
+- Claim: Signed attestations issued by a trusted claim issuer that confirm certain attributes or qualifications of the token holders, such as their identity, location, investor status, or KYC/AML clearance.
+- Identity Storage/Registry: A storage system for all Identity contracts and their associated wallets, which is used to
+ verify the eligibility of investors during transfers.
+
+### Token Lifecycle Management
+
+The T-REX standard provides a comprehensive framework for managing the lifecycle of security tokens. This includes the issuance of tokens, transfers between eligible investors, and the enforcement of compliance rules at every stage of the token's lifecycle. The standard also supports additional features such as token pausing and freezing, which can be used to manage the token in response to regulatory requirements or changes in the status of the token or its holders.
+
+### Additional Compliance Rules
+
+The T-REX standard supports the implementation of additional compliance rules through modular compliance. These modules can be used to enforce a wide range of rules and restrictions, such as caps on the number of investors or the percentage of tokens held by a single investor, restrictions on transfers between certain types of investors, and more. This flexibility allows issuers to tailor the compliance rules of their tokens to their specific needs and regulatory environment.
+
+### Inclusion of Agent-Related Functions
+
+The inclusion of Agent-scoped functions within the standard interfaces is deliberate. The intent is to accommodate secure and adaptable token management practices that surpass the capabilities of EOA management. We envision scenarios where the agent role is fulfilled by automated systems or smart contracts, capable of programmatically executing operational functions like minting, burning, and freezing in response to specified criteria or regulatory triggers. For example, a smart contract might automatically burn tokens to align with redemption requests in an open-ended fund, or freeze tokens associated with wallets engaged in fraudulent activities.
+
+Consequently, these functions are standardized to provide a uniform interface for various automated systems interacting with different ERC-3643 tokens, allowing for standardized tooling and interfaces that work across the entire ecosystem. This approach ensures that ERC-3643 remains flexible, future-proof, and capable of supporting a wide array of operational models.
+
+## Backwards Compatibility
+
+T-REX tokens should be backwards compatible with ERC-20 and ERC-173
+and should be able to interact with a [Claim Holder contract](../assets/eip-3643/ONCHAINID/IERC735.sol) to validate
+the claims linked to an [Identity contract](../assets/eip-3643/ONCHAINID/IIdentity.sol).
+
+
+## Security Considerations
+
+This specification has been audited by Kapersky and Hacken, and no notable security considerations were found.
+While the audits were primarily focused on the specific implementation by Tokeny, they also challenged and validated the core principles of the T-REX standard. The auditing teams approval of these principles provides assurance that the standard itself is robust and does not present any significant security concerns.
+
+## Copyright
+
+Copyright and related rights waived via [CC0](../LICENSE.md).
diff --git a/doc/coverage/code-coverage.png b/doc/coverage/code-coverage.png
index 84075e2..b395bbe 100644
Binary files a/doc/coverage/code-coverage.png and b/doc/coverage/code-coverage.png differ
diff --git a/doc/coverage/coverage/index-sort-b.html b/doc/coverage/coverage/index-sort-b.html
index 140fc17..15ae843 100644
--- a/doc/coverage/coverage/index-sort-b.html
+++ b/doc/coverage/coverage/index-sort-b.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -84,14 +84,14 @@
src/modules
-
+
- 95.0 %
- 95 / 100
- 87.5 %
- 28 / 32
- 83.9 %
- 26 / 31
+ 96.7 %
+ 116 / 120
+ 89.5 %
+ 34 / 38
+ 85.7 %
+ 30 / 35
src/deployment
@@ -99,9 +99,9 @@
100.0 %
- 49 / 49
+ 52 / 52
100.0 %
- 24 / 24
+ 25 / 25
100.0 %
5 / 5
@@ -111,7 +111,7 @@
100.0 %
- 63 / 63
+ 64 / 64
100.0 %
20 / 20
100.0 %
diff --git a/doc/coverage/coverage/index-sort-f.html b/doc/coverage/coverage/index-sort-f.html
index 1fedf73..b39c30e 100644
--- a/doc/coverage/coverage/index-sort-f.html
+++ b/doc/coverage/coverage/index-sort-f.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -84,14 +84,14 @@
src/modules
-
+
- 95.0 %
- 95 / 100
- 87.5 %
- 28 / 32
- 83.9 %
- 26 / 31
+ 96.7 %
+ 116 / 120
+ 89.5 %
+ 34 / 38
+ 85.7 %
+ 30 / 35
src
@@ -99,7 +99,7 @@
100.0 %
- 63 / 63
+ 64 / 64
100.0 %
20 / 20
100.0 %
@@ -111,9 +111,9 @@
100.0 %
- 49 / 49
+ 52 / 52
100.0 %
- 24 / 24
+ 25 / 25
100.0 %
5 / 5
diff --git a/doc/coverage/coverage/index-sort-l.html b/doc/coverage/coverage/index-sort-l.html
index de8fcda..0394496 100644
--- a/doc/coverage/coverage/index-sort-l.html
+++ b/doc/coverage/coverage/index-sort-l.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -84,14 +84,14 @@
src/modules
-
+
- 95.0 %
- 95 / 100
- 87.5 %
- 28 / 32
- 83.9 %
- 26 / 31
+ 96.7 %
+ 116 / 120
+ 89.5 %
+ 34 / 38
+ 85.7 %
+ 30 / 35
src/deployment
@@ -99,9 +99,9 @@
100.0 %
- 49 / 49
+ 52 / 52
100.0 %
- 24 / 24
+ 25 / 25
100.0 %
5 / 5
@@ -111,7 +111,7 @@
100.0 %
- 63 / 63
+ 64 / 64
100.0 %
20 / 20
100.0 %
diff --git a/doc/coverage/coverage/index.html b/doc/coverage/coverage/index.html
index af90d9e..f897605 100644
--- a/doc/coverage/coverage/index.html
+++ b/doc/coverage/coverage/index.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -87,7 +87,7 @@
100.0 %
- 63 / 63
+ 64 / 64
100.0 %
20 / 20
100.0 %
@@ -99,23 +99,23 @@
100.0 %
- 49 / 49
+ 52 / 52
100.0 %
- 24 / 24
+ 25 / 25
100.0 %
5 / 5
src/modules
-
+
- 95.0 %
- 95 / 100
- 87.5 %
- 28 / 32
- 83.9 %
- 26 / 31
+ 96.7 %
+ 116 / 120
+ 89.5 %
+ 34 / 38
+ 85.7 %
+ 30 / 35
diff --git a/doc/coverage/coverage/src/RuleEngineBase.sol.func-sort-c.html b/doc/coverage/coverage/src/RuleEngineBase.sol.func-sort-c.html
index cad3862..3386ca0 100644
--- a/doc/coverage/coverage/src/RuleEngineBase.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/RuleEngineBase.sol.func-sort-c.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -69,59 +69,59 @@
Hit count
- RuleEngineBase.created
+ RuleEngineBase.created
4
- RuleEngineBase.destroyed
+ RuleEngineBase.destroyed
4
- RuleEngineBase.transferred.0
+ RuleEngineBase.transferred.0
5
- RuleEngineBase.detectTransferRestrictionFrom
+ RuleEngineBase.detectTransferRestrictionFrom
18
- RuleEngineBase.transferred.1
+ RuleEngineBase.transferred.1
18
- RuleEngineBase._messageForTransferRestriction
+ RuleEngineBase._messageForTransferRestriction
19
- RuleEngineBase.messageForTransferRestriction
+ RuleEngineBase.messageForTransferRestriction
19
- RuleEngineBase.canTransferFrom
+ RuleEngineBase.canTransferFrom
21
- RuleEngineBase.canTransfer
+ RuleEngineBase.canTransfer
25
- RuleEngineBase.detectTransferRestriction
+ RuleEngineBase.detectTransferRestriction
34
- RuleEngineBase._detectTransferRestrictionFrom
+ RuleEngineBase._detectTransferRestrictionFrom
39
- RuleEngineBase._supportsRuleEngineBaseInterface
- 46
+ RuleEngineBase._supportsRuleEngineBaseInterface
+ 56
- RuleEngineBase._detectTransferRestriction
+ RuleEngineBase._detectTransferRestriction
59
- RuleEngineBase._checkRule
+ RuleEngineBase._checkRule
253
diff --git a/doc/coverage/coverage/src/RuleEngineBase.sol.func.html b/doc/coverage/coverage/src/RuleEngineBase.sol.func.html
index 906ea70..32fb24c 100644
--- a/doc/coverage/coverage/src/RuleEngineBase.sol.func.html
+++ b/doc/coverage/coverage/src/RuleEngineBase.sol.func.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -69,59 +69,59 @@
Hit count
- RuleEngineBase._checkRule
+ RuleEngineBase._checkRule
253
- RuleEngineBase._detectTransferRestriction
+ RuleEngineBase._detectTransferRestriction
59
- RuleEngineBase._detectTransferRestrictionFrom
+ RuleEngineBase._detectTransferRestrictionFrom
39
- RuleEngineBase._messageForTransferRestriction
+ RuleEngineBase._messageForTransferRestriction
19
- RuleEngineBase._supportsRuleEngineBaseInterface
- 46
+ RuleEngineBase._supportsRuleEngineBaseInterface
+ 56
- RuleEngineBase.canTransfer
+ RuleEngineBase.canTransfer
25
- RuleEngineBase.canTransferFrom
+ RuleEngineBase.canTransferFrom
21
- RuleEngineBase.created
+ RuleEngineBase.created
4
- RuleEngineBase.destroyed
+ RuleEngineBase.destroyed
4
- RuleEngineBase.detectTransferRestriction
+ RuleEngineBase.detectTransferRestriction
34
- RuleEngineBase.detectTransferRestrictionFrom
+ RuleEngineBase.detectTransferRestrictionFrom
18
- RuleEngineBase.messageForTransferRestriction
+ RuleEngineBase.messageForTransferRestriction
19
- RuleEngineBase.transferred.0
+ RuleEngineBase.transferred.0
5
- RuleEngineBase.transferred.1
+ RuleEngineBase.transferred.1
18
diff --git a/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html b/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html
index 343a1c7..22088f9 100644
--- a/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html
+++ b/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -85,202 +85,204 @@
14 : : import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol";
15 : :
16 : : /* ==== Modules === */
- 17 : : import {ERC3643ComplianceModule, IERC3643Compliance} from "./modules/ERC3643ComplianceModule.sol";
+ 17 : : import {ERC3643ComplianceExtendedModule} from "./modules/ERC3643ComplianceExtendedModule.sol";
18 : : import {VersionModule} from "./modules/VersionModule.sol";
19 : : import {RulesManagementModule} from "./modules/RulesManagementModule.sol";
20 : :
21 : : /* ==== Interface and other library === */
- 22 : : import {IRule} from "./interfaces/IRule.sol";
- 23 : : import {ComplianceInterfaceId} from "./modules/library/ComplianceInterfaceId.sol";
- 24 : : import {ERC1404InterfaceId} from "./modules/library/ERC1404InterfaceId.sol";
- 25 : : import {RuleEngineInvariantStorage} from "./modules/library/RuleEngineInvariantStorage.sol";
- 26 : : import {RuleInterfaceId} from "./modules/library/RuleInterfaceId.sol";
- 27 : :
- 28 : : /**
- 29 : : * @title Implementation of a ruleEngine as defined by the CMTAT
- 30 : : */
- 31 : : abstract contract RuleEngineBase is
- 32 : : VersionModule,
- 33 : : RulesManagementModule,
- 34 : : ERC3643ComplianceModule,
- 35 : : RuleEngineInvariantStorage,
- 36 : : IRuleEngineERC1404
- 37 : : {
- 38 : : /* ============ State functions ============ */
- 39 : : /*
- 40 : : * @inheritdoc IRuleEngine
- 41 : : */
- 42 : 5 : function transferred(address spender, address from, address to, uint256 value)
- 43 : : public
- 44 : : virtual
- 45 : : override(IRuleEngine)
- 46 : : onlyBoundToken
- 47 : : {
- 48 : : // Apply on RuleEngine
- 49 : 4 : RulesManagementModule._transferred(spender, from, to, value);
- 50 : : }
- 51 : :
- 52 : : /**
- 53 : : * @inheritdoc IERC3643IComplianceContract
- 54 : : */
- 55 : 18 : function transferred(address from, address to, uint256 value)
- 56 : : public
- 57 : : virtual
- 58 : : override(IERC3643IComplianceContract)
- 59 : : onlyBoundToken
- 60 : : {
- 61 : 16 : _transferred(from, to, value);
- 62 : : }
- 63 : :
- 64 : : /// @inheritdoc IERC3643Compliance
- 65 : 4 : function created(address to, uint256 value) public virtual override(IERC3643Compliance) onlyBoundToken {
- 66 : 2 : _transferred(address(0), to, value);
- 67 : : }
- 68 : :
- 69 : : /// @inheritdoc IERC3643Compliance
- 70 : 4 : function destroyed(address from, uint256 value) public virtual override(IERC3643Compliance) onlyBoundToken {
- 71 : 2 : _transferred(from, address(0), value);
- 72 : : }
- 73 : :
- 74 : : /* ============ View functions ============ */
- 75 : : /**
- 76 : : * @notice Go through all the rule to know if a restriction exists on the transfer
- 77 : : * @param from the origin address
- 78 : : * @param to the destination address
- 79 : : * @param value to transfer
- 80 : : * @return The restricion code or REJECTED_CODE_BASE.TRANSFER_OK (0) if the transfer is valid
- 81 : : *
- 82 : : */
- 83 : 34 : function detectTransferRestriction(address from, address to, uint256 value)
- 84 : : public
- 85 : : view
- 86 : : virtual
- 87 : : override(IERC1404)
- 88 : : returns (uint8)
- 89 : : {
- 90 : 59 : return _detectTransferRestriction(from, to, value);
- 91 : : }
- 92 : :
- 93 : : /**
- 94 : : * @inheritdoc IERC1404Extend
- 95 : : */
- 96 : 18 : function detectTransferRestrictionFrom(address spender, address from, address to, uint256 value)
- 97 : : public
- 98 : : view
- 99 : : virtual
- 100 : : override(IERC1404Extend)
- 101 : : returns (uint8)
- 102 : : {
- 103 : 39 : return _detectTransferRestrictionFrom(spender, from, to, value);
- 104 : : }
- 105 : :
- 106 : : /**
- 107 : : * @inheritdoc IERC1404
- 108 : : */
- 109 : 19 : function messageForTransferRestriction(uint8 restrictionCode)
- 110 : : public
- 111 : : view
- 112 : : virtual
- 113 : : override(IERC1404)
- 114 : : returns (string memory)
- 115 : : {
- 116 : 19 : return _messageForTransferRestriction(restrictionCode);
- 117 : : }
- 118 : :
- 119 : : /**
- 120 : : * @inheritdoc IERC3643ComplianceRead
- 121 : : */
- 122 : 25 : function canTransfer(address from, address to, uint256 value)
- 123 : : public
- 124 : : view
- 125 : : virtual
- 126 : : override(IERC3643ComplianceRead)
- 127 : : returns (bool)
- 128 : : {
- 129 : 25 : return detectTransferRestriction(from, to, value) == uint8(REJECTED_CODE_BASE.TRANSFER_OK);
- 130 : : }
- 131 : :
- 132 : : /**
- 133 : : * @inheritdoc IERC7551Compliance
- 134 : : */
- 135 : 21 : function canTransferFrom(address spender, address from, address to, uint256 value)
- 136 : : public
- 137 : : view
- 138 : : virtual
- 139 : : override(IERC7551Compliance)
- 140 : : returns (bool)
- 141 : : {
- 142 : 21 : return detectTransferRestrictionFrom(spender, from, to, value) == uint8(REJECTED_CODE_BASE.TRANSFER_OK);
- 143 : : }
- 144 : :
- 145 : : /*//////////////////////////////////////////////////////////////
- 146 : : INTERNAL/PRIVATE FUNCTIONS
- 147 : : //////////////////////////////////////////////////////////////*/
- 148 : 59 : function _detectTransferRestriction(address from, address to, uint256 value) internal view virtual returns (uint8) {
- 149 : 59 : uint256 rulesLength = rulesCount();
- 150 : 59 : for (uint256 i = 0; i < rulesLength; ++i) {
- 151 : 59 : uint8 restriction = IRule(rule(i)).detectTransferRestriction(from, to, value);
- 152 [ + ]: 59 : if (restriction > 0) {
- 153 : 43 : return restriction;
- 154 : : }
- 155 : : }
- 156 : 16 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK);
- 157 : : }
- 158 : :
- 159 : 39 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value)
- 160 : : internal
- 161 : : view
- 162 : : virtual
- 163 : : returns (uint8)
- 164 : : {
- 165 : 39 : uint256 rulesLength = rulesCount();
- 166 : 39 : for (uint256 i = 0; i < rulesLength; ++i) {
- 167 : 39 : uint8 restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender, from, to, value);
- 168 [ + ]: 39 : if (restriction > 0) {
- 169 : 29 : return restriction;
- 170 : : }
- 171 : : }
- 172 : 10 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK);
- 173 : : }
- 174 : :
- 175 : : /**
- 176 : : * @dev This function returns the message from the first rule claiming the code.
- 177 : : * Rule designers should keep restriction codes unique across rules.
- 178 : : * If a code is shared intentionally, all rules using that code should return
- 179 : : * the same message to avoid ambiguous operator feedback.
- 180 : : */
- 181 : 19 : function _messageForTransferRestriction(uint8 restrictionCode) internal view virtual returns (string memory) {
- 182 : 19 : uint256 rulesLength = rulesCount();
- 183 : 19 : for (uint256 i = 0; i < rulesLength; ++i) {
- 184 [ + ]: 16 : if (IRule(rule(i)).canReturnTransferRestrictionCode(restrictionCode)) {
- 185 : 14 : return IRule(rule(i)).messageForTransferRestriction(restrictionCode);
- 186 : : }
- 187 : : }
- 188 : 5 : return "Unknown restriction code";
- 189 : : }
- 190 : :
- 191 : : /**
- 192 : : * @dev Override to add ERC-165 interface check for the full IRule hierarchy.
- 193 : : */
- 194 : 253 : function _checkRule(address rule_) internal view virtual override {
- 195 : 253 : RulesManagementModule._checkRule(rule_);
- 196 [ + ]: 244 : if (!ERC165Checker.supportsInterface(rule_, RuleInterfaceId.IRULE_INTERFACE_ID)) {
- 197 : 6 : revert RuleEngine_RuleInvalidInterface();
- 198 : : }
- 199 : : }
- 200 : :
- 201 : : /**
- 202 : : * @dev Shared ERC-165 checks common to all RuleEngine deployment variants.
- 203 : : * Concrete deployments can extend this with access-control-specific interfaces.
- 204 : : */
- 205 : 46 : function _supportsRuleEngineBaseInterface(bytes4 interfaceId) internal pure returns (bool) {
- 206 : 46 : return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID
- 207 : 41 : || interfaceId == ERC1404InterfaceId.IERC1404_INTERFACE_ID
- 208 : 31 : || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID
- 209 : 26 : || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID
- 210 : 21 : || interfaceId == ComplianceInterfaceId.IERC7551_COMPLIANCE_INTERFACE_ID;
- 211 : : }
- 212 : : }
+ 22 : : import {IERC3643Compliance} from "./interfaces/IERC3643Compliance.sol";
+ 23 : : import {IRule} from "./interfaces/IRule.sol";
+ 24 : : import {ComplianceInterfaceId} from "./modules/library/ComplianceInterfaceId.sol";
+ 25 : : import {ERC1404InterfaceId} from "./modules/library/ERC1404InterfaceId.sol";
+ 26 : : import {RuleEngineInvariantStorage} from "./modules/library/RuleEngineInvariantStorage.sol";
+ 27 : : import {RuleInterfaceId} from "./modules/library/RuleInterfaceId.sol";
+ 28 : :
+ 29 : : /**
+ 30 : : * @title Implementation of a ruleEngine as defined by the CMTAT
+ 31 : : */
+ 32 : : abstract contract RuleEngineBase is
+ 33 : : VersionModule,
+ 34 : : RulesManagementModule,
+ 35 : : ERC3643ComplianceExtendedModule,
+ 36 : : RuleEngineInvariantStorage,
+ 37 : : IRuleEngineERC1404
+ 38 : : {
+ 39 : : /* ============ State functions ============ */
+ 40 : : /*
+ 41 : : * @inheritdoc IRuleEngine
+ 42 : : */
+ 43 : 5 : function transferred(address spender, address from, address to, uint256 value)
+ 44 : : public
+ 45 : : virtual
+ 46 : : override(IRuleEngine)
+ 47 : : onlyBoundToken
+ 48 : : {
+ 49 : : // Apply on RuleEngine
+ 50 : 4 : RulesManagementModule._transferred(spender, from, to, value);
+ 51 : : }
+ 52 : :
+ 53 : : /**
+ 54 : : * @inheritdoc IERC3643IComplianceContract
+ 55 : : */
+ 56 : 18 : function transferred(address from, address to, uint256 value)
+ 57 : : public
+ 58 : : virtual
+ 59 : : override(IERC3643IComplianceContract)
+ 60 : : onlyBoundToken
+ 61 : : {
+ 62 : 16 : _transferred(from, to, value);
+ 63 : : }
+ 64 : :
+ 65 : : /// @inheritdoc IERC3643Compliance
+ 66 : 4 : function created(address to, uint256 value) public virtual override(IERC3643Compliance) onlyBoundToken {
+ 67 : 2 : _transferred(address(0), to, value);
+ 68 : : }
+ 69 : :
+ 70 : : /// @inheritdoc IERC3643Compliance
+ 71 : 4 : function destroyed(address from, uint256 value) public virtual override(IERC3643Compliance) onlyBoundToken {
+ 72 : 2 : _transferred(from, address(0), value);
+ 73 : : }
+ 74 : :
+ 75 : : /* ============ View functions ============ */
+ 76 : : /**
+ 77 : : * @notice Go through all the rule to know if a restriction exists on the transfer
+ 78 : : * @param from the origin address
+ 79 : : * @param to the destination address
+ 80 : : * @param value to transfer
+ 81 : : * @return The restricion code or REJECTED_CODE_BASE.TRANSFER_OK (0) if the transfer is valid
+ 82 : : *
+ 83 : : */
+ 84 : 34 : function detectTransferRestriction(address from, address to, uint256 value)
+ 85 : : public
+ 86 : : view
+ 87 : : virtual
+ 88 : : override(IERC1404)
+ 89 : : returns (uint8)
+ 90 : : {
+ 91 : 59 : return _detectTransferRestriction(from, to, value);
+ 92 : : }
+ 93 : :
+ 94 : : /**
+ 95 : : * @inheritdoc IERC1404Extend
+ 96 : : */
+ 97 : 18 : function detectTransferRestrictionFrom(address spender, address from, address to, uint256 value)
+ 98 : : public
+ 99 : : view
+ 100 : : virtual
+ 101 : : override(IERC1404Extend)
+ 102 : : returns (uint8)
+ 103 : : {
+ 104 : 39 : return _detectTransferRestrictionFrom(spender, from, to, value);
+ 105 : : }
+ 106 : :
+ 107 : : /**
+ 108 : : * @inheritdoc IERC1404
+ 109 : : */
+ 110 : 19 : function messageForTransferRestriction(uint8 restrictionCode)
+ 111 : : public
+ 112 : : view
+ 113 : : virtual
+ 114 : : override(IERC1404)
+ 115 : : returns (string memory)
+ 116 : : {
+ 117 : 19 : return _messageForTransferRestriction(restrictionCode);
+ 118 : : }
+ 119 : :
+ 120 : : /**
+ 121 : : * @inheritdoc IERC3643ComplianceRead
+ 122 : : */
+ 123 : 25 : function canTransfer(address from, address to, uint256 value)
+ 124 : : public
+ 125 : : view
+ 126 : : virtual
+ 127 : : override(IERC3643ComplianceRead)
+ 128 : : returns (bool)
+ 129 : : {
+ 130 : 25 : return detectTransferRestriction(from, to, value) == uint8(REJECTED_CODE_BASE.TRANSFER_OK);
+ 131 : : }
+ 132 : :
+ 133 : : /**
+ 134 : : * @inheritdoc IERC7551Compliance
+ 135 : : */
+ 136 : 21 : function canTransferFrom(address spender, address from, address to, uint256 value)
+ 137 : : public
+ 138 : : view
+ 139 : : virtual
+ 140 : : override(IERC7551Compliance)
+ 141 : : returns (bool)
+ 142 : : {
+ 143 : 21 : return detectTransferRestrictionFrom(spender, from, to, value) == uint8(REJECTED_CODE_BASE.TRANSFER_OK);
+ 144 : : }
+ 145 : :
+ 146 : : /*//////////////////////////////////////////////////////////////
+ 147 : : INTERNAL/PRIVATE FUNCTIONS
+ 148 : : //////////////////////////////////////////////////////////////*/
+ 149 : 59 : function _detectTransferRestriction(address from, address to, uint256 value) internal view virtual returns (uint8) {
+ 150 : 59 : uint256 rulesLength = rulesCount();
+ 151 : 59 : for (uint256 i = 0; i < rulesLength; ++i) {
+ 152 : 59 : uint8 restriction = IRule(rule(i)).detectTransferRestriction(from, to, value);
+ 153 [ + ]: 59 : if (restriction > 0) {
+ 154 : 43 : return restriction;
+ 155 : : }
+ 156 : : }
+ 157 : 16 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK);
+ 158 : : }
+ 159 : :
+ 160 : 39 : function _detectTransferRestrictionFrom(address spender, address from, address to, uint256 value)
+ 161 : : internal
+ 162 : : view
+ 163 : : virtual
+ 164 : : returns (uint8)
+ 165 : : {
+ 166 : 39 : uint256 rulesLength = rulesCount();
+ 167 : 39 : for (uint256 i = 0; i < rulesLength; ++i) {
+ 168 : 39 : uint8 restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender, from, to, value);
+ 169 [ + ]: 39 : if (restriction > 0) {
+ 170 : 29 : return restriction;
+ 171 : : }
+ 172 : : }
+ 173 : 10 : return uint8(REJECTED_CODE_BASE.TRANSFER_OK);
+ 174 : : }
+ 175 : :
+ 176 : : /**
+ 177 : : * @dev This function returns the message from the first rule claiming the code.
+ 178 : : * Rule designers should keep restriction codes unique across rules.
+ 179 : : * If a code is shared intentionally, all rules using that code should return
+ 180 : : * the same message to avoid ambiguous operator feedback.
+ 181 : : */
+ 182 : 19 : function _messageForTransferRestriction(uint8 restrictionCode) internal view virtual returns (string memory) {
+ 183 : 19 : uint256 rulesLength = rulesCount();
+ 184 : 19 : for (uint256 i = 0; i < rulesLength; ++i) {
+ 185 [ + ]: 16 : if (IRule(rule(i)).canReturnTransferRestrictionCode(restrictionCode)) {
+ 186 : 14 : return IRule(rule(i)).messageForTransferRestriction(restrictionCode);
+ 187 : : }
+ 188 : : }
+ 189 : 5 : return "Unknown restriction code";
+ 190 : : }
+ 191 : :
+ 192 : : /**
+ 193 : : * @dev Override to add ERC-165 interface check for the full IRule hierarchy.
+ 194 : : */
+ 195 : 253 : function _checkRule(address rule_) internal view virtual override {
+ 196 : 253 : RulesManagementModule._checkRule(rule_);
+ 197 [ + ]: 244 : if (!ERC165Checker.supportsInterface(rule_, RuleInterfaceId.IRULE_INTERFACE_ID)) {
+ 198 : 6 : revert RuleEngine_RuleInvalidInterface();
+ 199 : : }
+ 200 : : }
+ 201 : :
+ 202 : : /**
+ 203 : : * @dev Shared ERC-165 checks common to all RuleEngine deployment variants.
+ 204 : : * Concrete deployments can extend this with access-control-specific interfaces.
+ 205 : : */
+ 206 : 56 : function _supportsRuleEngineBaseInterface(bytes4 interfaceId) internal pure returns (bool) {
+ 207 : 56 : return interfaceId == RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID
+ 208 : 51 : || interfaceId == ERC1404InterfaceId.IERC1404_INTERFACE_ID
+ 209 : 41 : || interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID
+ 210 : 36 : || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID
+ 211 : 31 : || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID
+ 212 : 21 : || interfaceId == ComplianceInterfaceId.IERC7551_COMPLIANCE_INTERFACE_ID;
+ 213 : : }
+ 214 : : }
diff --git a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func-sort-c.html b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func-sort-c.html
index 2d808cf..84148a7 100644
--- a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func-sort-c.html
@@ -37,7 +37,7 @@
-
+
@@ -78,19 +78,19 @@
RuleEngineOwnableShared.supportsInterface
- 29
+ 35
RuleEngineOwnableShared.constructor
- 128
+ 175
RuleEngineOwnableShared._msgSender
- 181
+ 249
RuleEngineOwnableShared._contextSuffixLength
- 183
+ 251
diff --git a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html
index 2f76325..b9f0c93 100644
--- a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html
+++ b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html
@@ -37,7 +37,7 @@
-
+
@@ -74,7 +74,7 @@
RuleEngineOwnableShared._contextSuffixLength
- 183
+ 251
RuleEngineOwnableShared._msgData
@@ -82,15 +82,15 @@
RuleEngineOwnableShared._msgSender
- 181
+ 249
RuleEngineOwnableShared.constructor
- 128
+ 175
RuleEngineOwnableShared.supportsInterface
- 29
+ 35
diff --git a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html
index 96d5846..cb1af0e 100644
--- a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html
+++ b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html
@@ -37,7 +37,7 @@
-
+
@@ -90,15 +90,15 @@
19 : : * (`Ownable` or `Ownable2Step`) while reusing constructor, ERC-165 and ERC-2771 code.
20 : : */
21 : : abstract contract RuleEngineOwnableShared is ERC2771ModuleStandalone, RuleEngineBase, ERC165 {
- 22 : 128 : constructor(address forwarderIrrevocable, address tokenContract) ERC2771ModuleStandalone(forwarderIrrevocable) {
- 23 [ + ]: 128 : if (tokenContract != address(0)) {
+ 22 : 175 : constructor(address forwarderIrrevocable, address tokenContract) ERC2771ModuleStandalone(forwarderIrrevocable) {
+ 23 [ + ]: 175 : if (tokenContract != address(0)) {
24 : 1 : _bindToken(tokenContract);
25 : : }
26 : : }
27 : :
28 : : /* ============ ERC-165 ============ */
- 29 : 29 : function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
- 30 : 29 : return _supportsRuleEngineBaseInterface(interfaceId) || interfaceId == OwnableInterfaceId.IERC173_INTERFACE_ID
+ 29 : 35 : function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
+ 30 : 35 : return _supportsRuleEngineBaseInterface(interfaceId) || interfaceId == OwnableInterfaceId.IERC173_INTERFACE_ID
31 : 5 : || ERC165.supportsInterface(interfaceId);
32 : : }
33 : :
@@ -118,8 +118,8 @@
47 : : /**
48 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
49 : : */
- 50 : 181 : function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) {
- 51 : 181 : return ERC2771Context._msgSender();
+ 50 : 249 : function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) {
+ 51 : 249 : return ERC2771Context._msgSender();
52 : : }
53 : :
54 : : /**
@@ -132,8 +132,8 @@
61 : : /**
62 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
63 : : */
- 64 : 183 : function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
- 65 : 183 : return ERC2771Context._contextSuffixLength();
+ 64 : 251 : function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
+ 65 : 251 : return ERC2771Context._contextSuffixLength();
66 : : }
67 : : }
diff --git a/doc/coverage/coverage/src/deployment/RuleEngine.sol.func-sort-c.html b/doc/coverage/coverage/src/deployment/RuleEngine.sol.func-sort-c.html
index be7f6ec..22e3bc9 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngine.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngine.sol.func-sort-c.html
@@ -37,7 +37,7 @@
-
+
@@ -78,23 +78,23 @@
RuleEngine.supportsInterface
- 17
+ 21
- RuleEngine._onlyComplianceManager
- 18
+ RuleEngine.grantRole
+ 39
- RuleEngine.grantRole
- 22
+ RuleEngine._onlyComplianceManager
+ 42
RuleEngine.constructor
- 146
+ 165
RuleEngine.hasRole
- 154
+ 173
RuleEngine._onlyRulesManager
@@ -102,11 +102,11 @@
RuleEngine._msgSender
- 450
+ 535
RuleEngine._contextSuffixLength
- 451
+ 536
diff --git a/doc/coverage/coverage/src/deployment/RuleEngine.sol.func.html b/doc/coverage/coverage/src/deployment/RuleEngine.sol.func.html
index 1cb351b..8984abe 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngine.sol.func.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngine.sol.func.html
@@ -37,7 +37,7 @@
-
+
@@ -70,7 +70,7 @@
RuleEngine._contextSuffixLength
- 451
+ 536
RuleEngine._msgData
@@ -78,11 +78,11 @@
RuleEngine._msgSender
- 450
+ 535
RuleEngine._onlyComplianceManager
- 18
+ 42
RuleEngine._onlyRulesLimitManager
@@ -94,19 +94,19 @@
RuleEngine.constructor
- 146
+ 165
RuleEngine.grantRole
- 22
+ 39
RuleEngine.hasRole
- 154
+ 173
RuleEngine.supportsInterface
- 17
+ 21
diff --git a/doc/coverage/coverage/src/deployment/RuleEngine.sol.gcov.html b/doc/coverage/coverage/src/deployment/RuleEngine.sol.gcov.html
index 25a82b7..42edf71 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngine.sol.gcov.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngine.sol.gcov.html
@@ -37,7 +37,7 @@
-
+
@@ -95,16 +95,16 @@
24 : : * @param admin Address of the contract (Access Control)
25 : : * @param forwarderIrrevocable Address of the forwarder, required for the gasless support
26 : : */
- 27 : 146 : constructor(address admin, address forwarderIrrevocable, address tokenContract)
+ 27 : 165 : constructor(address admin, address forwarderIrrevocable, address tokenContract)
28 : : ERC2771ModuleStandalone(forwarderIrrevocable)
29 : : {
- 30 [ + ]: 146 : if (admin == address(0)) {
+ 30 [ + ]: 165 : if (admin == address(0)) {
31 : 1 : revert RuleEngine_AdminWithAddressZeroNotAllowed();
32 : : }
- 33 [ + ]: 145 : if (tokenContract != address(0)) {
+ 33 [ + ]: 164 : if (tokenContract != address(0)) {
34 : 31 : _bindToken(tokenContract);
35 : : }
- 36 : 145 : _grantRole(DEFAULT_ADMIN_ROLE, admin);
+ 36 : 164 : _grantRole(DEFAULT_ADMIN_ROLE, admin);
37 : : }
38 : :
39 : : /* ============ ACCESS CONTROL ============ */
@@ -112,54 +112,54 @@
41 : : * @notice Grants `role` to `account`.
42 : : * @dev Prevents granting any role to accounts already configured as rules.
43 : : */
- 44 : 22 : function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
- 45 [ + ]: 22 : if (_rules.contains(account)) {
+ 44 : 39 : function grantRole(bytes32 role, address account) public virtual override(AccessControl, IAccessControl) {
+ 45 [ + ]: 39 : if (_rules.contains(account)) {
46 : 3 : revert RuleEngine_RulesManagementModule_RuleAccountCannotReceivePrivileges();
47 : : }
- 48 : 19 : AccessControl.grantRole(role, account);
+ 48 : 36 : AccessControl.grantRole(role, account);
49 : : }
50 : :
51 : : /**
52 : : * @notice Returns `true` if `account` has been granted `role`.
53 : : * @dev The Default Admin has all roles
54 : : */
- 55 : 154 : function hasRole(bytes32 role, address account)
+ 55 : 173 : function hasRole(bytes32 role, address account)
56 : : public
57 : : view
58 : : virtual
59 : : override(AccessControl, IAccessControl)
60 : : returns (bool)
61 : : {
- 62 [ + + ]: 413 : if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) {
- 63 : 219 : return true;
+ 62 [ + + ]: 490 : if (AccessControl.hasRole(DEFAULT_ADMIN_ROLE, account)) {
+ 63 : 236 : return true;
64 : : } else {
- 65 : 194 : return AccessControl.hasRole(role, account);
+ 65 : 254 : return AccessControl.hasRole(role, account);
66 : : }
67 : : }
68 : :
69 : : /* ============ ERC-165 ============ */
- 70 : 17 : function supportsInterface(bytes4 interfaceId)
+ 70 : 21 : function supportsInterface(bytes4 interfaceId)
71 : : public
72 : : view
73 : : virtual
74 : : override(AccessControlEnumerable, IERC165)
75 : : returns (bool)
76 : : {
- 77 : 17 : return _supportsRuleEngineBaseInterface(interfaceId) || AccessControlEnumerable.supportsInterface(interfaceId);
+ 77 : 21 : return _supportsRuleEngineBaseInterface(interfaceId) || AccessControlEnumerable.supportsInterface(interfaceId);
78 : : }
79 : :
80 : : /*//////////////////////////////////////////////////////////////
81 : : ERC-2771
82 : : //////////////////////////////////////////////////////////////*/
- 83 : 18 : function _onlyComplianceManager() internal virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {}
+ 83 : 42 : function _onlyComplianceManager() internal virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {}
84 : 196 : function _onlyRulesManager() internal virtual override onlyRole(RULES_MANAGEMENT_ROLE) {}
85 : 5 : function _onlyRulesLimitManager() internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {}
86 : :
87 : : /**
88 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
89 : : */
- 90 : 450 : function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) {
- 91 : 450 : return ERC2771Context._msgSender();
+ 90 : 535 : function _msgSender() internal view virtual override(ERC2771Context, Context) returns (address sender) {
+ 91 : 535 : return ERC2771Context._msgSender();
92 : : }
93 : :
94 : : /**
@@ -172,8 +172,8 @@
101 : : /**
102 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
103 : : */
- 104 : 451 : function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
- 105 : 451 : return ERC2771Context._contextSuffixLength();
+ 104 : 536 : function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
+ 105 : 536 : return ERC2771Context._contextSuffixLength();
106 : : }
107 : : }
diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func-sort-c.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func-sort-c.html
index 43de197..95ca527 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func-sort-c.html
@@ -37,7 +37,7 @@
-
+
@@ -82,7 +82,7 @@
RuleEngineOwnable._onlyComplianceManager
- 22
+ 46
RuleEngineOwnable._onlyRulesManager
@@ -90,11 +90,11 @@
RuleEngineOwnable._msgSender
- 146
+ 181
RuleEngineOwnable._contextSuffixLength
- 147
+ 182
diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func.html
index 778686c..d36afdf 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func.html
@@ -37,7 +37,7 @@
-
+
@@ -70,7 +70,7 @@
RuleEngineOwnable._contextSuffixLength
- 147
+ 182
RuleEngineOwnable._msgData
@@ -78,11 +78,11 @@
RuleEngineOwnable._msgSender
- 146
+ 181
RuleEngineOwnable._onlyComplianceManager
- 22
+ 46
RuleEngineOwnable._onlyRulesLimitManager
diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.gcov.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.gcov.html
index c3c3e37..3f1c9d9 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.gcov.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.gcov.html
@@ -37,7 +37,7 @@
-
+
@@ -101,7 +101,7 @@
30 : : /**
31 : : * @dev Access control check using Ownable pattern
32 : : */
- 33 : 22 : function _onlyComplianceManager() internal virtual override onlyOwner {}
+ 33 : 46 : function _onlyComplianceManager() internal virtual override onlyOwner {}
34 : :
35 : : /**
36 : : * @notice Transfers ownership of the contract to a new account (`newOwner`).
@@ -115,8 +115,8 @@
44 : : /**
45 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
46 : : */
- 47 : 146 : function _msgSender() internal view virtual override(RuleEngineOwnableShared, Context) returns (address sender) {
- 48 : 146 : return RuleEngineOwnableShared._msgSender();
+ 47 : 181 : function _msgSender() internal view virtual override(RuleEngineOwnableShared, Context) returns (address sender) {
+ 48 : 181 : return RuleEngineOwnableShared._msgSender();
49 : : }
50 : :
51 : : /**
@@ -129,8 +129,8 @@
58 : : /**
59 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
60 : : */
- 61 : 147 : function _contextSuffixLength() internal view virtual override(RuleEngineOwnableShared, Context) returns (uint256) {
- 62 : 147 : return RuleEngineOwnableShared._contextSuffixLength();
+ 61 : 182 : function _contextSuffixLength() internal view virtual override(RuleEngineOwnableShared, Context) returns (uint256) {
+ 62 : 182 : return RuleEngineOwnableShared._contextSuffixLength();
63 : : }
64 : : }
diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func-sort-c.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func-sort-c.html
index d43fc3b..4c225ed 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func-sort-c.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -69,32 +69,36 @@
Hit count
- RuleEngineOwnable2Step._msgData
+ RuleEngineOwnable2Step._msgData
1
- RuleEngineOwnable2Step._onlyRulesLimitManager
+ RuleEngineOwnable2Step._onlyRulesLimitManager
2
- RuleEngineOwnable2Step._onlyComplianceManager
- 3
-
-
- RuleEngineOwnable2Step._onlyRulesManager
+ RuleEngineOwnable2Step._onlyRulesManager
5
- RuleEngineOwnable2Step.transferOwnership
+ RuleEngineOwnable2Step.transferOwnership
6
- RuleEngineOwnable2Step._msgSender
- 35
+ RuleEngineOwnable2Step.supportsInterface
+ 13
+
+
+ RuleEngineOwnable2Step._onlyComplianceManager
+ 25
+
+
+ RuleEngineOwnable2Step._msgSender
+ 68
- RuleEngineOwnable2Step._contextSuffixLength
- 36
+ RuleEngineOwnable2Step._contextSuffixLength
+ 69
diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html
index 95204c6..9187783 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -69,31 +69,35 @@
Hit count
- RuleEngineOwnable2Step._contextSuffixLength
- 36
+ RuleEngineOwnable2Step._contextSuffixLength
+ 69
- RuleEngineOwnable2Step._msgData
+ RuleEngineOwnable2Step._msgData
1
- RuleEngineOwnable2Step._msgSender
- 35
+ RuleEngineOwnable2Step._msgSender
+ 68
- RuleEngineOwnable2Step._onlyComplianceManager
- 3
+ RuleEngineOwnable2Step._onlyComplianceManager
+ 25
- RuleEngineOwnable2Step._onlyRulesLimitManager
+ RuleEngineOwnable2Step._onlyRulesLimitManager
2
- RuleEngineOwnable2Step._onlyRulesManager
+ RuleEngineOwnable2Step._onlyRulesManager
5
- RuleEngineOwnable2Step.transferOwnership
+ RuleEngineOwnable2Step.supportsInterface
+ 13
+
+
+ RuleEngineOwnable2Step.transferOwnership
6
diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html
index 7d77041..ab3556d 100644
--- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html
+++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -78,63 +78,70 @@
7 : : import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
8 : : import {Context} from "@openzeppelin/contracts/utils/Context.sol";
9 : : import {RuleEngineOwnableShared} from "../RuleEngineOwnableShared.sol";
- 10 : :
- 11 : : /**
- 12 : : * @title Implementation of a ruleEngine with ERC-173 Ownable2Step access control
- 13 : : */
- 14 : : contract RuleEngineOwnable2Step is RuleEngineOwnableShared, Ownable2Step {
- 15 : : /**
- 16 : : * @param owner_ Address of the contract owner (ERC-173)
- 17 : : * @param forwarderIrrevocable Address of the forwarder, required for the gasless support
- 18 : : * @param tokenContract Address of the token contract to bind (can be zero address)
- 19 : : */
- 20 : : constructor(address owner_, address forwarderIrrevocable, address tokenContract)
- 21 : : RuleEngineOwnableShared(forwarderIrrevocable, tokenContract)
- 22 : : Ownable(owner_)
- 23 : : {}
- 24 : :
- 25 : : /* ============ ACCESS CONTROL ============ */
- 26 : : /**
- 27 : : * @dev Access control check using Ownable pattern
- 28 : : */
- 29 : 5 : function _onlyRulesManager() internal virtual override onlyOwner {}
- 30 : 2 : function _onlyRulesLimitManager() internal virtual override onlyOwner {}
- 31 : :
- 32 : : /**
- 33 : : * @dev Access control check using Ownable pattern
- 34 : : */
- 35 : 3 : function _onlyComplianceManager() internal virtual override onlyOwner {}
- 36 : :
- 37 : : /**
- 38 : : * @notice Starts ownership transfer to `newOwner`.
- 39 : : * @dev Reverts when `newOwner` is already configured as a rule.
- 40 : : */
- 41 : 6 : function transferOwnership(address newOwner) public virtual override onlyOwner {
- 42 : 6 : RuleEngineOwnableShared._checkOwnershipTransferTarget(newOwner);
- 43 : 5 : Ownable2Step.transferOwnership(newOwner);
- 44 : : }
- 45 : :
- 46 : : /**
- 47 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
- 48 : : */
- 49 : 35 : function _msgSender() internal view virtual override(RuleEngineOwnableShared, Context) returns (address sender) {
- 50 : 35 : return RuleEngineOwnableShared._msgSender();
+ 10 : : import {Ownable2StepInterfaceId} from "../modules/library/Ownable2StepInterfaceId.sol";
+ 11 : :
+ 12 : : /**
+ 13 : : * @title Implementation of a ruleEngine with ERC-173 Ownable2Step access control
+ 14 : : */
+ 15 : : contract RuleEngineOwnable2Step is RuleEngineOwnableShared, Ownable2Step {
+ 16 : : /**
+ 17 : : * @param owner_ Address of the contract owner (ERC-173)
+ 18 : : * @param forwarderIrrevocable Address of the forwarder, required for the gasless support
+ 19 : : * @param tokenContract Address of the token contract to bind (can be zero address)
+ 20 : : */
+ 21 : : constructor(address owner_, address forwarderIrrevocable, address tokenContract)
+ 22 : : RuleEngineOwnableShared(forwarderIrrevocable, tokenContract)
+ 23 : : Ownable(owner_)
+ 24 : : {}
+ 25 : :
+ 26 : : /* ============ ACCESS CONTROL ============ */
+ 27 : : /**
+ 28 : : * @dev Access control check using Ownable pattern
+ 29 : : */
+ 30 : 5 : function _onlyRulesManager() internal virtual override onlyOwner {}
+ 31 : 2 : function _onlyRulesLimitManager() internal virtual override onlyOwner {}
+ 32 : :
+ 33 : : /**
+ 34 : : * @dev Access control check using Ownable pattern
+ 35 : : */
+ 36 : 25 : function _onlyComplianceManager() internal virtual override onlyOwner {}
+ 37 : :
+ 38 : : /**
+ 39 : : * @notice Starts ownership transfer to `newOwner`.
+ 40 : : * @dev Reverts when `newOwner` is already configured as a rule.
+ 41 : : */
+ 42 : 6 : function transferOwnership(address newOwner) public virtual override onlyOwner {
+ 43 : 6 : RuleEngineOwnableShared._checkOwnershipTransferTarget(newOwner);
+ 44 : 5 : Ownable2Step.transferOwnership(newOwner);
+ 45 : : }
+ 46 : :
+ 47 : : /* ============ ERC-165 ============ */
+ 48 : 13 : function supportsInterface(bytes4 interfaceId) public view virtual override(RuleEngineOwnableShared) returns (bool) {
+ 49 : 13 : return interfaceId == Ownable2StepInterfaceId.IOWNABLE2STEP_INTERFACE_ID
+ 50 : 11 : || RuleEngineOwnableShared.supportsInterface(interfaceId);
51 : : }
52 : :
53 : : /**
54 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
55 : : */
- 56 : 1 : function _msgData() internal view virtual override(RuleEngineOwnableShared, Context) returns (bytes calldata) {
- 57 : 1 : return RuleEngineOwnableShared._msgData();
+ 56 : 68 : function _msgSender() internal view virtual override(RuleEngineOwnableShared, Context) returns (address sender) {
+ 57 : 68 : return RuleEngineOwnableShared._msgSender();
58 : : }
59 : :
60 : : /**
61 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
62 : : */
- 63 : 36 : function _contextSuffixLength() internal view virtual override(RuleEngineOwnableShared, Context) returns (uint256) {
- 64 : 36 : return RuleEngineOwnableShared._contextSuffixLength();
+ 63 : 1 : function _msgData() internal view virtual override(RuleEngineOwnableShared, Context) returns (bytes calldata) {
+ 64 : 1 : return RuleEngineOwnableShared._msgData();
65 : : }
- 66 : : }
+ 66 : :
+ 67 : : /**
+ 68 : : * @dev This surcharge is not necessary if you do not use the MetaTxModule
+ 69 : : */
+ 70 : 69 : function _contextSuffixLength() internal view virtual override(RuleEngineOwnableShared, Context) returns (uint256) {
+ 71 : 69 : return RuleEngineOwnableShared._contextSuffixLength();
+ 72 : : }
+ 73 : : }
diff --git a/doc/coverage/coverage/src/deployment/index-sort-b.html b/doc/coverage/coverage/src/deployment/index-sort-b.html
index ed7dd14..5ef3ff6 100644
--- a/doc/coverage/coverage/src/deployment/index-sort-b.html
+++ b/doc/coverage/coverage/src/deployment/index-sort-b.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -99,9 +99,9 @@
100.0 %
- 12 / 12
+ 15 / 15
100.0 %
- 7 / 7
+ 8 / 8
-
0 / 0
diff --git a/doc/coverage/coverage/src/deployment/index-sort-f.html b/doc/coverage/coverage/src/deployment/index-sort-f.html
index 465762f..414a352 100644
--- a/doc/coverage/coverage/src/deployment/index-sort-f.html
+++ b/doc/coverage/coverage/src/deployment/index-sort-f.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -99,9 +99,9 @@
100.0 %
- 12 / 12
+ 15 / 15
100.0 %
- 7 / 7
+ 8 / 8
-
0 / 0
diff --git a/doc/coverage/coverage/src/deployment/index-sort-l.html b/doc/coverage/coverage/src/deployment/index-sort-l.html
index 761e2e1..eefd8bd 100644
--- a/doc/coverage/coverage/src/deployment/index-sort-l.html
+++ b/doc/coverage/coverage/src/deployment/index-sort-l.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -99,9 +99,9 @@
100.0 %
- 12 / 12
+ 15 / 15
100.0 %
- 7 / 7
+ 8 / 8
-
0 / 0
diff --git a/doc/coverage/coverage/src/deployment/index.html b/doc/coverage/coverage/src/deployment/index.html
index 02b7147..a680e27 100644
--- a/doc/coverage/coverage/src/deployment/index.html
+++ b/doc/coverage/coverage/src/deployment/index.html
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
-
+
+
@@ -111,9 +111,9 @@
100.0 %
- 12 / 12
+ 15 / 15
100.0 %
- 7 / 7
+ 8 / 8
-
0 / 0
diff --git a/doc/coverage/coverage/src/index-sort-b.html b/doc/coverage/coverage/src/index-sort-b.html
index bd41fc0..353a9a9 100644
--- a/doc/coverage/coverage/src/index-sort-b.html
+++ b/doc/coverage/coverage/src/index-sort-b.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -99,7 +99,7 @@
100.0 %
- 48 / 48
+ 49 / 49
100.0 %
14 / 14
100.0 %
diff --git a/doc/coverage/coverage/src/index-sort-f.html b/doc/coverage/coverage/src/index-sort-f.html
index 6a0b8cd..8f77607 100644
--- a/doc/coverage/coverage/src/index-sort-f.html
+++ b/doc/coverage/coverage/src/index-sort-f.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -99,7 +99,7 @@
100.0 %
- 48 / 48
+ 49 / 49
100.0 %
14 / 14
100.0 %
diff --git a/doc/coverage/coverage/src/index-sort-l.html b/doc/coverage/coverage/src/index-sort-l.html
index 36d479c..d4235ae 100644
--- a/doc/coverage/coverage/src/index-sort-l.html
+++ b/doc/coverage/coverage/src/index-sort-l.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -99,7 +99,7 @@
100.0 %
- 48 / 48
+ 49 / 49
100.0 %
14 / 14
100.0 %
diff --git a/doc/coverage/coverage/src/index.html b/doc/coverage/coverage/src/index.html
index eaa4aa1..d0fc157 100644
--- a/doc/coverage/coverage/src/index.html
+++ b/doc/coverage/coverage/src/index.html
@@ -31,13 +31,13 @@
-
-
+
+
-
+
@@ -87,7 +87,7 @@
100.0 %
- 48 / 48
+ 49 / 49
100.0 %
14 / 14
100.0 %
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func-sort-c.html
new file mode 100644
index 0000000..13d1d9a
--- /dev/null
+++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func-sort-c.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+ LCOV - lcov.info - src/modules/ERC3643ComplianceExtendedModule.sol - functions
+
+
+
+
+
+
+ LCOV - code coverage report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html
new file mode 100644
index 0000000..51b35e9
--- /dev/null
+++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+ LCOV - lcov.info - src/modules/ERC3643ComplianceExtendedModule.sol - functions
+
+
+
+
+
+
+ LCOV - code coverage report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html
new file mode 100644
index 0000000..b228f55
--- /dev/null
+++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+ LCOV - lcov.info - src/modules/ERC3643ComplianceExtendedModule.sol
+
+
+
+
+
+
+ LCOV - code coverage report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func-sort-c.html
index 61a292e..aef8ae7 100644
--- a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func-sort-c.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -69,19 +69,15 @@
Hit count
- ERC3643ComplianceModule._onlyComplianceManager
+ ERC3643ComplianceModule._authorizeComplianceBindingChange
0
- ERC3643ComplianceModule.onlyComplianceManager
+ ERC3643ComplianceModule._onlyComplianceManager
0
- ERC3643ComplianceModule.getTokenBounds
- 4
-
-
- ERC3643ComplianceModule.getTokenBound
+ ERC3643ComplianceModule.getTokenBound
5
@@ -89,32 +85,32 @@
5
- ERC3643ComplianceModule._unbindToken
- 12
+ ERC3643ComplianceModule.onlyComplianceManager
+ 9
- ERC3643ComplianceModule.unbindToken
- 16
+ ERC3643ComplianceModule.unbindToken
+ 20
- ERC3643ComplianceModule.isTokenBound
- 21
+ ERC3643ComplianceModule._unbindToken
+ 25
- ERC3643ComplianceModule._checkBoundToken
+ ERC3643ComplianceModule._checkBoundToken
31
- ERC3643ComplianceModule.bindToken
- 38
+ ERC3643ComplianceModule.isTokenBound
+ 37
- ERC3643ComplianceModule._authorizeComplianceBindingChange
- 54
+ ERC3643ComplianceModule.bindToken
+ 52
- ERC3643ComplianceModule._bindToken
- 66
+ ERC3643ComplianceModule._bindToken
+ 101
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html
index f5ade38..dff97aa 100644
--- a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html
+++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -69,40 +69,36 @@
Hit count
- ERC3643ComplianceModule._authorizeComplianceBindingChange
- 54
+ ERC3643ComplianceModule._authorizeComplianceBindingChange
+ 0
- ERC3643ComplianceModule._bindToken
- 66
+ ERC3643ComplianceModule._bindToken
+ 101
- ERC3643ComplianceModule._checkBoundToken
+ ERC3643ComplianceModule._checkBoundToken
31
- ERC3643ComplianceModule._onlyComplianceManager
+ ERC3643ComplianceModule._onlyComplianceManager
0
- ERC3643ComplianceModule._unbindToken
- 12
+ ERC3643ComplianceModule._unbindToken
+ 25
- ERC3643ComplianceModule.bindToken
- 38
+ ERC3643ComplianceModule.bindToken
+ 52
- ERC3643ComplianceModule.getTokenBound
+ ERC3643ComplianceModule.getTokenBound
5
- ERC3643ComplianceModule.getTokenBounds
- 4
-
-
- ERC3643ComplianceModule.isTokenBound
- 21
+ ERC3643ComplianceModule.isTokenBound
+ 37
ERC3643ComplianceModule.onlyBoundToken
@@ -110,11 +106,11 @@
ERC3643ComplianceModule.onlyComplianceManager
- 0
+ 9
- ERC3643ComplianceModule.unbindToken
- 16
+ ERC3643ComplianceModule.unbindToken
+ 20
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html
index 232a450..a1c95ac 100644
--- a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html
+++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -84,7 +84,7 @@
13 : : using EnumerableSet for EnumerableSet.AddressSet;
14 : : /* ==== State Variables === */
15 : : // Token binding tracking
- 16 : : EnumerableSet.AddressSet private _boundTokens;
+ 16 : : EnumerableSet.AddressSet internal _boundTokens;
17 : : // Access Control
18 : : // Will not be present in the final bytecode if not used
19 : : bytes32 public constant COMPLIANCE_MANAGER_ROLE = keccak256("COMPLIANCE_MANAGER_ROLE");
@@ -102,8 +102,8 @@
31 : : _;
32 : : }
33 : :
- 34 : 0 : modifier onlyComplianceManager() {
- 35 : 0 : _onlyComplianceManager();
+ 34 : 9 : modifier onlyComplianceManager() {
+ 35 : 9 : _onlyComplianceManager();
36 : : _;
37 : : }
38 : :
@@ -117,86 +117,68 @@
46 : : * @dev Operator warning: "multi-tenant" means one RuleEngine is shared by
47 : : * multiple token contracts. In that setup, bind only tokens that are equally
48 : : * trusted and governed together.
- 49 : : * @dev T-REX compatibility: allows token self-binding when caller equals
- 50 : : * `token`, because TREX `Token.setCompliance` invokes `compliance.bindToken(address(this))`.
- 51 : : */
- 52 : 38 : function bindToken(address token) public virtual override {
- 53 : 38 : _authorizeComplianceBindingChange(token);
- 54 : 34 : _bindToken(token);
- 55 : : }
- 56 : :
- 57 : : /**
- 58 : : * @inheritdoc IERC3643Compliance
- 59 : : * @dev Operator warning: unbinding is an administrative operation and does not
- 60 : : * erase any state already stored by external rule contracts in a previously
- 61 : : * shared ("multi-tenant") setup.
- 62 : : * @dev T-REX compatibility: allows token self-unbinding when caller equals
- 63 : : * `token`, because TREX token contracts may call `compliance.unbindToken(address(this))`.
- 64 : : */
- 65 : 16 : function unbindToken(address token) public virtual override {
- 66 : 16 : _authorizeComplianceBindingChange(token);
- 67 : 12 : _unbindToken(token);
- 68 : : }
- 69 : :
- 70 : : /// @inheritdoc IERC3643Compliance
- 71 : 21 : function isTokenBound(address token) public view virtual override returns (bool) {
- 72 : 21 : return _boundTokens.contains(token);
- 73 : : }
- 74 : :
- 75 : : /// @inheritdoc IERC3643Compliance
- 76 : 5 : function getTokenBound() public view virtual override returns (address) {
- 77 [ + + ]: 5 : if (_boundTokens.length() > 0) {
- 78 : : // Note that there are no guarantees on the ordering of values inside the array,
- 79 : : // and it may change when more values are added or removed.
- 80 : 3 : return _boundTokens.at(0);
- 81 : : } else {
- 82 : 2 : return address(0);
- 83 : : }
- 84 : : }
+ 49 : : */
+ 50 : 52 : function bindToken(address token) public virtual override {
+ 51 : 52 : _authorizeComplianceBindingChange(token);
+ 52 : 45 : _bindToken(token);
+ 53 : : }
+ 54 : :
+ 55 : : /**
+ 56 : : * @inheritdoc IERC3643Compliance
+ 57 : : * @dev Operator warning: unbinding is an administrative operation and does not
+ 58 : : * erase any state already stored by external rule contracts in a previously
+ 59 : : * shared ("multi-tenant") setup.
+ 60 : : */
+ 61 : 20 : function unbindToken(address token) public virtual override {
+ 62 : 20 : _authorizeComplianceBindingChange(token);
+ 63 : 13 : _unbindToken(token);
+ 64 : : }
+ 65 : :
+ 66 : : /// @inheritdoc IERC3643Compliance
+ 67 : 37 : function isTokenBound(address token) public view virtual override returns (bool) {
+ 68 : 37 : return _boundTokens.contains(token);
+ 69 : : }
+ 70 : :
+ 71 : : /// @inheritdoc IERC3643Compliance
+ 72 : 5 : function getTokenBound() public view virtual override returns (address) {
+ 73 [ + + ]: 5 : if (_boundTokens.length() > 0) {
+ 74 : : // Note that there are no guarantees on the ordering of values inside the array,
+ 75 : : // and it may change when more values are added or removed.
+ 76 : 3 : return _boundTokens.at(0);
+ 77 : : } else {
+ 78 : 2 : return address(0);
+ 79 : : }
+ 80 : : }
+ 81 : :
+ 82 : : /*//////////////////////////////////////////////////////////////
+ 83 : : INTERNAL/PRIVATE FUNCTIONS
+ 84 : : //////////////////////////////////////////////////////////////*/
85 : :
- 86 : : /// @inheritdoc IERC3643Compliance
- 87 : 4 : function getTokenBounds() public view override returns (address[] memory) {
- 88 : 4 : return _boundTokens.values();
- 89 : : }
+ 86 : 25 : function _unbindToken(address token) internal {
+ 87 [ + + ]: 25 : require(_boundTokens.contains(token), RuleEngine_ERC3643Compliance_TokenNotBound());
+ 88 : : // Should never revert because we check if the token address is already set before
+ 89 [ # + ]: 20 : require(_boundTokens.remove(token), RuleEngine_ERC3643Compliance_OperationNotSuccessful());
90 : :
- 91 : : /*//////////////////////////////////////////////////////////////
- 92 : : INTERNAL/PRIVATE FUNCTIONS
- 93 : : //////////////////////////////////////////////////////////////*/
- 94 : :
- 95 : 12 : function _unbindToken(address token) internal {
- 96 [ + + ]: 12 : require(_boundTokens.contains(token), RuleEngine_ERC3643Compliance_TokenNotBound());
+ 91 : 20 : emit TokenUnbound(token);
+ 92 : : }
+ 93 : :
+ 94 : 101 : function _bindToken(address token) internal {
+ 95 [ + + ]: 101 : require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress());
+ 96 [ + + ]: 96 : require(!_boundTokens.contains(token), RuleEngine_ERC3643Compliance_TokenAlreadyBound());
97 : : // Should never revert because we check if the token address is already set before
- 98 [ # + ]: 10 : require(_boundTokens.remove(token), RuleEngine_ERC3643Compliance_OperationNotSuccessful());
- 99 : :
- 100 : 10 : emit TokenUnbound(token);
- 101 : : }
- 102 : :
- 103 : 66 : function _bindToken(address token) internal {
- 104 [ + + ]: 66 : require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress());
- 105 [ + + ]: 64 : require(!_boundTokens.contains(token), RuleEngine_ERC3643Compliance_TokenAlreadyBound());
- 106 : : // Should never revert because we check if the token address is already set before
- 107 [ # + ]: 62 : require(_boundTokens.add(token), RuleEngine_ERC3643Compliance_OperationNotSuccessful());
- 108 : 62 : emit TokenBound(token);
- 109 : : }
- 110 : :
- 111 : 31 : function _checkBoundToken() internal view virtual {
- 112 [ + ]: 31 : if (!_boundTokens.contains(_msgSender())) {
- 113 : 7 : revert RuleEngine_ERC3643Compliance_UnauthorizedCaller();
- 114 : : }
- 115 : : }
- 116 : :
- 117 : : /**
- 118 : : * @dev Authorizes bind/unbind operations.
- 119 : : * Allows compliance manager, or token self-calls for T-REX compatibility.
- 120 : : */
- 121 : 54 : function _authorizeComplianceBindingChange(address token) internal virtual {
- 122 [ + ]: 54 : if (_msgSender() != token) {
- 123 : 43 : _onlyComplianceManager();
- 124 : : }
- 125 : : }
- 126 : :
- 127 : 0 : function _onlyComplianceManager() internal virtual;
- 128 : : }
+ 98 [ # + ]: 91 : require(_boundTokens.add(token), RuleEngine_ERC3643Compliance_OperationNotSuccessful());
+ 99 : 91 : emit TokenBound(token);
+ 100 : : }
+ 101 : :
+ 102 : 31 : function _checkBoundToken() internal view virtual {
+ 103 [ + ]: 31 : if (!_boundTokens.contains(_msgSender())) {
+ 104 : 7 : revert RuleEngine_ERC3643Compliance_UnauthorizedCaller();
+ 105 : : }
+ 106 : : }
+ 107 : :
+ 108 : 0 : function _authorizeComplianceBindingChange(address token) internal virtual;
+ 109 : 0 : function _onlyComplianceManager() internal virtual;
+ 110 : : }
diff --git a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func-sort-c.html
index e3c9a39..47a830f 100644
--- a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func-sort-c.html
@@ -37,7 +37,7 @@
-
+
diff --git a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html
index f303599..89854ca 100644
--- a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html
+++ b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html
@@ -37,7 +37,7 @@
-
+
diff --git a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html
index afffd64..ade1adf 100644
--- a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html
+++ b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html
@@ -37,7 +37,7 @@
-
+
@@ -101,7 +101,7 @@
30 : : /// @dev Array of rules
31 : : EnumerableSet.AddressSet internal _rules;
32 : : /// @dev Maximum number of rules allowed in the engine.
- 33 : : uint256 internal _maxRules = 10;
+ 33 : : uint256 internal _maxRules = DEFAULT_MAX_RULES;
34 : :
35 : : /*//////////////////////////////////////////////////////////////
36 : : PUBLIC/EXTERNAL FUNCTIONS
diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html
index a850218..3527b2b 100644
--- a/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html
+++ b/doc/coverage/coverage/src/modules/VersionModule.sol.func-sort-c.html
@@ -37,7 +37,7 @@
-
+
diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.func.html b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html
index 47875c2..4c659b9 100644
--- a/doc/coverage/coverage/src/modules/VersionModule.sol.func.html
+++ b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html
@@ -37,7 +37,7 @@
-
+
diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html
index 348fd4f..4929d2f 100644
--- a/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html
+++ b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html
@@ -37,7 +37,7 @@
-
+
diff --git a/doc/coverage/coverage/src/modules/index-sort-b.html b/doc/coverage/coverage/src/modules/index-sort-b.html
index fa15856..d6915d3 100644
--- a/doc/coverage/coverage/src/modules/index-sort-b.html
+++ b/doc/coverage/coverage/src/modules/index-sort-b.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -96,14 +96,14 @@
ERC3643ComplianceModule.sol
-
+
- 91.2 %
- 31 / 34
- 83.3 %
- 10 / 12
- 85.7 %
- 12 / 14
+ 93.3 %
+ 28 / 30
+ 81.8 %
+ 9 / 11
+ 84.6 %
+ 11 / 13
VersionModule.sol
@@ -117,6 +117,18 @@
-
0 / 0
+
+ ERC3643ComplianceExtendedModule.sol
+
+
+
+ 100.0 %
+ 24 / 24
+ 100.0 %
+ 7 / 7
+ 100.0 %
+ 5 / 5
+
diff --git a/doc/coverage/coverage/src/modules/index-sort-f.html b/doc/coverage/coverage/src/modules/index-sort-f.html
index a2c384a..1125c04 100644
--- a/doc/coverage/coverage/src/modules/index-sort-f.html
+++ b/doc/coverage/coverage/src/modules/index-sort-f.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -84,14 +84,14 @@
ERC3643ComplianceModule.sol
-
+
- 91.2 %
- 31 / 34
- 83.3 %
- 10 / 12
- 85.7 %
- 12 / 14
+ 93.3 %
+ 28 / 30
+ 81.8 %
+ 9 / 11
+ 84.6 %
+ 11 / 13
RulesManagementModule.sol
@@ -117,6 +117,18 @@
-
0 / 0
+
+ ERC3643ComplianceExtendedModule.sol
+
+
+
+ 100.0 %
+ 24 / 24
+ 100.0 %
+ 7 / 7
+ 100.0 %
+ 5 / 5
+
diff --git a/doc/coverage/coverage/src/modules/index-sort-l.html b/doc/coverage/coverage/src/modules/index-sort-l.html
index d18eaa9..f4ce7fd 100644
--- a/doc/coverage/coverage/src/modules/index-sort-l.html
+++ b/doc/coverage/coverage/src/modules/index-sort-l.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -84,14 +84,14 @@
ERC3643ComplianceModule.sol
-
+
- 91.2 %
- 31 / 34
- 83.3 %
- 10 / 12
- 85.7 %
- 12 / 14
+ 93.3 %
+ 28 / 30
+ 81.8 %
+ 9 / 11
+ 84.6 %
+ 11 / 13
RulesManagementModule.sol
@@ -117,6 +117,18 @@
-
0 / 0
+
+ ERC3643ComplianceExtendedModule.sol
+
+
+
+ 100.0 %
+ 24 / 24
+ 100.0 %
+ 7 / 7
+ 100.0 %
+ 5 / 5
+
diff --git a/doc/coverage/coverage/src/modules/index.html b/doc/coverage/coverage/src/modules/index.html
index 164da3f..ac7a2f7 100644
--- a/doc/coverage/coverage/src/modules/index.html
+++ b/doc/coverage/coverage/src/modules/index.html
@@ -31,27 +31,27 @@
-
-
-
+
+
+
-
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -81,17 +81,29 @@
Functions
Branches
+
+ ERC3643ComplianceExtendedModule.sol
+
+
+
+ 100.0 %
+ 24 / 24
+ 100.0 %
+ 7 / 7
+ 100.0 %
+ 5 / 5
+
ERC3643ComplianceModule.sol
-
+
- 91.2 %
- 31 / 34
- 83.3 %
- 10 / 12
- 85.7 %
- 12 / 14
+ 93.3 %
+ 28 / 30
+ 81.8 %
+ 9 / 11
+ 84.6 %
+ 11 / 13
RulesManagementModule.sol
diff --git a/doc/coverage/lcov.info b/doc/coverage/lcov.info
index f9c8faf..ef3e869 100644
--- a/doc/coverage/lcov.info
+++ b/doc/coverage/lcov.info
@@ -1,104 +1,105 @@
TN:
SF:src/RuleEngineBase.sol
-DA:42,5
-FN:42,RuleEngineBase.transferred.0
+DA:43,5
+FN:43,RuleEngineBase.transferred.0
FNDA:5,RuleEngineBase.transferred.0
-DA:49,4
-DA:55,18
-FN:55,RuleEngineBase.transferred.1
+DA:50,4
+DA:56,18
+FN:56,RuleEngineBase.transferred.1
FNDA:18,RuleEngineBase.transferred.1
-DA:61,16
-DA:65,4
-FN:65,RuleEngineBase.created
+DA:62,16
+DA:66,4
+FN:66,RuleEngineBase.created
FNDA:4,RuleEngineBase.created
-DA:66,2
-DA:70,4
-FN:70,RuleEngineBase.destroyed
+DA:67,2
+DA:71,4
+FN:71,RuleEngineBase.destroyed
FNDA:4,RuleEngineBase.destroyed
-DA:71,2
-DA:83,34
-FN:83,RuleEngineBase.detectTransferRestriction
+DA:72,2
+DA:84,34
+FN:84,RuleEngineBase.detectTransferRestriction
FNDA:34,RuleEngineBase.detectTransferRestriction
-DA:90,59
-DA:96,18
-FN:96,RuleEngineBase.detectTransferRestrictionFrom
+DA:91,59
+DA:97,18
+FN:97,RuleEngineBase.detectTransferRestrictionFrom
FNDA:18,RuleEngineBase.detectTransferRestrictionFrom
-DA:103,39
-DA:109,19
-FN:109,RuleEngineBase.messageForTransferRestriction
+DA:104,39
+DA:110,19
+FN:110,RuleEngineBase.messageForTransferRestriction
FNDA:19,RuleEngineBase.messageForTransferRestriction
-DA:116,19
-DA:122,25
-FN:122,RuleEngineBase.canTransfer
+DA:117,19
+DA:123,25
+FN:123,RuleEngineBase.canTransfer
FNDA:25,RuleEngineBase.canTransfer
-DA:129,25
-DA:135,21
-FN:135,RuleEngineBase.canTransferFrom
+DA:130,25
+DA:136,21
+FN:136,RuleEngineBase.canTransferFrom
FNDA:21,RuleEngineBase.canTransferFrom
-DA:142,21
-DA:148,59
-FN:148,RuleEngineBase._detectTransferRestriction
-FNDA:59,RuleEngineBase._detectTransferRestriction
+DA:143,21
DA:149,59
+FN:149,RuleEngineBase._detectTransferRestriction
+FNDA:59,RuleEngineBase._detectTransferRestriction
DA:150,59
DA:151,59
DA:152,59
-BRDA:152,0,0,43
-DA:153,43
-DA:156,16
-DA:159,39
-FN:159,RuleEngineBase._detectTransferRestrictionFrom
+DA:153,59
+BRDA:153,0,0,43
+DA:154,43
+DA:157,16
+DA:160,39
+FN:160,RuleEngineBase._detectTransferRestrictionFrom
FNDA:39,RuleEngineBase._detectTransferRestrictionFrom
-DA:165,39
DA:166,39
DA:167,39
DA:168,39
-BRDA:168,1,0,29
-DA:169,29
-DA:172,10
-DA:181,19
-FN:181,RuleEngineBase._messageForTransferRestriction
-FNDA:19,RuleEngineBase._messageForTransferRestriction
+DA:169,39
+BRDA:169,1,0,29
+DA:170,29
+DA:173,10
DA:182,19
+FN:182,RuleEngineBase._messageForTransferRestriction
+FNDA:19,RuleEngineBase._messageForTransferRestriction
DA:183,19
-DA:184,16
-BRDA:184,2,0,14
-DA:185,14
-DA:188,5
-DA:194,253
-FN:194,RuleEngineBase._checkRule
-FNDA:253,RuleEngineBase._checkRule
+DA:184,19
+DA:185,16
+BRDA:185,2,0,14
+DA:186,14
+DA:189,5
DA:195,253
-DA:196,244
-BRDA:196,3,0,6
-DA:197,6
-DA:205,46
-FN:205,RuleEngineBase._supportsRuleEngineBaseInterface
-FNDA:46,RuleEngineBase._supportsRuleEngineBaseInterface
-DA:206,46
-DA:207,41
-DA:208,31
-DA:209,26
-DA:210,21
+FN:195,RuleEngineBase._checkRule
+FNDA:253,RuleEngineBase._checkRule
+DA:196,253
+DA:197,244
+BRDA:197,3,0,6
+DA:198,6
+DA:206,56
+FN:206,RuleEngineBase._supportsRuleEngineBaseInterface
+FNDA:56,RuleEngineBase._supportsRuleEngineBaseInterface
+DA:207,56
+DA:208,51
+DA:209,41
+DA:210,36
+DA:211,31
+DA:212,21
FNF:14
FNH:14
-LF:48
-LH:48
+LF:49
+LH:49
BRF:4
BRH:4
end_of_record
TN:
SF:src/RuleEngineOwnableShared.sol
-DA:22,128
+DA:22,175
FN:22,RuleEngineOwnableShared.constructor
-FNDA:128,RuleEngineOwnableShared.constructor
-DA:23,128
+FNDA:175,RuleEngineOwnableShared.constructor
+DA:23,175
BRDA:23,0,0,1
DA:24,1
-DA:29,29
+DA:29,35
FN:29,RuleEngineOwnableShared.supportsInterface
-FNDA:29,RuleEngineOwnableShared.supportsInterface
-DA:30,29
+FNDA:35,RuleEngineOwnableShared.supportsInterface
+DA:30,35
DA:31,5
DA:37,10
FN:37,RuleEngineOwnableShared._checkOwnershipTransferTarget
@@ -106,18 +107,18 @@ FNDA:10,RuleEngineOwnableShared._checkOwnershipTransferTarget
DA:38,10
BRDA:38,1,0,2
DA:39,2
-DA:50,181
+DA:50,249
FN:50,RuleEngineOwnableShared._msgSender
-FNDA:181,RuleEngineOwnableShared._msgSender
-DA:51,181
+FNDA:249,RuleEngineOwnableShared._msgSender
+DA:51,249
DA:57,2
FN:57,RuleEngineOwnableShared._msgData
FNDA:2,RuleEngineOwnableShared._msgData
DA:58,2
-DA:64,183
+DA:64,251
FN:64,RuleEngineOwnableShared._contextSuffixLength
-FNDA:183,RuleEngineOwnableShared._contextSuffixLength
-DA:65,183
+FNDA:251,RuleEngineOwnableShared._contextSuffixLength
+DA:65,251
FNF:6
FNH:6
LF:15
@@ -127,56 +128,56 @@ BRH:2
end_of_record
TN:
SF:src/deployment/RuleEngine.sol
-DA:27,146
+DA:27,165
FN:27,RuleEngine.constructor
-FNDA:146,RuleEngine.constructor
-DA:30,146
+FNDA:165,RuleEngine.constructor
+DA:30,165
BRDA:30,0,0,1
DA:31,1
-DA:33,145
+DA:33,164
BRDA:33,1,0,31
DA:34,31
-DA:36,145
-DA:44,22
+DA:36,164
+DA:44,39
FN:44,RuleEngine.grantRole
-FNDA:22,RuleEngine.grantRole
-DA:45,22
+FNDA:39,RuleEngine.grantRole
+DA:45,39
BRDA:45,2,0,3
DA:46,3
-DA:48,19
-DA:55,154
+DA:48,36
+DA:55,173
FN:55,RuleEngine.hasRole
-FNDA:154,RuleEngine.hasRole
-DA:62,413
-BRDA:62,3,0,219
-BRDA:62,3,1,194
-DA:63,219
-DA:65,194
-DA:70,17
+FNDA:173,RuleEngine.hasRole
+DA:62,490
+BRDA:62,3,0,236
+BRDA:62,3,1,254
+DA:63,236
+DA:65,254
+DA:70,21
FN:70,RuleEngine.supportsInterface
-FNDA:17,RuleEngine.supportsInterface
-DA:77,17
-DA:83,18
+FNDA:21,RuleEngine.supportsInterface
+DA:77,21
+DA:83,42
FN:83,RuleEngine._onlyComplianceManager
-FNDA:18,RuleEngine._onlyComplianceManager
+FNDA:42,RuleEngine._onlyComplianceManager
DA:84,196
FN:84,RuleEngine._onlyRulesManager
FNDA:196,RuleEngine._onlyRulesManager
DA:85,5
FN:85,RuleEngine._onlyRulesLimitManager
FNDA:5,RuleEngine._onlyRulesLimitManager
-DA:90,450
+DA:90,535
FN:90,RuleEngine._msgSender
-FNDA:450,RuleEngine._msgSender
-DA:91,450
+FNDA:535,RuleEngine._msgSender
+DA:91,535
DA:97,1
FN:97,RuleEngine._msgData
FNDA:1,RuleEngine._msgData
DA:98,1
-DA:104,451
+DA:104,536
FN:104,RuleEngine._contextSuffixLength
-FNDA:451,RuleEngine._contextSuffixLength
-DA:105,451
+FNDA:536,RuleEngine._contextSuffixLength
+DA:105,536
FNF:10
FNH:10
LF:25
@@ -192,26 +193,26 @@ FNDA:65,RuleEngineOwnable._onlyRulesManager
DA:28,2
FN:28,RuleEngineOwnable._onlyRulesLimitManager
FNDA:2,RuleEngineOwnable._onlyRulesLimitManager
-DA:33,22
+DA:33,46
FN:33,RuleEngineOwnable._onlyComplianceManager
-FNDA:22,RuleEngineOwnable._onlyComplianceManager
+FNDA:46,RuleEngineOwnable._onlyComplianceManager
DA:39,5
FN:39,RuleEngineOwnable.transferOwnership
FNDA:5,RuleEngineOwnable.transferOwnership
DA:40,4
DA:41,3
-DA:47,146
+DA:47,181
FN:47,RuleEngineOwnable._msgSender
-FNDA:146,RuleEngineOwnable._msgSender
-DA:48,146
+FNDA:181,RuleEngineOwnable._msgSender
+DA:48,181
DA:54,1
FN:54,RuleEngineOwnable._msgData
FNDA:1,RuleEngineOwnable._msgData
DA:55,1
-DA:61,147
+DA:61,182
FN:61,RuleEngineOwnable._contextSuffixLength
-FNDA:147,RuleEngineOwnable._contextSuffixLength
-DA:62,147
+FNDA:182,RuleEngineOwnable._contextSuffixLength
+DA:62,182
FNF:7
FNH:7
LF:12
@@ -221,119 +222,169 @@ BRH:0
end_of_record
TN:
SF:src/deployment/RuleEngineOwnable2Step.sol
-DA:29,5
-FN:29,RuleEngineOwnable2Step._onlyRulesManager
+DA:30,5
+FN:30,RuleEngineOwnable2Step._onlyRulesManager
FNDA:5,RuleEngineOwnable2Step._onlyRulesManager
-DA:30,2
-FN:30,RuleEngineOwnable2Step._onlyRulesLimitManager
+DA:31,2
+FN:31,RuleEngineOwnable2Step._onlyRulesLimitManager
FNDA:2,RuleEngineOwnable2Step._onlyRulesLimitManager
-DA:35,3
-FN:35,RuleEngineOwnable2Step._onlyComplianceManager
-FNDA:3,RuleEngineOwnable2Step._onlyComplianceManager
-DA:41,6
-FN:41,RuleEngineOwnable2Step.transferOwnership
-FNDA:6,RuleEngineOwnable2Step.transferOwnership
+DA:36,25
+FN:36,RuleEngineOwnable2Step._onlyComplianceManager
+FNDA:25,RuleEngineOwnable2Step._onlyComplianceManager
DA:42,6
-DA:43,5
-DA:49,35
-FN:49,RuleEngineOwnable2Step._msgSender
-FNDA:35,RuleEngineOwnable2Step._msgSender
-DA:50,35
-DA:56,1
-FN:56,RuleEngineOwnable2Step._msgData
+FN:42,RuleEngineOwnable2Step.transferOwnership
+FNDA:6,RuleEngineOwnable2Step.transferOwnership
+DA:43,6
+DA:44,5
+DA:48,13
+FN:48,RuleEngineOwnable2Step.supportsInterface
+FNDA:13,RuleEngineOwnable2Step.supportsInterface
+DA:49,13
+DA:50,11
+DA:56,68
+FN:56,RuleEngineOwnable2Step._msgSender
+FNDA:68,RuleEngineOwnable2Step._msgSender
+DA:57,68
+DA:63,1
+FN:63,RuleEngineOwnable2Step._msgData
FNDA:1,RuleEngineOwnable2Step._msgData
-DA:57,1
-DA:63,36
-FN:63,RuleEngineOwnable2Step._contextSuffixLength
-FNDA:36,RuleEngineOwnable2Step._contextSuffixLength
-DA:64,36
-FNF:7
-FNH:7
-LF:12
-LH:12
+DA:64,1
+DA:70,69
+FN:70,RuleEngineOwnable2Step._contextSuffixLength
+FNDA:69,RuleEngineOwnable2Step._contextSuffixLength
+DA:71,69
+FNF:8
+FNH:8
+LF:15
+LH:15
BRF:0
BRH:0
end_of_record
TN:
+SF:src/modules/ERC3643ComplianceExtendedModule.sol
+DA:16,18
+FN:16,ERC3643ComplianceExtendedModule.bindTokens
+FNDA:18,ERC3643ComplianceExtendedModule.bindTokens
+DA:17,15
+DA:18,24
+DA:23,9
+FN:23,ERC3643ComplianceExtendedModule.unbindTokens
+FNDA:9,ERC3643ComplianceExtendedModule.unbindTokens
+DA:24,6
+DA:25,12
+DA:30,14
+FN:30,ERC3643ComplianceExtendedModule.setTokenSelfBindingApproval
+FNDA:14,ERC3643ComplianceExtendedModule.setTokenSelfBindingApproval
+DA:31,11
+BRDA:31,0,0,3
+BRDA:31,0,1,8
+DA:32,8
+DA:33,8
+DA:37,12
+FN:37,ERC3643ComplianceExtendedModule.setTokenSelfBindingApprovalBatch
+FNDA:12,ERC3643ComplianceExtendedModule.setTokenSelfBindingApprovalBatch
+DA:43,9
+DA:44,18
+DA:45,18
+BRDA:45,1,0,3
+BRDA:45,1,1,15
+DA:46,15
+DA:48,6
+DA:52,6
+FN:52,ERC3643ComplianceExtendedModule.isTokenSelfBindingApproved
+FNDA:6,ERC3643ComplianceExtendedModule.isTokenSelfBindingApproved
+DA:53,6
+DA:57,4
+FN:57,ERC3643ComplianceExtendedModule.getTokenBounds
+FNDA:4,ERC3643ComplianceExtendedModule.getTokenBounds
+DA:58,4
+DA:65,72
+FN:65,ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange
+FNDA:72,ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange
+DA:66,72
+BRDA:66,2,0,72
+DA:67,72
+DA:69,60
+FNF:7
+FNH:7
+LF:24
+LH:24
+BRF:5
+BRH:5
+end_of_record
+TN:
SF:src/modules/ERC3643ComplianceModule.sol
DA:29,5
FN:29,ERC3643ComplianceModule.onlyBoundToken
FNDA:5,ERC3643ComplianceModule.onlyBoundToken
DA:30,5
-DA:34,0
+DA:34,9
FN:34,ERC3643ComplianceModule.onlyComplianceManager
-FNDA:0,ERC3643ComplianceModule.onlyComplianceManager
-DA:35,0
-DA:52,38
-FN:52,ERC3643ComplianceModule.bindToken
-FNDA:38,ERC3643ComplianceModule.bindToken
-DA:53,38
-DA:54,34
-DA:65,16
-FN:65,ERC3643ComplianceModule.unbindToken
-FNDA:16,ERC3643ComplianceModule.unbindToken
-DA:66,16
-DA:67,12
-DA:71,21
-FN:71,ERC3643ComplianceModule.isTokenBound
-FNDA:21,ERC3643ComplianceModule.isTokenBound
-DA:72,21
-DA:76,5
-FN:76,ERC3643ComplianceModule.getTokenBound
+FNDA:9,ERC3643ComplianceModule.onlyComplianceManager
+DA:35,9
+DA:50,52
+FN:50,ERC3643ComplianceModule.bindToken
+FNDA:52,ERC3643ComplianceModule.bindToken
+DA:51,52
+DA:52,45
+DA:61,20
+FN:61,ERC3643ComplianceModule.unbindToken
+FNDA:20,ERC3643ComplianceModule.unbindToken
+DA:62,20
+DA:63,13
+DA:67,37
+FN:67,ERC3643ComplianceModule.isTokenBound
+FNDA:37,ERC3643ComplianceModule.isTokenBound
+DA:68,37
+DA:72,5
+FN:72,ERC3643ComplianceModule.getTokenBound
FNDA:5,ERC3643ComplianceModule.getTokenBound
-DA:77,5
-BRDA:77,0,0,3
-BRDA:77,0,1,2
-DA:80,3
-DA:82,2
-DA:87,4
-FN:87,ERC3643ComplianceModule.getTokenBounds
-FNDA:4,ERC3643ComplianceModule.getTokenBounds
-DA:88,4
-DA:95,12
-FN:95,ERC3643ComplianceModule._unbindToken
-FNDA:12,ERC3643ComplianceModule._unbindToken
-DA:96,12
-BRDA:96,1,0,2
-BRDA:96,1,1,10
-DA:98,10
-BRDA:98,2,0,-
-BRDA:98,2,1,10
-DA:100,10
-DA:103,66
-FN:103,ERC3643ComplianceModule._bindToken
-FNDA:66,ERC3643ComplianceModule._bindToken
-DA:104,66
-BRDA:104,3,0,2
-BRDA:104,3,1,64
-DA:105,64
-BRDA:105,4,0,2
-BRDA:105,4,1,62
-DA:107,62
-BRDA:107,5,0,-
-BRDA:107,5,1,62
-DA:108,62
-DA:111,31
-FN:111,ERC3643ComplianceModule._checkBoundToken
+DA:73,5
+BRDA:73,0,0,3
+BRDA:73,0,1,2
+DA:76,3
+DA:78,2
+DA:86,25
+FN:86,ERC3643ComplianceModule._unbindToken
+FNDA:25,ERC3643ComplianceModule._unbindToken
+DA:87,25
+BRDA:87,1,0,5
+BRDA:87,1,1,20
+DA:89,20
+BRDA:89,2,0,-
+BRDA:89,2,1,20
+DA:91,20
+DA:94,101
+FN:94,ERC3643ComplianceModule._bindToken
+FNDA:101,ERC3643ComplianceModule._bindToken
+DA:95,101
+BRDA:95,3,0,5
+BRDA:95,3,1,96
+DA:96,96
+BRDA:96,4,0,5
+BRDA:96,4,1,91
+DA:98,91
+BRDA:98,5,0,-
+BRDA:98,5,1,91
+DA:99,91
+DA:102,31
+FN:102,ERC3643ComplianceModule._checkBoundToken
FNDA:31,ERC3643ComplianceModule._checkBoundToken
-DA:112,31
-BRDA:112,6,0,7
-DA:113,7
-DA:121,54
-FN:121,ERC3643ComplianceModule._authorizeComplianceBindingChange
-FNDA:54,ERC3643ComplianceModule._authorizeComplianceBindingChange
-DA:122,54
-BRDA:122,7,0,43
-DA:123,43
-DA:127,0
-FN:127,ERC3643ComplianceModule._onlyComplianceManager
+DA:103,31
+BRDA:103,6,0,7
+DA:104,7
+DA:108,0
+FN:108,ERC3643ComplianceModule._authorizeComplianceBindingChange
+FNDA:0,ERC3643ComplianceModule._authorizeComplianceBindingChange
+DA:109,0
+FN:109,ERC3643ComplianceModule._onlyComplianceManager
FNDA:0,ERC3643ComplianceModule._onlyComplianceManager
-FNF:12
-FNH:10
-LF:34
-LH:31
-BRF:14
-BRH:12
+FNF:11
+FNH:9
+LF:30
+LH:28
+BRF:13
+BRH:11
end_of_record
TN:
SF:src/modules/RulesManagementModule.sol
diff --git a/doc/security/audits/tools/aderyn-report.md b/doc/security/audits/tools/aderyn-report.md
index 9619842..f2e6879 100644
--- a/doc/security/audits/tools/aderyn-report.md
+++ b/doc/security/audits/tools/aderyn-report.md
@@ -24,33 +24,36 @@ This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a stati
| Key | Value |
| --- | --- |
-| .sol Files | 18 |
-| Total nSLOC | 542 |
+| .sol Files | 21 |
+| Total nSLOC | 607 |
## Files Details
| Filepath | nSLOC |
| --- | --- |
-| src/RuleEngineBase.sol | 138 |
+| src/RuleEngineBase.sol | 140 |
| src/RuleEngineOwnableShared.sol | 33 |
| src/deployment/RuleEngine.sol | 63 |
| src/deployment/RuleEngineOwnable.sol | 26 |
-| src/deployment/RuleEngineOwnable2Step.sol | 27 |
-| src/interfaces/IERC3643Compliance.sol | 13 |
+| src/deployment/RuleEngineOwnable2Step.sol | 32 |
+| src/interfaces/IERC3643Compliance.sol | 12 |
+| src/interfaces/IERC3643ComplianceExtended.sol | 12 |
| src/interfaces/IRule.sol | 5 |
| src/interfaces/IRulesManagementModule.sol | 14 |
| src/modules/ERC2771ModuleStandalone.sol | 6 |
-| src/modules/ERC3643ComplianceModule.sol | 65 |
+| src/modules/ERC3643ComplianceExtendedModule.sol | 48 |
+| src/modules/ERC3643ComplianceModule.sol | 58 |
| src/modules/RulesManagementModule.sol | 105 |
| src/modules/VersionModule.sol | 8 |
-| src/modules/library/ComplianceInterfaceId.sol | 5 |
+| src/modules/library/ComplianceInterfaceId.sol | 6 |
| src/modules/library/ERC1404InterfaceId.sol | 4 |
+| src/modules/library/Ownable2StepInterfaceId.sol | 4 |
| src/modules/library/OwnableInterfaceId.sol | 4 |
| src/modules/library/RuleEngineInvariantStorage.sol | 5 |
| src/modules/library/RuleInterfaceId.sol | 4 |
-| src/modules/library/RulesManagementModuleInvariantStorage.sol | 17 |
-| **Total** | **542** |
+| src/modules/library/RulesManagementModuleInvariantStorage.sol | 18 |
+| **Total** | **607** |
## Issue Summary
@@ -124,31 +127,31 @@ Contracts have owners with privileged rights to perform admin tasks and need to
function transferOwnership(address newOwner) public virtual override onlyOwner {
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 14](src/deployment/RuleEngineOwnable2Step.sol#L14)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 15](src/deployment/RuleEngineOwnable2Step.sol#L15)
```solidity
contract RuleEngineOwnable2Step is RuleEngineOwnableShared, Ownable2Step {
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 29](src/deployment/RuleEngineOwnable2Step.sol#L29)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 30](src/deployment/RuleEngineOwnable2Step.sol#L30)
```solidity
function _onlyRulesManager() internal virtual override onlyOwner {}
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 30](src/deployment/RuleEngineOwnable2Step.sol#L30)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 31](src/deployment/RuleEngineOwnable2Step.sol#L31)
```solidity
function _onlyRulesLimitManager() internal virtual override onlyOwner {}
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 35](src/deployment/RuleEngineOwnable2Step.sol#L35)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 36](src/deployment/RuleEngineOwnable2Step.sol#L36)
```solidity
function _onlyComplianceManager() internal virtual override onlyOwner {}
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 41](src/deployment/RuleEngineOwnable2Step.sol#L41)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 42](src/deployment/RuleEngineOwnable2Step.sol#L42)
```solidity
function transferOwnership(address newOwner) public virtual override onlyOwner {
@@ -162,7 +165,7 @@ Contracts have owners with privileged rights to perform admin tasks and need to
Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of `pragma solidity ^0.8.0;`, use `pragma solidity 0.8.0;`
-14 Found Instances
+16 Found Instances
- Found in src/RuleEngineBase.sol [Line: 3](src/RuleEngineBase.sol#L3)
@@ -201,6 +204,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid
pragma solidity ^0.8.20;
```
+- Found in src/interfaces/IERC3643ComplianceExtended.sol [Line: 3](src/interfaces/IERC3643ComplianceExtended.sol#L3)
+
+ ```solidity
+ pragma solidity ^0.8.20;
+ ```
+
- Found in src/interfaces/IRule.sol [Line: 3](src/interfaces/IRule.sol#L3)
```solidity
@@ -219,6 +228,12 @@ Consider using a specific version of Solidity in your contracts instead of a wid
pragma solidity ^0.8.20;
```
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 3](src/modules/ERC3643ComplianceExtendedModule.sol#L3)
+
+ ```solidity
+ pragma solidity ^0.8.20;
+ ```
+
- Found in src/modules/ERC3643ComplianceModule.sol [Line: 3](src/modules/ERC3643ComplianceModule.sol#L3)
```solidity
@@ -257,7 +272,7 @@ Consider using a specific version of Solidity in your contracts instead of a wid
Solc compiler version 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain other than mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail.
-18 Found Instances
+21 Found Instances
- Found in src/RuleEngineBase.sol [Line: 3](src/RuleEngineBase.sol#L3)
@@ -296,6 +311,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai
pragma solidity ^0.8.20;
```
+- Found in src/interfaces/IERC3643ComplianceExtended.sol [Line: 3](src/interfaces/IERC3643ComplianceExtended.sol#L3)
+
+ ```solidity
+ pragma solidity ^0.8.20;
+ ```
+
- Found in src/interfaces/IRule.sol [Line: 3](src/interfaces/IRule.sol#L3)
```solidity
@@ -314,6 +335,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai
pragma solidity ^0.8.20;
```
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 3](src/modules/ERC3643ComplianceExtendedModule.sol#L3)
+
+ ```solidity
+ pragma solidity ^0.8.20;
+ ```
+
- Found in src/modules/ERC3643ComplianceModule.sol [Line: 3](src/modules/ERC3643ComplianceModule.sol#L3)
```solidity
@@ -344,6 +371,12 @@ Solc compiler version 0.8.20 switches the default target EVM version to Shanghai
pragma solidity ^0.8.20;
```
+- Found in src/modules/library/Ownable2StepInterfaceId.sol [Line: 3](src/modules/library/Ownable2StepInterfaceId.sol#L3)
+
+ ```solidity
+ pragma solidity ^0.8.20;
+ ```
+
- Found in src/modules/library/OwnableInterfaceId.sol [Line: 3](src/modules/library/OwnableInterfaceId.sol#L3)
```solidity
@@ -432,19 +465,19 @@ Consider removing empty blocks.
function _onlyComplianceManager() internal virtual override onlyOwner {}
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 29](src/deployment/RuleEngineOwnable2Step.sol#L29)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 30](src/deployment/RuleEngineOwnable2Step.sol#L30)
```solidity
function _onlyRulesManager() internal virtual override onlyOwner {}
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 30](src/deployment/RuleEngineOwnable2Step.sol#L30)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 31](src/deployment/RuleEngineOwnable2Step.sol#L31)
```solidity
function _onlyRulesLimitManager() internal virtual override onlyOwner {}
```
-- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 35](src/deployment/RuleEngineOwnable2Step.sol#L35)
+- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 36](src/deployment/RuleEngineOwnable2Step.sol#L36)
```solidity
function _onlyComplianceManager() internal virtual override onlyOwner {}
@@ -458,8 +491,26 @@ Consider removing empty blocks.
Avoid `require` / `revert` statements in a loop because a single bad item can cause the whole transaction to fail. It's better to forgive on fail and return failed elements post processing of the loop
-1 Found Instances
+4 Found Instances
+
+
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 17](src/modules/ERC3643ComplianceExtendedModule.sol#L17)
+
+ ```solidity
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ ```
+
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 24](src/modules/ERC3643ComplianceExtendedModule.sol#L24)
+ ```solidity
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ ```
+
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 43](src/modules/ERC3643ComplianceExtendedModule.sol#L43)
+
+ ```solidity
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ ```
- Found in src/modules/RulesManagementModule.sol [Line: 60](src/modules/RulesManagementModule.sol#L60)
@@ -475,8 +526,26 @@ Avoid `require` / `revert` statements in a loop because a single bad item can ca
Invoking `SSTORE` operations in loops may waste gas. Use a local variable to hold the loop computation result.
-1 Found Instances
+4 Found Instances
+
+
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 17](src/modules/ERC3643ComplianceExtendedModule.sol#L17)
+
+ ```solidity
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ ```
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 24](src/modules/ERC3643ComplianceExtendedModule.sol#L24)
+
+ ```solidity
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ ```
+
+- Found in src/modules/ERC3643ComplianceExtendedModule.sol [Line: 43](src/modules/ERC3643ComplianceExtendedModule.sol#L43)
+
+ ```solidity
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ ```
- Found in src/modules/RulesManagementModule.sol [Line: 60](src/modules/RulesManagementModule.sol#L60)
diff --git a/doc/security/audits/tools/slither-report-feedback.md b/doc/security/audits/tools/slither-report-feedback.md
index f10c7f5..402cf43 100644
--- a/doc/security/audits/tools/slither-report-feedback.md
+++ b/doc/security/audits/tools/slither-report-feedback.md
@@ -2,16 +2,14 @@
**Tool:** [Slither](https://github.com/crytic/slither)
**Report file:** `doc/security/audits/tools/slither-report.md`
-**Assessment date:** 2026-05-05
+**Assessment date:** 2026-05-06
## Summary
| IDs | Detector | Tool Impact | Assessment | Decision |
|-----|----------|-------------|------------|----------|
| 0-9 | `calls-loop` | Low | Inherent to pluggable rule-engine dispatch architecture | Accepted by design |
-| 10 | `dead-code` (`RuleEngineOwnable2Step._msgData`) | Informational | False positive in context; required override in ERC2771/context inheritance chain | Accepted / no action |
-| 11 | `naming-convention` (CMTAT dependency enum) | Informational | External dependency style issue, not project code risk | Ignored |
-| 12-13 | `unindexed-event-address` | Informational | Valid optimization note, but ABI-breaking to change now | Deferred |
+| 10-12 | `unindexed-event-address` | Informational | Valid optimization note, but ABI-breaking to change now | Deferred |
## Detailed triage
@@ -19,14 +17,8 @@
The RuleEngine intentionally iterates `_rules` and performs external rule calls for transfer checks and transfer hooks. This is core product behavior.
Risk is controlled through trusted-rule governance and documented operational limits on rule count. No direct security defect is introduced by this detector output.
-### ID 10: `dead-code` on `RuleEngineOwnable2Step._msgData`
-This override is part of required multiple-inheritance context resolution (`RuleEngineOwnableShared` + `Context`) for ERC2771-compatible message handling. Static analysis can mark it as unused even though it is part of the contract’s context surface and override chain. Keep as-is.
-
-### ID 11: `naming-convention` in `lib/CMTAT/.../draft-IERC1404.sol`
-Finding targets vendored dependency naming style. This is not a vulnerability and should not be patched locally unless forking upstream conventions intentionally.
-
-### IDs 12-13: `unindexed-event-address`
-`TokenBound(address)` / `TokenUnbound(address)` could add `indexed` for cheaper filtering, but doing so changes event signature and breaks existing consumers. Given low event frequency and backward-compatibility requirements, defer to a planned major interface revision.
+### IDs 10-12: `unindexed-event-address`
+`TokenBound(address)` and `TokenUnbound(address)` could add `indexed` for cheaper filtering, but doing so changes event signatures and breaks existing consumers. `TokenSelfBindingApprovalSet(address,bool)` has been updated to index `token` (extension interface, acceptable change in this release). Keep deferring `TokenBound`/`TokenUnbound` to a planned major ERC-3643 interface revision.
## Conclusion
No actionable security fixes are required from this Slither run. Findings are architectural by-design, dependency style-only, or compatibility tradeoffs.
diff --git a/doc/security/audits/tools/slither-report.md b/doc/security/audits/tools/slither-report.md
index 842bbfa..692b43a 100644
--- a/doc/security/audits/tools/slither-report.md
+++ b/doc/security/audits/tools/slither-report.md
@@ -1,9 +1,7 @@
**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results.
Summary
- [calls-loop](#calls-loop) (10 results) (Low)
- - [dead-code](#dead-code) (1 results) (Informational)
- - [naming-convention](#naming-convention) (1 results) (Informational)
- - [unindexed-event-address](#unindexed-event-address) (2 results) (Informational)
+ - [unindexed-event-address](#unindexed-event-address) (3 results) (Informational)
## calls-loop
Impact: Low
Confidence: Medium
@@ -32,91 +30,79 @@ src/modules/RulesManagementModule.sol#L201-L206
- [ ] ID-3
-[RuleEngineBase._messageForTransferRestriction(uint8)](src/RuleEngineBase.sol#L181-L189) has external calls inside a loop: [IRule(rule(i)).messageForTransferRestriction(restrictionCode)](src/RuleEngineBase.sol#L185)
+[RuleEngineBase._detectTransferRestriction(address,address,uint256)](src/RuleEngineBase.sol#L149-L158) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestriction(from,to,value)](src/RuleEngineBase.sol#L152)
Calls stack containing the loop:
- RuleEngineBase.messageForTransferRestriction(uint8)
+ RuleEngineBase.canTransfer(address,address,uint256)
+ RuleEngineBase.detectTransferRestriction(address,address,uint256)
-src/RuleEngineBase.sol#L181-L189
+src/RuleEngineBase.sol#L149-L158
- [ ] ID-4
-[RulesManagementModule._transferred(address,address,uint256)](src/modules/RulesManagementModule.sol#L201-L206) has external calls inside a loop: [IRule(_rules.at(i)).transferred(from,to,value)](src/modules/RulesManagementModule.sol#L204)
+[RuleEngineBase._messageForTransferRestriction(uint8)](src/RuleEngineBase.sol#L182-L190) has external calls inside a loop: [IRule(rule(i)).messageForTransferRestriction(restrictionCode)](src/RuleEngineBase.sol#L186)
Calls stack containing the loop:
- RuleEngineBase.created(address,uint256)
+ RuleEngineBase.messageForTransferRestriction(uint8)
-src/modules/RulesManagementModule.sol#L201-L206
+src/RuleEngineBase.sol#L182-L190
- [ ] ID-5
-[RuleEngineBase._detectTransferRestrictionFrom(address,address,address,uint256)](src/RuleEngineBase.sol#L159-L173) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender,from,to,value)](src/RuleEngineBase.sol#L167)
+[RuleEngineBase._messageForTransferRestriction(uint8)](src/RuleEngineBase.sol#L182-L190) has external calls inside a loop: [IRule(rule(i)).canReturnTransferRestrictionCode(restrictionCode)](src/RuleEngineBase.sol#L185)
Calls stack containing the loop:
- RuleEngineBase.detectTransferRestrictionFrom(address,address,address,uint256)
+ RuleEngineBase.messageForTransferRestriction(uint8)
-src/RuleEngineBase.sol#L159-L173
+src/RuleEngineBase.sol#L182-L190
- [ ] ID-6
-[RuleEngineBase._messageForTransferRestriction(uint8)](src/RuleEngineBase.sol#L181-L189) has external calls inside a loop: [IRule(rule(i)).canReturnTransferRestrictionCode(restrictionCode)](src/RuleEngineBase.sol#L184)
+[RuleEngineBase._detectTransferRestrictionFrom(address,address,address,uint256)](src/RuleEngineBase.sol#L160-L174) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender,from,to,value)](src/RuleEngineBase.sol#L168)
Calls stack containing the loop:
- RuleEngineBase.messageForTransferRestriction(uint8)
+ RuleEngineBase.canTransferFrom(address,address,address,uint256)
+ RuleEngineBase.detectTransferRestrictionFrom(address,address,address,uint256)
-src/RuleEngineBase.sol#L181-L189
+src/RuleEngineBase.sol#L160-L174
- [ ] ID-7
-[RuleEngineBase._detectTransferRestriction(address,address,uint256)](src/RuleEngineBase.sol#L148-L157) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestriction(from,to,value)](src/RuleEngineBase.sol#L151)
+[RulesManagementModule._transferred(address,address,uint256)](src/modules/RulesManagementModule.sol#L201-L206) has external calls inside a loop: [IRule(_rules.at(i)).transferred(from,to,value)](src/modules/RulesManagementModule.sol#L204)
Calls stack containing the loop:
- RuleEngineBase.detectTransferRestriction(address,address,uint256)
+ RuleEngineBase.created(address,uint256)
-src/RuleEngineBase.sol#L148-L157
+src/modules/RulesManagementModule.sol#L201-L206
- [ ] ID-8
-[RuleEngineBase._detectTransferRestriction(address,address,uint256)](src/RuleEngineBase.sol#L148-L157) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestriction(from,to,value)](src/RuleEngineBase.sol#L151)
+[RuleEngineBase._detectTransferRestriction(address,address,uint256)](src/RuleEngineBase.sol#L149-L158) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestriction(from,to,value)](src/RuleEngineBase.sol#L152)
Calls stack containing the loop:
- RuleEngineBase.canTransfer(address,address,uint256)
RuleEngineBase.detectTransferRestriction(address,address,uint256)
-src/RuleEngineBase.sol#L148-L157
+src/RuleEngineBase.sol#L149-L158
- [ ] ID-9
-[RuleEngineBase._detectTransferRestrictionFrom(address,address,address,uint256)](src/RuleEngineBase.sol#L159-L173) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender,from,to,value)](src/RuleEngineBase.sol#L167)
+[RuleEngineBase._detectTransferRestrictionFrom(address,address,address,uint256)](src/RuleEngineBase.sol#L160-L174) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender,from,to,value)](src/RuleEngineBase.sol#L168)
Calls stack containing the loop:
- RuleEngineBase.canTransferFrom(address,address,address,uint256)
RuleEngineBase.detectTransferRestrictionFrom(address,address,address,uint256)
-src/RuleEngineBase.sol#L159-L173
+src/RuleEngineBase.sol#L160-L174
-## dead-code
+## unindexed-event-address
Impact: Informational
-Confidence: Medium
+Confidence: High
- [ ] ID-10
-[RuleEngineOwnable2Step._msgData()](src/deployment/RuleEngineOwnable2Step.sol#L56-L58) is never used and should be removed
+Event [IERC3643Compliance.TokenBound(address)](src/interfaces/IERC3643Compliance.sol#L14) has address parameters but no indexed parameters
-src/deployment/RuleEngineOwnable2Step.sol#L56-L58
+src/interfaces/IERC3643Compliance.sol#L14
-## naming-convention
-Impact: Informational
-Confidence: High
- [ ] ID-11
-Enum [IERC1404Extend.REJECTED_CODE_BASE](lib/CMTAT/contracts/interfaces/tokenization/draft-IERC1404.sol#L49-L57) is not in CapWords
+Event [IERC3643ComplianceExtended.TokenSelfBindingApprovalSet(address,bool)](src/interfaces/IERC3643ComplianceExtended.sol#L13) has address parameters but no indexed parameters
-lib/CMTAT/contracts/interfaces/tokenization/draft-IERC1404.sol#L49-L57
+src/interfaces/IERC3643ComplianceExtended.sol#L13
-## unindexed-event-address
-Impact: Informational
-Confidence: High
- [ ] ID-12
-Event [IERC3643Compliance.TokenBound(address)](src/interfaces/IERC3643Compliance.sol#L14) has address parameters but no indexed parameters
-
-src/interfaces/IERC3643Compliance.sol#L14
-
-
- - [ ] ID-13
Event [IERC3643Compliance.TokenUnbound(address)](src/interfaces/IERC3643Compliance.sol#L20) has address parameters but no indexed parameters
src/interfaces/IERC3643Compliance.sol#L20
diff --git a/src/RuleEngineBase.sol b/src/RuleEngineBase.sol
index 4e86787..08d8219 100644
--- a/src/RuleEngineBase.sol
+++ b/src/RuleEngineBase.sol
@@ -14,11 +14,12 @@ import {IERC3643ComplianceRead, IERC3643IComplianceContract} from "CMTAT/interfa
import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol";
/* ==== Modules === */
-import {ERC3643ComplianceModule, IERC3643Compliance} from "./modules/ERC3643ComplianceModule.sol";
+import {ERC3643ComplianceExtendedModule} from "./modules/ERC3643ComplianceExtendedModule.sol";
import {VersionModule} from "./modules/VersionModule.sol";
import {RulesManagementModule} from "./modules/RulesManagementModule.sol";
/* ==== Interface and other library === */
+import {IERC3643Compliance} from "./interfaces/IERC3643Compliance.sol";
import {IRule} from "./interfaces/IRule.sol";
import {ComplianceInterfaceId} from "./modules/library/ComplianceInterfaceId.sol";
import {ERC1404InterfaceId} from "./modules/library/ERC1404InterfaceId.sol";
@@ -31,7 +32,7 @@ import {RuleInterfaceId} from "./modules/library/RuleInterfaceId.sol";
abstract contract RuleEngineBase is
VersionModule,
RulesManagementModule,
- ERC3643ComplianceModule,
+ ERC3643ComplianceExtendedModule,
RuleEngineInvariantStorage,
IRuleEngineERC1404
{
@@ -207,6 +208,7 @@ abstract contract RuleEngineBase is
|| interfaceId == ERC1404InterfaceId.IERC1404_INTERFACE_ID
|| interfaceId == ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID
|| interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID
+ || interfaceId == ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID
|| interfaceId == ComplianceInterfaceId.IERC7551_COMPLIANCE_INTERFACE_ID;
}
}
diff --git a/src/interfaces/IERC3643Compliance.sol b/src/interfaces/IERC3643Compliance.sol
index eb965de..cc7c350 100644
--- a/src/interfaces/IERC3643Compliance.sol
+++ b/src/interfaces/IERC3643Compliance.sol
@@ -47,6 +47,7 @@ interface IERC3643Compliance is IERC3643ComplianceRead, IERC3643IComplianceContr
*/
function unbindToken(address token) external;
+
/**
* @notice Checks whether a token is currently bound to this compliance contract.
* @dev
@@ -65,16 +66,6 @@ interface IERC3643Compliance is IERC3643ComplianceRead, IERC3643IComplianceContr
*/
function getTokenBound() external view returns (address token);
- /**
- * @notice Returns all tokens currently bound to this compliance contract.
- * @dev This is a view-only function and does not modify state.
- * This function is not part of the original ERC-3643 specification
- * This operation will copy the entire storage to memory, which can be quite expensive.
- * This is designed to mostly be used by view accessors that are queried without any gas fees.
- * @return tokens An array of addresses of bound token contracts.
- */
- function getTokenBounds() external view returns (address[] memory tokens);
-
/**
* @notice Updates the compliance contract state when tokens are created (minted).
* @dev Called by the token contract when new tokens are issued to an account.
diff --git a/src/interfaces/IERC3643ComplianceExtended.sol b/src/interfaces/IERC3643ComplianceExtended.sol
new file mode 100644
index 0000000..7f334b0
--- /dev/null
+++ b/src/interfaces/IERC3643ComplianceExtended.sol
@@ -0,0 +1,73 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+import {IERC3643Compliance} from "./IERC3643Compliance.sol";
+
+interface IERC3643ComplianceExtended is IERC3643Compliance {
+ /**
+ * @notice Emitted when self-binding permission is updated for a token.
+ * @param token The token address whose self-binding permission changed.
+ * @param approved True if token self-bind/unbind is allowed, false otherwise.
+ */
+ event TokenSelfBindingApprovalSet(address indexed token, bool approved);
+ /**
+ * @notice Emitted when self-binding permission is updated in batch.
+ * @param tokens The token addresses whose self-binding permission changed.
+ * @param approved True if token self-bind/unbind is allowed, false otherwise.
+ */
+ event TokenSelfBindingApprovalBatchSet(address[] tokens, bool approved);
+
+ /**
+ * @notice Associates multiple token contracts with this compliance contract.
+ * @dev This function is not part of the original ERC-3643 compliance interface.
+ * Must be restricted by implementation-specific compliance manager access control.
+ * Reverts if any token is invalid or already bound.
+ * @param tokens The token addresses to bind.
+ */
+ function bindTokens(address[] calldata tokens) external;
+
+ /**
+ * @notice Removes the association of multiple token contracts from this compliance contract.
+ * @dev This function is not part of the original ERC-3643 compliance interface.
+ * Must be restricted by implementation-specific compliance manager access control.
+ * Reverts if any token is not currently bound.
+ * @param tokens The token addresses to unbind.
+ */
+ function unbindTokens(address[] calldata tokens) external;
+
+ /**
+ * @notice Sets whether a token is allowed to self-bind and self-unbind.
+ * @dev This function is not part of the original ERC-3643 compliance interface.
+ * Must be restricted by implementation-specific compliance manager access control.
+ * @param token The token address to configure.
+ * @param approved Whether self-binding is approved for `token`.
+ */
+ function setTokenSelfBindingApproval(address token, bool approved) external;
+
+ /**
+ * @notice Sets self-binding approval for multiple tokens in one transaction.
+ * @dev This function is not part of the original ERC-3643 compliance interface.
+ * Must be restricted by implementation-specific compliance manager access control.
+ * Reverts if any token in `tokens` is the zero address.
+ * @param tokens The token addresses to configure.
+ * @param approved Whether self-binding is approved for all provided tokens.
+ */
+ function setTokenSelfBindingApprovalBatch(address[] calldata tokens, bool approved) external;
+
+ /**
+ * @notice Returns whether a token is approved to self-bind and self-unbind.
+ * @dev This function is not part of the original ERC-3643 compliance interface.
+ * @param token The token address to query.
+ * @return approved True if self-binding is approved for `token`, false otherwise.
+ */
+ function isTokenSelfBindingApproved(address token) external view returns (bool approved);
+
+ /**
+ * @notice Returns all tokens currently bound to this compliance contract.
+ * @dev This function is not part of the original ERC-3643 compliance interface.
+ * This operation copies the entire storage set to memory and is mainly intended for off-chain reads.
+ * @return tokens An array of bound token addresses.
+ */
+ function getTokenBounds() external view returns (address[] memory tokens);
+}
diff --git a/src/mocks/IERC3643ComplianceExtendedSubset.sol b/src/mocks/IERC3643ComplianceExtendedSubset.sol
new file mode 100644
index 0000000..a211c2c
--- /dev/null
+++ b/src/mocks/IERC3643ComplianceExtendedSubset.sol
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: MPL-2.0
+pragma solidity ^0.8.20;
+
+interface IERC3643ComplianceExtendedSubset {
+ function bindTokens(address[] calldata tokens) external;
+ function unbindTokens(address[] calldata tokens) external;
+ function setTokenSelfBindingApproval(address token, bool approved) external;
+ function setTokenSelfBindingApprovalBatch(address[] calldata tokens, bool approved) external;
+ function isTokenSelfBindingApproved(address token) external view returns (bool approved);
+ function getTokenBounds() external view returns (address[] memory tokens);
+}
diff --git a/src/modules/ERC3643ComplianceExtendedModule.sol b/src/modules/ERC3643ComplianceExtendedModule.sol
new file mode 100644
index 0000000..5830a3f
--- /dev/null
+++ b/src/modules/ERC3643ComplianceExtendedModule.sol
@@ -0,0 +1,71 @@
+//SPDX-License-Identifier: MPL-2.0
+
+pragma solidity ^0.8.20;
+
+import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+/* ==== Interface and other library === */
+import {IERC3643ComplianceExtended} from "../interfaces/IERC3643ComplianceExtended.sol";
+import {ERC3643ComplianceModule} from "./ERC3643ComplianceModule.sol";
+
+abstract contract ERC3643ComplianceExtendedModule is ERC3643ComplianceModule, IERC3643ComplianceExtended {
+ using EnumerableSet for EnumerableSet.AddressSet;
+
+ mapping(address token => bool approved) private _tokenSelfBindingApproval;
+
+ /// @inheritdoc IERC3643ComplianceExtended
+ function bindTokens(address[] calldata tokens) public virtual override onlyComplianceManager {
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ _bindToken(tokens[i]);
+ }
+ }
+
+ /// @inheritdoc IERC3643ComplianceExtended
+ function unbindTokens(address[] calldata tokens) public virtual override onlyComplianceManager {
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ _unbindToken(tokens[i]);
+ }
+ }
+
+ /// @inheritdoc IERC3643ComplianceExtended
+ function setTokenSelfBindingApproval(address token, bool approved) public virtual override onlyComplianceManager {
+ require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress());
+ _tokenSelfBindingApproval[token] = approved;
+ emit TokenSelfBindingApprovalSet(token, approved);
+ }
+
+ /// @inheritdoc IERC3643ComplianceExtended
+ function setTokenSelfBindingApprovalBatch(address[] calldata tokens, bool approved)
+ public
+ virtual
+ override
+ onlyComplianceManager
+ {
+ for (uint256 i = 0; i < tokens.length; ++i) {
+ address token = tokens[i];
+ require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress());
+ _tokenSelfBindingApproval[token] = approved;
+ }
+ emit TokenSelfBindingApprovalBatchSet(tokens, approved);
+ }
+
+ /// @inheritdoc IERC3643ComplianceExtended
+ function isTokenSelfBindingApproved(address token) public view virtual override returns (bool) {
+ return _tokenSelfBindingApproval[token];
+ }
+
+ /// @inheritdoc IERC3643ComplianceExtended
+ function getTokenBounds() public view virtual override returns (address[] memory) {
+ return _boundTokens.values();
+ }
+
+ /**
+ * @dev Authorizes bind/unbind operations.
+ * Allows compliance manager, or approved token self-calls for T-REX compatibility.
+ */
+ function _authorizeComplianceBindingChange(address token) internal virtual override {
+ if (_msgSender() == token && _tokenSelfBindingApproval[token]) {
+ return;
+ }
+ _onlyComplianceManager();
+ }
+}
diff --git a/src/modules/ERC3643ComplianceModule.sol b/src/modules/ERC3643ComplianceModule.sol
index 8a48bcc..1fcad48 100644
--- a/src/modules/ERC3643ComplianceModule.sol
+++ b/src/modules/ERC3643ComplianceModule.sol
@@ -13,7 +13,7 @@ abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance {
using EnumerableSet for EnumerableSet.AddressSet;
/* ==== State Variables === */
// Token binding tracking
- EnumerableSet.AddressSet private _boundTokens;
+ EnumerableSet.AddressSet internal _boundTokens;
// Access Control
// Will not be present in the final bytecode if not used
bytes32 public constant COMPLIANCE_MANAGER_ROLE = keccak256("COMPLIANCE_MANAGER_ROLE");
@@ -46,8 +46,6 @@ abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance {
* @dev Operator warning: "multi-tenant" means one RuleEngine is shared by
* multiple token contracts. In that setup, bind only tokens that are equally
* trusted and governed together.
- * @dev T-REX compatibility: allows token self-binding when caller equals
- * `token`, because TREX `Token.setCompliance` invokes `compliance.bindToken(address(this))`.
*/
function bindToken(address token) public virtual override {
_authorizeComplianceBindingChange(token);
@@ -59,8 +57,6 @@ abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance {
* @dev Operator warning: unbinding is an administrative operation and does not
* erase any state already stored by external rule contracts in a previously
* shared ("multi-tenant") setup.
- * @dev T-REX compatibility: allows token self-unbinding when caller equals
- * `token`, because TREX token contracts may call `compliance.unbindToken(address(this))`.
*/
function unbindToken(address token) public virtual override {
_authorizeComplianceBindingChange(token);
@@ -83,11 +79,6 @@ abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance {
}
}
- /// @inheritdoc IERC3643Compliance
- function getTokenBounds() public view override returns (address[] memory) {
- return _boundTokens.values();
- }
-
/*//////////////////////////////////////////////////////////////
INTERNAL/PRIVATE FUNCTIONS
//////////////////////////////////////////////////////////////*/
@@ -114,15 +105,6 @@ abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance {
}
}
- /**
- * @dev Authorizes bind/unbind operations.
- * Allows compliance manager, or token self-calls for T-REX compatibility.
- */
- function _authorizeComplianceBindingChange(address token) internal virtual {
- if (_msgSender() != token) {
- _onlyComplianceManager();
- }
- }
-
+ function _authorizeComplianceBindingChange(address token) internal virtual;
function _onlyComplianceManager() internal virtual;
}
diff --git a/src/modules/RulesManagementModule.sol b/src/modules/RulesManagementModule.sol
index 4f56938..e3d6cff 100644
--- a/src/modules/RulesManagementModule.sol
+++ b/src/modules/RulesManagementModule.sol
@@ -30,7 +30,7 @@ abstract contract RulesManagementModule is RulesManagementModuleInvariantStorage
/// @dev Array of rules
EnumerableSet.AddressSet internal _rules;
/// @dev Maximum number of rules allowed in the engine.
- uint256 internal _maxRules = 10;
+ uint256 internal _maxRules = DEFAULT_MAX_RULES;
/*//////////////////////////////////////////////////////////////
PUBLIC/EXTERNAL FUNCTIONS
diff --git a/src/modules/library/ComplianceInterfaceId.sol b/src/modules/library/ComplianceInterfaceId.sol
index 1c604a8..477948a 100644
--- a/src/modules/library/ComplianceInterfaceId.sol
+++ b/src/modules/library/ComplianceInterfaceId.sol
@@ -8,5 +8,6 @@ pragma solidity ^0.8.20;
*/
library ComplianceInterfaceId {
bytes4 public constant ERC3643_COMPLIANCE_INTERFACE_ID = 0x3144991c;
+ bytes4 public constant ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID = 0x646ba2be;
bytes4 public constant IERC7551_COMPLIANCE_INTERFACE_ID = 0x7157797f;
}
diff --git a/src/modules/library/RulesManagementModuleInvariantStorage.sol b/src/modules/library/RulesManagementModuleInvariantStorage.sol
index 377a031..50ae472 100644
--- a/src/modules/library/RulesManagementModuleInvariantStorage.sol
+++ b/src/modules/library/RulesManagementModuleInvariantStorage.sol
@@ -5,7 +5,7 @@ pragma solidity ^0.8.20;
import {IRule} from "../../interfaces/IRule.sol";
abstract contract RulesManagementModuleInvariantStorage {
- //uint256 public constant DEFAULT_MAX_RULES = 10;
+ uint256 public constant DEFAULT_MAX_RULES = 10;
/* ==== Errors === */
error RuleEngine_RulesManagementModule_RuleAddressZeroNotAllowed();
diff --git a/test/HelperContract.sol b/test/HelperContract.sol
index ef59d3d..11aaa6a 100644
--- a/test/HelperContract.sol
+++ b/test/HelperContract.sol
@@ -12,7 +12,7 @@ import {RuleEngine} from "src/deployment/RuleEngine.sol";
// forge-lint: disable-next-line(unused-import)
import {RulesManagementModule} from "src/RuleEngineBase.sol";
// forge-lint: disable-next-line(unused-import)
-import {ERC3643ComplianceModule} from "src/RuleEngineBase.sol";
+import {ERC3643ComplianceModule} from "src/modules/ERC3643ComplianceModule.sol";
// RuleConditionalTransfer
import {RuleConditionalTransferLight} from "src/mocks/rules/operation/RuleConditionalTransferLight.sol";
import {
diff --git a/test/HelperContractOwnable.sol b/test/HelperContractOwnable.sol
index 08c9357..13f0885 100644
--- a/test/HelperContractOwnable.sol
+++ b/test/HelperContractOwnable.sol
@@ -12,7 +12,7 @@ import {RuleEngineOwnable} from "src/deployment/RuleEngineOwnable.sol";
// forge-lint: disable-next-line(unused-import)
import {RulesManagementModule} from "src/RuleEngineBase.sol";
// forge-lint: disable-next-line(unused-import)
-import {ERC3643ComplianceModule} from "src/RuleEngineBase.sol";
+import {ERC3643ComplianceModule} from "src/modules/ERC3643ComplianceModule.sol";
// RuleConditionalTransfer
import {RuleConditionalTransferLight} from "src/mocks/rules/operation/RuleConditionalTransferLight.sol";
import {
diff --git a/test/RuleEngine/ERC3643Compliance.t.sol b/test/RuleEngine/ERC3643Compliance.t.sol
index b936a43..e41712c 100644
--- a/test/RuleEngine/ERC3643Compliance.t.sol
+++ b/test/RuleEngine/ERC3643Compliance.t.sol
@@ -2,6 +2,7 @@
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
+import {Vm} from "forge-std/Vm.sol";
// forge-lint: disable-next-line(unaliased-plain-import)
import "../HelperContract.sol";
import {IERC3643Compliance} from "../../src/interfaces/IERC3643Compliance.sol";
@@ -28,7 +29,32 @@ contract ERC3643MockToken {
}
}
+contract ERC3643MockTokenWithSetCompliance {
+ IERC3643Compliance public tokenCompliance;
+ address public owner;
+
+ constructor(address owner_) {
+ owner = owner_;
+ }
+
+ modifier onlyOwner() {
+ require(msg.sender == owner, "Not owner");
+ _;
+ }
+
+ function setCompliance(address compliance) external onlyOwner {
+ if (address(tokenCompliance) != address(0)) {
+ tokenCompliance.unbindToken(address(this));
+ }
+ tokenCompliance = IERC3643Compliance(compliance);
+ tokenCompliance.bindToken(address(this));
+ }
+}
+
contract RuleEngineTest is Test, HelperContract {
+ bytes4 private constant ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR =
+ bytes4(keccak256("AccessControlUnauthorizedAccount(address,bytes32)"));
+
RuleEngine public ruleEngine;
ERC3643MockToken public token1;
ERC3643MockToken public token2;
@@ -162,14 +188,20 @@ contract RuleEngineTest is Test, HelperContract {
ruleEngine.bindToken(address(0x1));
}
- function testTokenCanBindItself() public {
+ function testApprovedTokenCanBindItself() public {
+ vm.prank(operator);
+ ruleEngine.setTokenSelfBindingApproval(address(token1), true);
+
vm.prank(address(token1));
ruleEngine.bindToken(address(token1));
assertTrue(ruleEngine.isTokenBound(address(token1)));
}
- function testBoundTokenCanUnbindItself() public {
+ function testApprovedBoundTokenCanUnbindItself() public {
+ vm.prank(operator);
+ ruleEngine.setTokenSelfBindingApproval(address(token1), true);
+
vm.prank(address(token1));
ruleEngine.bindToken(address(token1));
@@ -179,8 +211,41 @@ contract RuleEngineTest is Test, HelperContract {
assertFalse(ruleEngine.isTokenBound(address(token1)));
}
+ function testTokenCannotBindItselfWithoutApproval() public {
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ address(token1),
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
+ vm.prank(address(token1));
+ ruleEngine.bindToken(address(token1));
+ }
+
+ function testTokenCannotUnbindItselfWithoutApproval() public {
+ vm.prank(operator);
+ ruleEngine.bindToken(address(token1));
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ address(token1),
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
+ vm.prank(address(token1));
+ ruleEngine.unbindToken(address(token1));
+ }
+
function testTokenCannotBindAnotherToken() public {
- vm.expectRevert();
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ address(token1),
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
vm.prank(address(token1));
ruleEngine.bindToken(address(token2));
}
@@ -189,11 +254,181 @@ contract RuleEngineTest is Test, HelperContract {
vm.prank(operator);
ruleEngine.bindToken(address(token2));
- vm.expectRevert();
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ address(token1),
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
vm.prank(address(token1));
ruleEngine.unbindToken(address(token2));
}
+ function testOnlyComplianceManagerCanSetTokenSelfBindingApproval() public {
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ user1,
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
+ vm.prank(user1);
+ ruleEngine.setTokenSelfBindingApproval(address(token1), true);
+ }
+
+ function testCannotSetTokenSelfBindingApprovalForZeroAddress() public {
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(operator);
+ ruleEngine.setTokenSelfBindingApproval(address(0), true);
+ }
+
+ function testCanSetTokenSelfBindingApprovalBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(operator);
+ ruleEngine.setTokenSelfBindingApprovalBatch(tokens, true);
+
+ assertTrue(ruleEngine.isTokenSelfBindingApproved(address(token1)));
+ assertTrue(ruleEngine.isTokenSelfBindingApproved(address(token2)));
+ }
+
+ function testSetTokenSelfBindingApprovalBatchEmitsSingleBatchEvent() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.recordLogs();
+ vm.prank(operator);
+ ruleEngine.setTokenSelfBindingApprovalBatch(tokens, true);
+ Vm.Log[] memory entries = vm.getRecordedLogs();
+
+ assertEq(entries.length, 1);
+ assertEq(entries[0].topics[0], keccak256("TokenSelfBindingApprovalBatchSet(address[],bool)"));
+ }
+
+ function testOnlyComplianceManagerCanSetTokenSelfBindingApprovalBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token1);
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ user1,
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
+ vm.prank(user1);
+ ruleEngine.setTokenSelfBindingApprovalBatch(tokens, true);
+ }
+
+ function testCannotSetTokenSelfBindingApprovalBatchWithZeroAddress() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(0);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(operator);
+ ruleEngine.setTokenSelfBindingApprovalBatch(tokens, true);
+ }
+
+ function testCanBindTokensBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(operator);
+ ruleEngine.bindTokens(tokens);
+
+ assertTrue(ruleEngine.isTokenBound(address(token1)));
+ assertTrue(ruleEngine.isTokenBound(address(token2)));
+ }
+
+ function testCanUnbindTokensBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.startPrank(operator);
+ ruleEngine.bindTokens(tokens);
+ ruleEngine.unbindTokens(tokens);
+ vm.stopPrank();
+
+ assertFalse(ruleEngine.isTokenBound(address(token1)));
+ assertFalse(ruleEngine.isTokenBound(address(token2)));
+ }
+
+ function testOnlyComplianceManagerCanBindTokensBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token1);
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ user1,
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
+ vm.prank(user1);
+ ruleEngine.bindTokens(tokens);
+ }
+
+ function testOnlyComplianceManagerCanUnbindTokensBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token1);
+
+ vm.prank(operator);
+ ruleEngine.bindTokens(tokens);
+
+ vm.expectRevert(
+ abi.encodeWithSelector(
+ ACCESS_CONTROL_UNAUTHORIZED_ACCOUNT_SELECTOR,
+ user1,
+ ruleEngine.COMPLIANCE_MANAGER_ROLE()
+ )
+ );
+ vm.prank(user1);
+ ruleEngine.unbindTokens(tokens);
+ }
+
+ function testCannotBindTokensBatchWithZeroAddress() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(0);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(operator);
+ ruleEngine.bindTokens(tokens);
+ }
+
+ function testCannotBindTokensBatchWithAlreadyBoundToken() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(operator);
+ ruleEngine.bindToken(address(token1));
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector);
+ vm.prank(operator);
+ ruleEngine.bindTokens(tokens);
+ }
+
+ function testCannotUnbindTokensBatchWithTokenNotBound() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(operator);
+ ruleEngine.bindToken(address(token1));
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_TokenNotBound.selector);
+ vm.prank(operator);
+ ruleEngine.unbindTokens(tokens);
+ }
+
function testCannotCreatedIfNotBound() public {
vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector);
ruleEngine.created(user1, 100);
@@ -208,4 +443,28 @@ contract RuleEngineTest is Test, HelperContract {
vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector);
ruleEngine.transferred(user1, user2, 200);
}
+
+ function testTokenSetComplianceCanMigrateBetweenRuleEngines() public {
+ RuleEngine ruleEngine2 = new RuleEngine(admin, ZERO_ADDRESS, ZERO_ADDRESS);
+ ERC3643MockTokenWithSetCompliance trexLikeToken = new ERC3643MockTokenWithSetCompliance(user1);
+
+ vm.startPrank(admin);
+ ruleEngine2.grantRole(ruleEngine2.COMPLIANCE_MANAGER_ROLE(), operator);
+ vm.stopPrank();
+
+ vm.startPrank(operator);
+ ruleEngine.setTokenSelfBindingApproval(address(trexLikeToken), true);
+ ruleEngine2.setTokenSelfBindingApproval(address(trexLikeToken), true);
+ vm.stopPrank();
+
+ vm.prank(user1);
+ trexLikeToken.setCompliance(address(ruleEngine));
+ assertTrue(ruleEngine.isTokenBound(address(trexLikeToken)));
+ assertFalse(ruleEngine2.isTokenBound(address(trexLikeToken)));
+
+ vm.prank(user1);
+ trexLikeToken.setCompliance(address(ruleEngine2));
+ assertFalse(ruleEngine.isTokenBound(address(trexLikeToken)));
+ assertTrue(ruleEngine2.isTokenBound(address(trexLikeToken)));
+ }
}
diff --git a/test/RuleEngine/RuleEngineCoverage.t.sol b/test/RuleEngine/RuleEngineCoverage.t.sol
index 3328f5f..373f7c5 100644
--- a/test/RuleEngine/RuleEngineCoverage.t.sol
+++ b/test/RuleEngine/RuleEngineCoverage.t.sol
@@ -8,8 +8,10 @@ import "../HelperContract.sol";
import {RuleEngineExposed} from "src/mocks/RuleEngineExposed.sol";
import {RuleInvalidMock} from "src/mocks/RuleInvalidMock.sol";
import {ICompliance} from "src/mocks/ICompliance.sol";
+import {IERC3643ComplianceExtendedSubset} from "src/mocks/IERC3643ComplianceExtendedSubset.sol";
import {IERC1404Subset} from "src/mocks/IERC1404Subset.sol";
import {IERC7551ComplianceSubset} from "src/mocks/IERC7551ComplianceSubset.sol";
+import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.sol";
import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol";
/**
@@ -50,6 +52,11 @@ contract RuleEngineCoverageTest is Test, HelperContract {
assertTrue(ruleEngineMock.supportsInterface(type(ICompliance).interfaceId));
}
+ function testSupportsERC3643ComplianceExtendedSubsetInterface() public view {
+ assertTrue(ruleEngineMock.supportsInterface(ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID));
+ assertTrue(ruleEngineMock.supportsInterface(type(IERC3643ComplianceExtendedSubset).interfaceId));
+ }
+
function testSupportsIERC7551ComplianceSubsetInterface() public view {
assertTrue(ruleEngineMock.supportsInterface(type(IERC7551ComplianceSubset).interfaceId));
}
diff --git a/test/RuleEngine/RuleEngineDeployment.t.sol b/test/RuleEngine/RuleEngineDeployment.t.sol
index 14ebbe0..d380c61 100644
--- a/test/RuleEngine/RuleEngineDeployment.t.sol
+++ b/test/RuleEngine/RuleEngineDeployment.t.sol
@@ -11,8 +11,10 @@ import {IAccessControlEnumerable} from "@openzeppelin/contracts/access/extension
import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol";
import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol";
import {ICompliance} from "src/mocks/ICompliance.sol";
+import {IERC3643ComplianceExtendedSubset} from "src/mocks/IERC3643ComplianceExtendedSubset.sol";
import {IERC7551ComplianceSubset} from "src/mocks/IERC7551ComplianceSubset.sol";
import {IERC1404Subset} from "src/mocks/IERC1404Subset.sol";
+import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.sol";
import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol";
/**
@@ -68,6 +70,8 @@ contract RuleEngineTest is Test, HelperContract {
assertTrue(ruleEngineMock.supportsInterface(type(IERC1404Subset).interfaceId));
assertTrue(ruleEngineMock.supportsInterface(ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID));
assertTrue(ruleEngineMock.supportsInterface(type(ICompliance).interfaceId));
+ assertTrue(ruleEngineMock.supportsInterface(ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID));
+ assertTrue(ruleEngineMock.supportsInterface(type(IERC3643ComplianceExtendedSubset).interfaceId));
assertTrue(ruleEngineMock.supportsInterface(type(IERC7551ComplianceSubset).interfaceId));
}
diff --git a/test/RuleEngineOwnable/ERC3643Compliance.t.sol b/test/RuleEngineOwnable/ERC3643Compliance.t.sol
index 0545a49..757b2c0 100644
--- a/test/RuleEngineOwnable/ERC3643Compliance.t.sol
+++ b/test/RuleEngineOwnable/ERC3643Compliance.t.sol
@@ -2,6 +2,8 @@
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
+import {Vm} from "forge-std/Vm.sol";
+import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
// forge-lint: disable-next-line(unaliased-plain-import)
import "../HelperContractOwnable.sol";
import {IERC3643Compliance} from "../../src/interfaces/IERC3643Compliance.sol";
@@ -155,14 +157,20 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable {
ruleEngineMock.bindToken(address(0x1));
}
- function testTokenCanBindItself() public {
+ function testApprovedTokenCanBindItself() public {
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApproval(address(token1), true);
+
vm.prank(address(token1));
ruleEngineMock.bindToken(address(token1));
assertTrue(ruleEngineMock.isTokenBound(address(token1)));
}
- function testBoundTokenCanUnbindItself() public {
+ function testApprovedBoundTokenCanUnbindItself() public {
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApproval(address(token1), true);
+
vm.prank(address(token1));
ruleEngineMock.bindToken(address(token1));
@@ -172,8 +180,23 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable {
assertFalse(ruleEngineMock.isTokenBound(address(token1)));
}
+ function testTokenCannotBindItselfWithoutApproval() public {
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(token1)));
+ vm.prank(address(token1));
+ ruleEngineMock.bindToken(address(token1));
+ }
+
+ function testTokenCannotUnbindItselfWithoutApproval() public {
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindToken(address(token1));
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(token1)));
+ vm.prank(address(token1));
+ ruleEngineMock.unbindToken(address(token1));
+ }
+
function testTokenCannotBindAnotherToken() public {
- vm.expectRevert();
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(token1)));
vm.prank(address(token1));
ruleEngineMock.bindToken(address(token2));
}
@@ -182,11 +205,151 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable {
vm.prank(OWNER_ADDRESS);
ruleEngineMock.bindToken(address(token2));
- vm.expectRevert();
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(token1)));
vm.prank(address(token1));
ruleEngineMock.unbindToken(address(token2));
}
+ function testOnlyOwnerCanSetTokenSelfBindingApproval() public {
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user1));
+ vm.prank(user1);
+ ruleEngineMock.setTokenSelfBindingApproval(address(token1), true);
+ }
+
+ function testCannotSetTokenSelfBindingApprovalForZeroAddress() public {
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApproval(address(0), true);
+ }
+
+ function testCanSetTokenSelfBindingApprovalBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+
+ assertTrue(ruleEngineMock.isTokenSelfBindingApproved(address(token1)));
+ assertTrue(ruleEngineMock.isTokenSelfBindingApproved(address(token2)));
+ }
+
+ function testSetTokenSelfBindingApprovalBatchEmitsSingleBatchEvent() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.recordLogs();
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+ Vm.Log[] memory entries = vm.getRecordedLogs();
+
+ assertEq(entries.length, 1);
+ assertEq(entries[0].topics[0], keccak256("TokenSelfBindingApprovalBatchSet(address[],bool)"));
+ }
+
+ function testOnlyOwnerCanSetTokenSelfBindingApprovalBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token1);
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user1));
+ vm.prank(user1);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+ }
+
+ function testCannotSetTokenSelfBindingApprovalBatchWithZeroAddress() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(0);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+ }
+
+ function testCanBindTokensBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+
+ assertTrue(ruleEngineMock.isTokenBound(address(token1)));
+ assertTrue(ruleEngineMock.isTokenBound(address(token2)));
+ }
+
+ function testCanUnbindTokensBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.startPrank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+ ruleEngineMock.unbindTokens(tokens);
+ vm.stopPrank();
+
+ assertFalse(ruleEngineMock.isTokenBound(address(token1)));
+ assertFalse(ruleEngineMock.isTokenBound(address(token2)));
+ }
+
+ function testOnlyOwnerCanBindTokensBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token1);
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user1));
+ vm.prank(user1);
+ ruleEngineMock.bindTokens(tokens);
+ }
+
+ function testOnlyOwnerCanUnbindTokensBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token1);
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user1));
+ vm.prank(user1);
+ ruleEngineMock.unbindTokens(tokens);
+ }
+
+ function testCannotBindTokensBatchWithZeroAddress() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(0);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+ }
+
+ function testCannotBindTokensBatchWithAlreadyBoundToken() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindToken(address(token1));
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+ }
+
+ function testCannotUnbindTokensBatchWithTokenNotBound() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = address(token1);
+ tokens[1] = address(token2);
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindToken(address(token1));
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_TokenNotBound.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.unbindTokens(tokens);
+ }
+
function testCannotCreatedIfNotBound() public {
vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector);
ruleEngineMock.created(user1, 100);
diff --git a/test/RuleEngineOwnable/RuleEngineOwnableCoverage.t.sol b/test/RuleEngineOwnable/RuleEngineOwnableCoverage.t.sol
index cf5d616..9f45d20 100644
--- a/test/RuleEngineOwnable/RuleEngineOwnableCoverage.t.sol
+++ b/test/RuleEngineOwnable/RuleEngineOwnableCoverage.t.sol
@@ -9,9 +9,11 @@ import {RuleEngineOwnableExposed} from "src/mocks/RuleEngineExposed.sol";
import {RuleInvalidMock} from "src/mocks/RuleInvalidMock.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {ICompliance} from "src/mocks/ICompliance.sol";
+import {IERC3643ComplianceExtendedSubset} from "src/mocks/IERC3643ComplianceExtendedSubset.sol";
import {IERC173Subset} from "src/mocks/IERC173Subset.sol";
import {IERC1404Subset} from "src/mocks/IERC1404Subset.sol";
import {IERC7551ComplianceSubset} from "src/mocks/IERC7551ComplianceSubset.sol";
+import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.sol";
import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol";
import {OwnableInterfaceId} from "src/modules/library/OwnableInterfaceId.sol";
@@ -58,6 +60,11 @@ contract RuleEngineOwnableCoverageTest is Test, HelperContractOwnable {
assertTrue(ruleEngineMock.supportsInterface(type(ICompliance).interfaceId));
}
+ function testSupportsERC3643ComplianceExtendedSubsetInterface() public view {
+ assertTrue(ruleEngineMock.supportsInterface(ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID));
+ assertTrue(ruleEngineMock.supportsInterface(type(IERC3643ComplianceExtendedSubset).interfaceId));
+ }
+
function testSupportsIERC7551ComplianceSubsetInterface() public view {
assertTrue(ruleEngineMock.supportsInterface(type(IERC7551ComplianceSubset).interfaceId));
}
diff --git a/test/RuleEngineOwnable/RuleEngineOwnableDeployment.t.sol b/test/RuleEngineOwnable/RuleEngineOwnableDeployment.t.sol
index 3ab94c6..72d1a41 100644
--- a/test/RuleEngineOwnable/RuleEngineOwnableDeployment.t.sol
+++ b/test/RuleEngineOwnable/RuleEngineOwnableDeployment.t.sol
@@ -9,9 +9,11 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol";
import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol";
import {ICompliance} from "src/mocks/ICompliance.sol";
+import {IERC3643ComplianceExtendedSubset} from "src/mocks/IERC3643ComplianceExtendedSubset.sol";
import {IERC173Subset} from "src/mocks/IERC173Subset.sol";
import {IERC1404Subset} from "src/mocks/IERC1404Subset.sol";
import {IERC7551ComplianceSubset} from "src/mocks/IERC7551ComplianceSubset.sol";
+import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.sol";
import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol";
import {OwnableInterfaceId} from "src/modules/library/OwnableInterfaceId.sol";
@@ -77,6 +79,8 @@ contract RuleEngineOwnableDeploymentTest is Test, HelperContractOwnable {
assertTrue(ruleEngineMock.supportsInterface(RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID));
assertTrue(ruleEngineMock.supportsInterface(ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID));
assertTrue(ruleEngineMock.supportsInterface(type(ICompliance).interfaceId));
+ assertTrue(ruleEngineMock.supportsInterface(ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID));
+ assertTrue(ruleEngineMock.supportsInterface(type(IERC3643ComplianceExtendedSubset).interfaceId));
assertTrue(ruleEngineMock.supportsInterface(type(IERC7551ComplianceSubset).interfaceId));
}
diff --git a/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol b/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol
index 6a5916f..d5ba966 100644
--- a/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol
+++ b/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol
@@ -2,16 +2,19 @@
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
+import {Vm} from "forge-std/Vm.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {MinimalForwarderMock} from "CMTAT/mocks/MinimalForwarderMock.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {ERC1404ExtendInterfaceId} from "CMTAT/library/ERC1404ExtendInterfaceId.sol";
import {RuleEngineInterfaceId} from "CMTAT/library/RuleEngineInterfaceId.sol";
import {ICompliance} from "src/mocks/ICompliance.sol";
+import {IERC3643ComplianceExtendedSubset} from "src/mocks/IERC3643ComplianceExtendedSubset.sol";
import {IERC173Subset} from "src/mocks/IERC173Subset.sol";
import {IOwnable2StepSubset} from "src/mocks/IOwnable2StepSubset.sol";
import {IERC1404Subset} from "src/mocks/IERC1404Subset.sol";
import {IERC7551ComplianceSubset} from "src/mocks/IERC7551ComplianceSubset.sol";
+import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.sol";
import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol";
import {OwnableInterfaceId} from "src/modules/library/OwnableInterfaceId.sol";
import {Ownable2StepInterfaceId} from "src/modules/library/Ownable2StepInterfaceId.sol";
@@ -65,6 +68,8 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step {
assertTrue(ruleEngineMock.supportsInterface(RuleEngineInterfaceId.RULE_ENGINE_INTERFACE_ID));
assertTrue(ruleEngineMock.supportsInterface(ERC1404ExtendInterfaceId.ERC1404EXTEND_INTERFACE_ID));
assertTrue(ruleEngineMock.supportsInterface(type(ICompliance).interfaceId));
+ assertTrue(ruleEngineMock.supportsInterface(ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID));
+ assertTrue(ruleEngineMock.supportsInterface(type(IERC3643ComplianceExtendedSubset).interfaceId));
assertTrue(ruleEngineMock.supportsInterface(type(IERC7551ComplianceSubset).interfaceId));
}
@@ -160,14 +165,20 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step {
ruleEngineMock.clearRules();
}
- function testTokenCanBindItself() public {
+ function testApprovedTokenCanBindItself() public {
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApproval(TOKEN_1, true);
+
vm.prank(TOKEN_1);
ruleEngineMock.bindToken(TOKEN_1);
assertTrue(ruleEngineMock.isTokenBound(TOKEN_1));
}
- function testBoundTokenCanUnbindItself() public {
+ function testApprovedBoundTokenCanUnbindItself() public {
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApproval(TOKEN_1, true);
+
vm.prank(TOKEN_1);
ruleEngineMock.bindToken(TOKEN_1);
@@ -177,8 +188,23 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step {
assertFalse(ruleEngineMock.isTokenBound(TOKEN_1));
}
+ function testTokenCannotBindItselfWithoutApproval() public {
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
+ vm.prank(TOKEN_1);
+ ruleEngineMock.bindToken(TOKEN_1);
+ }
+
+ function testTokenCannotUnbindItselfWithoutApproval() public {
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindToken(TOKEN_1);
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
+ vm.prank(TOKEN_1);
+ ruleEngineMock.unbindToken(TOKEN_1);
+ }
+
function testTokenCannotBindAnotherToken() public {
- vm.expectRevert();
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
vm.prank(TOKEN_1);
ruleEngineMock.bindToken(TOKEN_2);
}
@@ -187,11 +213,151 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step {
vm.prank(OWNER_ADDRESS);
ruleEngineMock.bindToken(TOKEN_2);
- vm.expectRevert();
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
vm.prank(TOKEN_1);
ruleEngineMock.unbindToken(TOKEN_2);
}
+ function testOnlyOwnerCanSetTokenSelfBindingApproval() public {
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
+ vm.prank(TOKEN_1);
+ ruleEngineMock.setTokenSelfBindingApproval(TOKEN_1, true);
+ }
+
+ function testCannotSetTokenSelfBindingApprovalForZeroAddress() public {
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApproval(address(0), true);
+ }
+
+ function testCanSetTokenSelfBindingApprovalBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = TOKEN_2;
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+
+ assertTrue(ruleEngineMock.isTokenSelfBindingApproved(TOKEN_1));
+ assertTrue(ruleEngineMock.isTokenSelfBindingApproved(TOKEN_2));
+ }
+
+ function testSetTokenSelfBindingApprovalBatchEmitsSingleBatchEvent() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = TOKEN_2;
+
+ vm.recordLogs();
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+ Vm.Log[] memory entries = vm.getRecordedLogs();
+
+ assertEq(entries.length, 1);
+ assertEq(entries[0].topics[0], keccak256("TokenSelfBindingApprovalBatchSet(address[],bool)"));
+ }
+
+ function testOnlyOwnerCanSetTokenSelfBindingApprovalBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = TOKEN_1;
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
+ vm.prank(TOKEN_1);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+ }
+
+ function testCannotSetTokenSelfBindingApprovalBatchWithZeroAddress() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = address(0);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true);
+ }
+
+ function testCanBindTokensBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = TOKEN_2;
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+
+ assertTrue(ruleEngineMock.isTokenBound(TOKEN_1));
+ assertTrue(ruleEngineMock.isTokenBound(TOKEN_2));
+ }
+
+ function testCanUnbindTokensBatch() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = TOKEN_2;
+
+ vm.startPrank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+ ruleEngineMock.unbindTokens(tokens);
+ vm.stopPrank();
+
+ assertFalse(ruleEngineMock.isTokenBound(TOKEN_1));
+ assertFalse(ruleEngineMock.isTokenBound(TOKEN_2));
+ }
+
+ function testOnlyOwnerCanBindTokensBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = TOKEN_1;
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
+ vm.prank(TOKEN_1);
+ ruleEngineMock.bindTokens(tokens);
+ }
+
+ function testOnlyOwnerCanUnbindTokensBatch() public {
+ address[] memory tokens = new address[](1);
+ tokens[0] = TOKEN_1;
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, TOKEN_1));
+ vm.prank(TOKEN_1);
+ ruleEngineMock.unbindTokens(tokens);
+ }
+
+ function testCannotBindTokensBatchWithZeroAddress() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = address(0);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+ }
+
+ function testCannotBindTokensBatchWithAlreadyBoundToken() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = TOKEN_2;
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindToken(TOKEN_1);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindTokens(tokens);
+ }
+
+ function testCannotUnbindTokensBatchWithTokenNotBound() public {
+ address[] memory tokens = new address[](2);
+ tokens[0] = TOKEN_1;
+ tokens[1] = TOKEN_2;
+
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.bindToken(TOKEN_1);
+
+ vm.expectRevert(ERC3643ComplianceModule.RuleEngine_ERC3643Compliance_TokenNotBound.selector);
+ vm.prank(OWNER_ADDRESS);
+ ruleEngineMock.unbindTokens(tokens);
+ }
+
function testMsgDataReturnsCalldata() public view {
bytes memory data = ruleEngineOwnable2StepExposed.exposedMsgData();
assertEq(data.length, 4);