diff --git a/AGENTS.md b/AGENTS.md index 6e92152..f64b8dc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -50,7 +50,7 @@ CMTAT submodule also needs `cd lib/CMTAT && npm install` for its OpenZeppelin de | `CMTATv3.0.0/` | `lib/CMTATv3.0.0/contracts/` | | `@openzeppelin/contracts/` | `lib/openzeppelin-contracts/contracts` | -Use `@openzeppelin/contracts/` for OpenZeppelin imports, `CMTAT/` for CMTAT imports, `src/` for local imports. +Use `@openzeppelin/contracts/` for OpenZeppelin imports and `CMTAT/` for CMTAT imports. For project files, `src/` and `script/` import **relatively** (`./modules/...`, `../src/...`); the `src/` remapping is used by the tests. ## Architecture @@ -72,10 +72,14 @@ RuleEngineBase (abstract) ├── RulesManagementModule → add/remove/set/clear rules, maxRules cap │ ├── AccessControl (OZ) │ └── RulesManagementModuleInvariantStorage → errors, events, roles -├── ERC3643ComplianceExtendedModule → bind/unbind tokens (extended API) -│ └── ERC3643ComplianceModule → core ERC-3643 compliance -│ ├── IERC3643Compliance -│ └── ERC3643ComplianceModuleInvariantStorage → errors +├── ERC3643ComplianceExtendedModule → ERC-3643 flavour of the binding registry +│ ├── ERC3643ComplianceModule → ERC-3643 adapter: getTokenBound(), compliance naming +│ │ ├── IERC3643Compliance +│ │ └── TokenBindingModule → bind/unbind tokens (standard-agnostic registry) +│ │ ├── ITokenBinding +│ │ └── TokenBindingModuleInvariantStorage → errors +│ └── TokenBindingExtendedModule → batch binding, token self-binding (standard-agnostic) +│ └── ITokenBindingExtended ├── RuleEngineInvariantStorage → errors └── IRuleEngineERC1404 → CMTAT interface @@ -105,7 +109,9 @@ Modules define **virtual internal hooks** for access control. Concrete contracts function _onlyRulesManager() internal virtual; function _onlyRulesLimitManager() internal virtual; // guards setMaxRules -// In ERC3643ComplianceModule (abstract): +// In TokenBindingModule (abstract): +function _onlyTokenBindingManager() internal virtual; +// wired by ERC3643ComplianceModule to its own abstract hook: function _onlyComplianceManager() internal virtual; // RuleEngine overrides with RBAC: @@ -182,11 +188,25 @@ can report a mint as allowed that `transferred(spender, ...)` will revert. Use t `detectTransferRestrictionFrom` / `canTransferFrom` to pre-check an operation that has an operator. See `H-1` in `doc/security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md`. +### Token Binding Layering + +Token binding is split so it can be reused outside this project: + +- `TokenBindingModule` / `TokenBindingExtendedModule` (+ `ITokenBinding` / `ITokenBindingExtended`, + `TokenBindingModuleInvariantStorage`) hold the whole registry and depend only on OpenZeppelin + (`Context`, `EnumerableSet`). No rule, ERC-1404 or ERC-3643 code. +- `ERC3643ComplianceModule` / `ERC3643ComplianceExtendedModule` are thin ERC-3643 adapters: they add + `getTokenBound()` and wire `_onlyTokenBindingManager()` to `_onlyComplianceManager()`. + +**Keep new binding logic in the generic modules and new ERC-3643 logic in the adapters.** +`src/mocks/TokenBindingStandaloneMock.sol` (+ `test/TokenBinding/`) pins that the registry still works +standalone. See `doc/technical/TokenBinding-module.md`. + ### Storage: EnumerableSet Both rules and bound tokens use `EnumerableSet.AddressSet`: - `_rules` in `RulesManagementModule` — the set of active rules -- `_boundTokens` in `ERC3643ComplianceModule` — tokens allowed to call `transferred` +- `_boundTokens` in `TokenBindingModule` — tokens allowed to call `transferred` This gives O(1) add/remove/contains and iterable storage. @@ -196,14 +216,25 @@ This gives O(1) add/remove/contains and iterable storage. |-----------|---------|---------------| | `IRule` | What every rule must implement (extends `IRuleEngineERC1404`) | `src/interfaces/IRule.sol` | | `IRulesManagementModule` | Rule CRUD operations | `src/interfaces/IRulesManagementModule.sol` | -| `IERC3643Compliance` | Token binding + compliance hooks | `src/interfaces/IERC3643Compliance.sol` | +| `ITokenBinding` | Token binding registry, standard-agnostic | `src/interfaces/ITokenBinding.sol` | +| `ITokenBindingExtended` | Batch binding, token self-binding, `getTokenBounds` | `src/interfaces/ITokenBindingExtended.sol` | +| `IERC3643Compliance` | ERC-3643 compliance hooks (extends `ITokenBinding`) | `src/interfaces/IERC3643Compliance.sol` | | `IRuleEngine` | Full CMTAT integration interface | `lib/CMTAT/contracts/interfaces/engine/IRuleEngine.sol` | **ERC-165 interface IDs:** -- `IRule`: `0x2497d6cb` (defined in `src/modules/library/RuleInterfaceId.sol`) +- `IRule`: `0x2497d6cb` (`src/modules/library/RuleInterfaceId.sol`) +- `IERC3643Compliance`: `0x3144991c`, extended: `0x646ba2be`, `IERC7551Compliance`: `0x7157797f` (`ComplianceInterfaceId.sol`) +- `IERC1404`: `0xab84a5c8` (`ERC1404InterfaceId.sol`) - `IRuleEngine`: from `CMTAT/library/RuleEngineInterfaceId.sol` - `IERC1404Extend`: from `CMTAT/library/ERC1404ExtendInterfaceId.sol` -- `ERC-173`: `0x7f5828d0` (hardcoded in `RuleEngineOwnable`) +- `ERC-173`: `0x7f5828d0` (`OwnableInterfaceId.sol`); `Ownable2Step` subset: `0x9ab669ef` + +**The project's IDs are computed, not hardcoded.** `type(I).interfaceId` covers only the functions `I` +declares *directly*, so each constant XORs the interface with its parents; a marker interface that declares +nothing of its own — `IERC3643ComplianceExtended` — has a `type(...).interfaceId` of `0x00000000` and must +never be used for an ERC-165 check. `test/RuleEngine/IRuleInterfaceId.t.sol` pins every constant to its wire +value, so an upstream interface change fails a test instead of silently changing what `supportsInterface` +answers. ERC-173 and the `Ownable2Step` subset stay literal: neither has an interface declaration in scope. ## Invariant Storage Pattern @@ -213,6 +244,7 @@ Errors, events, and role constants are centralized in "invariant storage" abstra |----------|----------| | `RuleEngineInvariantStorage` | `RuleEngine_AdminWithAddressZeroNotAllowed`, `RuleEngine_RuleInvalidInterface` | | `RulesManagementModuleInvariantStorage` | Rule errors, `AddRule`/`RemoveRule`/`ClearRules` events, `RULES_MANAGEMENT_ROLE` | +| `TokenBindingModuleInvariantStorage` | `TokenBinding_*` binding errors (standard-agnostic, no `RuleEngine_` prefix) | **Convention:** Error names follow `Contract_Module_ErrorName` pattern. Test contracts inherit these to access `.selector` for `vm.expectRevert`. @@ -226,8 +258,9 @@ src/ │ └── RuleEngineOwnable2Step.sol # Ownable2Step variant (deploy this) ├── RuleEngineBase.sol # Abstract core logic (do not deploy) ├── RuleEngineOwnableShared.sol # Shared logic for ownable variants -├── interfaces/ # IRule, IRulesManagementModule, IERC3643Compliance -├── modules/ # VersionModule, RulesManagementModule, ERC3643ComplianceModule, ERC2771ModuleStandalone +├── interfaces/ # IRule, IRulesManagementModule, ITokenBinding(Extended), IERC3643Compliance(Extended) +├── modules/ # VersionModule, RulesManagementModule, TokenBinding(Extended)Module, +│ # ERC3643Compliance(Extended)Module, ERC2771ModuleStandalone │ └── library/ # InvariantStorage contracts, RuleInterfaceId └── mocks/ # Test-only/reference contracts diff --git a/CHANGELOG.md b/CHANGELOG.md index 315d157..e07c0a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,46 @@ forge lint -### v3.0.0-rc5 +### v3.0.0-rc6 + +### Summary + +Token binding is separated from the ERC-3643 compliance code: `TokenBindingModule` and +`TokenBindingExtendedModule` now hold the whole binding registry and depend only on OpenZeppelin, so they can be reused in another project, while the compliance modules become thin ERC-3643 adapters. + +The external API and the advertised ERC-165 interface IDs are unchanged; the only breaking change is the rename of the binding errors `RuleEngine_ERC3643Compliance_*` to `TokenBinding_*`, which changes their selectors. + +### Changed + +- **Token binding is now separated from the ERC-3643 compliance code**, so the registry can be reused in another project as-is. `TokenBindingModule` (+ `TokenBindingExtendedModule`) holds the whole binding logic — bound token set, `bindToken` / `unbindToken` / `isTokenBound`, the `onlyBoundToken` guard, batch binding, token self-binding and `getTokenBounds()` — and depends only on OpenZeppelin's `Context` and `EnumerableSet`. `ERC3643ComplianceModule` / `ERC3643ComplianceExtendedModule` become thin ERC-3643 adapters, keeping only `getTokenBound()` and the compliance-manager vocabulary (`_onlyTokenBindingManager()` is wired to `_onlyComplianceManager()`). +- The binding functions and events moved to the standard-agnostic `ITokenBinding` / `ITokenBindingExtended`, which `IERC3643Compliance` / `IERC3643ComplianceExtended` now extend. **The external API and the advertised ERC-165 interface IDs are unchanged**: same functions, same selectors, same events, same access control. +- **Renamed the binding errors** `RuleEngine_ERC3643Compliance_*` to `TokenBinding_*` (`TokenBinding_InvalidTokenAddress`, `TokenBinding_TokenAlreadyBound`, `TokenBinding_TokenNotBound`, `TokenBinding_UnauthorizedCaller`), so a reused module carries no RuleEngine or ERC-3643 wording. This changes the error selectors. `ERC3643ComplianceModuleInvariantStorage` is replaced by `TokenBindingModuleInvariantStorage`. +- `_authorizeComplianceBindingChange(address)` renamed to `_authorizeTokenBindingChange(address)` and is no longer abstract: `TokenBindingModule` defaults it to the binding manager check, and `TokenBindingExtendedModule` overrides it with the self-binding aware variant. The deployable contracts are unchanged — they still implement `_onlyComplianceManager()` only. +- `script/`: the two deployment scripts now import project files relatively (`../src/...`) instead of through the `src/` remapping, and `run()` carries NatSpec in both. `src/` and `script/` are clean against the project style checks (function order, modifier order, NatSpec, revert strings, imports, emoji); the test suite is deliberately left on the `src/` remapping and on Foundry test-naming conventions. +- **The ERC-165 interface IDs are now computed from the interfaces instead of hardcoded** (`CLAUDE_ANALYSIS.md` `F-2`): `RuleInterfaceId.IRULE_INTERFACE_ID`, the three `ComplianceInterfaceId` constants and `ERC1404InterfaceId.IERC1404_INTERFACE_ID` XOR each interface with its parents, since `type(I).interfaceId` counts only directly declared selectors. **Every value is unchanged** (`0x2497d6cb`, `0x3144991c`, `0x646ba2be`, `0x7157797f`, `0xab84a5c8`) and is now pinned by a test, so an upstream CMTAT interface change fails the suite instead of silently altering what `supportsInterface` advertises. `IERC3643ComplianceExtended` declares no function of its own, so `type(IERC3643ComplianceExtended).interfaceId` is `0x00000000`: a NatSpec warning now says so and names the constant to use. `OwnableInterfaceId` and `Ownable2StepInterfaceId` stay literal — neither has an interface declaration outside `src/mocks/`. +- Removed the unused `onlyComplianceManager` modifier from `ERC3643ComplianceModule`; the generic `onlyTokenBindingManager` modifier of `TokenBindingModule` guards the binding administration functions. + +### Added + +- Add `TokenBindingStandaloneMock`: a minimal engine embedding `TokenBindingModule` alone with `Ownable` access control, showing what another project has to provide to reuse the registry, and pinning that it works with no compliance code around it. +- Add `test/TokenBinding/TokenBindingStandalone.t.sol` (10 tests) covering the standalone registry: bind, unbind, events, manager-only administration, zero address, already-bound / not-bound, the `onlyBoundToken` guard, and that self-binding is rejected without `TokenBindingExtendedModule`. + +### Documentation + +- Add `testInterfaceIdConstantsMatchTheirWireValues` and `testMarkerInterfaceHasZeroNaiveIdAndIsNotUsedAsSuch` to `test/RuleEngine/IRuleInterfaceId.t.sol`, pinning the five advertised interface IDs to their wire values and the marker interface's naive ID to `0x00000000` (346 -> 348 tests). +- Add the v3.0.0-rc6 code-quality review in [doc/security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md](./doc/security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md): 14 findings, none a vulnerability, one fixed in this release. The reusability claim behind the token binding split is verified by two compile probes (a foreign ERC-2771 + RBAC host, and a CMTAT token embedding the registry) — both build, with no linearization conflict — and the ERC-3643 adapter indirection is measured at 35 gas per binding operation. `F-2` (hardcoded interface IDs) is fixed in this release; two items are left open for a decision: `C-2` (batch approval event, carried from rc5) and `I-2` (a rule advertises `RULE_ENGINE_INTERFACE_ID`, so it can be attached to a token as its engine with no RuleEngine in between, but no test or document covers that configuration). `I-1` (`IRule` requires `canTransfer` / `canTransferFrom`, which the engine never calls) stands, with the narrowed interface ID computed as `0xb1a69752`; an intermediate correction claiming the finding was void is kept in the report, marked superseded, with the reasoning error explained. +- Add the v3.0.0-rc6 Slither and Aderyn reports with their assessment feedback in [doc/security/audits/tools/v3.0.0-rc6](./doc/security/audits/tools/v3.0.0-rc6), each prefixed with a summary table of findings and dispositions, and refresh [AUDIT_OVERVIEW.md](./doc/security/audits/AUDIT_OVERVIEW.md). Slither: 0 High / 0 Medium / 10 Low / 2 Informational, unchanged from rc5. Aderyn: 0 High / 8 Low, 76 -> 84 instances, the growth coming entirely from the net four files added by the token binding split (the per-file pragma and PUSH0 detectors). Nothing to fix in either. +- Add [doc/technical/TokenBinding-module.md](./doc/technical/TokenBinding-module.md): the layering, the two hooks a deployment implements, how to reuse the module in another project, and the operational warnings of the registry. +- `doc/README.md` "ERC-165 Support by Deployment Version": add the missing `IERC3643ComplianceExtended` (`0x646ba2be`) row — all three variants advertise it and five tests assert it, but the table never listed it — and note that `IRule` (`0x2497d6cb`) is deliberately absent because the engine *requires* it of rules rather than implementing it, that the IDs are computed and pinned, and that `type(IERC3643ComplianceExtended).interfaceId` is `0x00000000` and must not be used. +- Update `README.md`, `doc/README.md`, `doc/technical/RuleEngine-with-ERC3643.md`, `CLAUDE.md` and `AGENTS.md` for the new layering. +- Replace the access-control schema of `doc/README.md` (a drawio export, `doc/security/accessControl/access-control-RuleEngine.png`) with a PlantUML diagram, [doc/schema/plantuml/ruleengine-access-control.puml](./doc/schema/plantuml/ruleengine-access-control.puml), following the convention of the other diagrams: a text source is versioned next to the rendered PNG. The new diagram is up to date with the current code — it shows `setMaxRules` under `DEFAULT_ADMIN_ROLE`, the extended binding functions, the token self-binding path and the `onlyBoundToken` data plane, and it names `TokenBindingModule` / `TokenBindingExtendedModule` rather than the compliance modules. +- Regenerate the contract UML diagrams with [sol2uml](https://github.com/naddison36/sol2uml) into `doc/schema/sol2uml/`, replacing the `doc/schema/vscode-uml/` images removed in `d6621e2` — the 14 references left in `doc/README.md` pointed at deleted files. Added [doc/script/script_sol2uml.sh](./doc/script/script_sol2uml.sh), which regenerates the whole set from relative source paths, so the diagrams are reproducible like the Surya ones. New diagrams for `TokenBindingModule`, `TokenBindingExtendedModule` and `ERC3643ComplianceExtendedModule` were added to their sections. +- `doc/README.md`: the eight `[...](../src/...)` links now use absolute GitHub URLs. `doc/script/convert_links_for_pdf.sh` only rewrites the `./` form and its base URL points at the `doc/` directory, so a `../` link cannot be rewritten and stayed dead in the generated specification PDF. +- `doc/README.md`: the "Role by modules" table now lists `bindToken` / `unbindToken` under `TokenBindingModule` and the batch and self-binding functions under `TokenBindingExtendedModule`, adds the missing `setMaxRules` row, and points `COMPLIANCE_MANAGER_ROLE` to `ERC3643ComplianceRolesStorage`, where it is actually declared. + +### v3.0.0-rc5-2026-08-13 + +Commit: `ab9def2f19ae71af304127f42d20d9831cad1a2b` ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 6e92152..f64b8dc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,7 @@ CMTAT submodule also needs `cd lib/CMTAT && npm install` for its OpenZeppelin de | `CMTATv3.0.0/` | `lib/CMTATv3.0.0/contracts/` | | `@openzeppelin/contracts/` | `lib/openzeppelin-contracts/contracts` | -Use `@openzeppelin/contracts/` for OpenZeppelin imports, `CMTAT/` for CMTAT imports, `src/` for local imports. +Use `@openzeppelin/contracts/` for OpenZeppelin imports and `CMTAT/` for CMTAT imports. For project files, `src/` and `script/` import **relatively** (`./modules/...`, `../src/...`); the `src/` remapping is used by the tests. ## Architecture @@ -72,10 +72,14 @@ RuleEngineBase (abstract) ├── RulesManagementModule → add/remove/set/clear rules, maxRules cap │ ├── AccessControl (OZ) │ └── RulesManagementModuleInvariantStorage → errors, events, roles -├── ERC3643ComplianceExtendedModule → bind/unbind tokens (extended API) -│ └── ERC3643ComplianceModule → core ERC-3643 compliance -│ ├── IERC3643Compliance -│ └── ERC3643ComplianceModuleInvariantStorage → errors +├── ERC3643ComplianceExtendedModule → ERC-3643 flavour of the binding registry +│ ├── ERC3643ComplianceModule → ERC-3643 adapter: getTokenBound(), compliance naming +│ │ ├── IERC3643Compliance +│ │ └── TokenBindingModule → bind/unbind tokens (standard-agnostic registry) +│ │ ├── ITokenBinding +│ │ └── TokenBindingModuleInvariantStorage → errors +│ └── TokenBindingExtendedModule → batch binding, token self-binding (standard-agnostic) +│ └── ITokenBindingExtended ├── RuleEngineInvariantStorage → errors └── IRuleEngineERC1404 → CMTAT interface @@ -105,7 +109,9 @@ Modules define **virtual internal hooks** for access control. Concrete contracts function _onlyRulesManager() internal virtual; function _onlyRulesLimitManager() internal virtual; // guards setMaxRules -// In ERC3643ComplianceModule (abstract): +// In TokenBindingModule (abstract): +function _onlyTokenBindingManager() internal virtual; +// wired by ERC3643ComplianceModule to its own abstract hook: function _onlyComplianceManager() internal virtual; // RuleEngine overrides with RBAC: @@ -182,11 +188,25 @@ can report a mint as allowed that `transferred(spender, ...)` will revert. Use t `detectTransferRestrictionFrom` / `canTransferFrom` to pre-check an operation that has an operator. See `H-1` in `doc/security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md`. +### Token Binding Layering + +Token binding is split so it can be reused outside this project: + +- `TokenBindingModule` / `TokenBindingExtendedModule` (+ `ITokenBinding` / `ITokenBindingExtended`, + `TokenBindingModuleInvariantStorage`) hold the whole registry and depend only on OpenZeppelin + (`Context`, `EnumerableSet`). No rule, ERC-1404 or ERC-3643 code. +- `ERC3643ComplianceModule` / `ERC3643ComplianceExtendedModule` are thin ERC-3643 adapters: they add + `getTokenBound()` and wire `_onlyTokenBindingManager()` to `_onlyComplianceManager()`. + +**Keep new binding logic in the generic modules and new ERC-3643 logic in the adapters.** +`src/mocks/TokenBindingStandaloneMock.sol` (+ `test/TokenBinding/`) pins that the registry still works +standalone. See `doc/technical/TokenBinding-module.md`. + ### Storage: EnumerableSet Both rules and bound tokens use `EnumerableSet.AddressSet`: - `_rules` in `RulesManagementModule` — the set of active rules -- `_boundTokens` in `ERC3643ComplianceModule` — tokens allowed to call `transferred` +- `_boundTokens` in `TokenBindingModule` — tokens allowed to call `transferred` This gives O(1) add/remove/contains and iterable storage. @@ -196,14 +216,25 @@ This gives O(1) add/remove/contains and iterable storage. |-----------|---------|---------------| | `IRule` | What every rule must implement (extends `IRuleEngineERC1404`) | `src/interfaces/IRule.sol` | | `IRulesManagementModule` | Rule CRUD operations | `src/interfaces/IRulesManagementModule.sol` | -| `IERC3643Compliance` | Token binding + compliance hooks | `src/interfaces/IERC3643Compliance.sol` | +| `ITokenBinding` | Token binding registry, standard-agnostic | `src/interfaces/ITokenBinding.sol` | +| `ITokenBindingExtended` | Batch binding, token self-binding, `getTokenBounds` | `src/interfaces/ITokenBindingExtended.sol` | +| `IERC3643Compliance` | ERC-3643 compliance hooks (extends `ITokenBinding`) | `src/interfaces/IERC3643Compliance.sol` | | `IRuleEngine` | Full CMTAT integration interface | `lib/CMTAT/contracts/interfaces/engine/IRuleEngine.sol` | **ERC-165 interface IDs:** -- `IRule`: `0x2497d6cb` (defined in `src/modules/library/RuleInterfaceId.sol`) +- `IRule`: `0x2497d6cb` (`src/modules/library/RuleInterfaceId.sol`) +- `IERC3643Compliance`: `0x3144991c`, extended: `0x646ba2be`, `IERC7551Compliance`: `0x7157797f` (`ComplianceInterfaceId.sol`) +- `IERC1404`: `0xab84a5c8` (`ERC1404InterfaceId.sol`) - `IRuleEngine`: from `CMTAT/library/RuleEngineInterfaceId.sol` - `IERC1404Extend`: from `CMTAT/library/ERC1404ExtendInterfaceId.sol` -- `ERC-173`: `0x7f5828d0` (hardcoded in `RuleEngineOwnable`) +- `ERC-173`: `0x7f5828d0` (`OwnableInterfaceId.sol`); `Ownable2Step` subset: `0x9ab669ef` + +**The project's IDs are computed, not hardcoded.** `type(I).interfaceId` covers only the functions `I` +declares *directly*, so each constant XORs the interface with its parents; a marker interface that declares +nothing of its own — `IERC3643ComplianceExtended` — has a `type(...).interfaceId` of `0x00000000` and must +never be used for an ERC-165 check. `test/RuleEngine/IRuleInterfaceId.t.sol` pins every constant to its wire +value, so an upstream interface change fails a test instead of silently changing what `supportsInterface` +answers. ERC-173 and the `Ownable2Step` subset stay literal: neither has an interface declaration in scope. ## Invariant Storage Pattern @@ -213,6 +244,7 @@ Errors, events, and role constants are centralized in "invariant storage" abstra |----------|----------| | `RuleEngineInvariantStorage` | `RuleEngine_AdminWithAddressZeroNotAllowed`, `RuleEngine_RuleInvalidInterface` | | `RulesManagementModuleInvariantStorage` | Rule errors, `AddRule`/`RemoveRule`/`ClearRules` events, `RULES_MANAGEMENT_ROLE` | +| `TokenBindingModuleInvariantStorage` | `TokenBinding_*` binding errors (standard-agnostic, no `RuleEngine_` prefix) | **Convention:** Error names follow `Contract_Module_ErrorName` pattern. Test contracts inherit these to access `.selector` for `vm.expectRevert`. @@ -226,8 +258,9 @@ src/ │ └── RuleEngineOwnable2Step.sol # Ownable2Step variant (deploy this) ├── RuleEngineBase.sol # Abstract core logic (do not deploy) ├── RuleEngineOwnableShared.sol # Shared logic for ownable variants -├── interfaces/ # IRule, IRulesManagementModule, IERC3643Compliance -├── modules/ # VersionModule, RulesManagementModule, ERC3643ComplianceModule, ERC2771ModuleStandalone +├── interfaces/ # IRule, IRulesManagementModule, ITokenBinding(Extended), IERC3643Compliance(Extended) +├── modules/ # VersionModule, RulesManagementModule, TokenBinding(Extended)Module, +│ # ERC3643Compliance(Extended)Module, ERC2771ModuleStandalone │ └── library/ # InvariantStorage contracts, RuleInterfaceId └── mocks/ # Test-only/reference contracts diff --git a/README.md b/README.md index 311b422..775744c 100644 --- a/README.md +++ b/README.md @@ -49,18 +49,22 @@ Sequence diagrams for each token type: [CMTAT](./doc/schema/plantuml/ruleengine- ## Architecture ``` -RuleEngineBase (abstract) — core logic, shared by all variants -├── VersionModule — version() -├── RulesManagementModule — add/remove/set/clear rules, maxRules cap -├── ERC3643ComplianceExtendedModule -│ └── ERC3643ComplianceModule — bind/unbind tokens, compliance hooks -└── IRuleEngineERC1404 — CMTAT interface +RuleEngineBase (abstract) — core logic, shared by all variants +├── VersionModule — version() +├── RulesManagementModule — add/remove/set/clear rules, maxRules cap +├── ERC3643ComplianceExtendedModule — ERC-3643 flavour of the binding registry +│ ├── ERC3643ComplianceModule — getTokenBound(), compliance manager hook +│ │ └── TokenBindingModule — bind/unbind tokens (standard-agnostic) +│ └── TokenBindingExtendedModule — batch binding, token self-binding +└── IRuleEngineERC1404 — CMTAT interface RuleEngine = RuleEngineBase + AccessControl + ERC2771ModuleStandalone RuleEngineOwnable = RuleEngineOwnableShared + Ownable + ERC2771ModuleStandalone RuleEngineOwnable2Step = RuleEngineOwnableShared + Ownable2Step + ERC2771ModuleStandalone ``` +Token binding is deliberately split in two layers: `TokenBindingModule` / `TokenBindingExtendedModule` hold the whole registry (storage, `bindToken` / `unbindToken` / `isTokenBound`, batch binding, self-binding, the `onlyBoundToken` guard) and depend on nothing but OpenZeppelin, so they can be reused by any project that has to bind tokens; `ERC3643ComplianceModule` / `ERC3643ComplianceExtendedModule` are thin ERC-3643 adapters on top, adding `getTokenBound()` and the compliance-manager vocabulary. `src/mocks/TokenBindingStandaloneMock.sol` shows the registry used on its own, outside any compliance context. + Modules declare access control as **virtual internal hooks** (`_onlyRulesManager`, `_onlyComplianceManager`, `_onlyRulesLimitManager`); each deployable contract overrides them with either RBAC roles or `onlyOwner`. Rules and bound tokens are stored in OpenZeppelin `EnumerableSet.AddressSet` for O(1) add/remove/contains plus iteration. ## Repository layout @@ -73,8 +77,9 @@ src/ │ ├── RuleEngine.sol │ ├── RuleEngineOwnable.sol │ └── RuleEngineOwnable2Step.sol -├── interfaces/ # IRule, IRulesManagementModule, IERC3643Compliance(Extended) -├── modules/ # VersionModule, RulesManagementModule, +├── interfaces/ # IRule, IRulesManagementModule, ITokenBinding(Extended), +│ # IERC3643Compliance(Extended) +├── modules/ # VersionModule, RulesManagementModule, TokenBinding(Extended)Module, │ │ # ERC3643Compliance(Extended)Module, ERC2771ModuleStandalone │ └── library/ # invariant storage (errors/events), role constants, interface IDs └── mocks/ # reference rules and test doubles — not for production diff --git a/doc/README.md b/doc/README.md index 6895182..1190963 100644 --- a/doc/README.md +++ b/doc/README.md @@ -34,6 +34,7 @@ The RuleEngine is an external contract used to apply transfer restrictions to an - [Contract Constructors](#contract-constructors) - [RuleEngineBase](#ruleenginebase) - [VersionModule](#versionmodule) + - [TokenBindingModule](#tokenbindingmodule) - [ERC3643ComplianceModule](#erc3643compliancemodule) - [ERC3643ComplianceExtendedModule](#erc3643complianceextendedmodule) - [RulesManagementModule](#rulesmanagementmodule) @@ -323,14 +324,20 @@ 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). +The [ERC-3643](https://eips.ethereum.org/EIPS/eip-3643) compliance interface is defined in [IERC3643Compliance.sol](https://github.com/CMTA/RuleEngine/blob/main/src/interfaces/IERC3643Compliance.sol). +Non-standard helper functions are defined in [IERC3643ComplianceExtended.sol](https://github.com/CMTA/RuleEngine/blob/main/src/interfaces/IERC3643ComplianceExtended.sol). + +Token binding itself is not specific to ERC-3643, so it is declared in the standard-agnostic +[ITokenBinding.sol](https://github.com/CMTA/RuleEngine/blob/main/src/interfaces/ITokenBinding.sol) (`bindToken`, `unbindToken`, `isTokenBound`, +`TokenBound` / `TokenUnbound`) and [ITokenBindingExtended.sol](https://github.com/CMTA/RuleEngine/blob/main/src/interfaces/ITokenBindingExtended.sol) +(batch binding, token self-binding, `getTokenBounds`), which the two ERC-3643 interfaces above extend. 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) +- Token binding registry, reusable outside any compliance context: [TokenBindingModule.sol](https://github.com/CMTA/RuleEngine/blob/main/src/modules/TokenBindingModule.sol) and [TokenBindingExtendedModule.sol](https://github.com/CMTA/RuleEngine/blob/main/src/modules/TokenBindingExtendedModule.sol) +- Base ERC-3643 surface: [ERC3643ComplianceModule.sol](https://github.com/CMTA/RuleEngine/blob/main/src/modules/ERC3643ComplianceModule.sol) +- Non-standard extensions: [ERC3643ComplianceExtendedModule.sol](https://github.com/CMTA/RuleEngine/blob/main/src/modules/ERC3643ComplianceExtendedModule.sol) -![ERC3643ComplianceModuleUML](./schema/vscode-uml/ERC3643ComplianceModuleUML.png) +![ERC3643ComplianceModuleUML](./schema/sol2uml/ERC3643ComplianceModuleUML.png) ## Technical @@ -386,16 +393,31 @@ The table below summarizes which ERC-165 interfaces are advertised by each deplo | `IERC1404` | `0xab84a5c8` | | | | | `IERC1404Extend` | `0x78a8de7d` | | | | | `IERC3643Compliance` | `0x3144991c` | | | | +| `IERC3643ComplianceExtended` | `0x646ba2be` | | | | | `IERC7551Compliance` (subset) | `0x7157797f` | | | | | `IERC173` | `0x7f5828d0` | | | | | `Ownable2Step` specific (`pendingOwner()`, `acceptOwnership()`) | `0x9ab669ef` | | | | | `IAccessControl` | `0x7965db0b` | | | | | `IAccessControlEnumerable` | `0x5a05180f` | | | | +The six interfaces common to all three variants come from `RuleEngineBase._supportsRuleEngineBaseInterface`; +each deployable adds the IDs of its own access-control model on top. + Notes: - `RuleEngine` advertises OpenZeppelin RBAC interfaces because it inherits `AccessControlEnumerable`. - `RuleEngineOwnable` / `RuleEngineOwnable2Step` intentionally do not advertise `IAccessControl`. - `Ownable2Step` specific interface ID is defined in `Ownable2StepInterfaceId` and includes only `pendingOwner()` and `acceptOwnership()`. +- **`IRule` (`0x2497d6cb`) is deliberately absent from this table.** The engine does not implement `IRule`; it + *requires* it of every rule, checking it in `_checkRule` before a rule is added. Do not expect + `ruleEngine.supportsInterface(0x2497d6cb)` to return true. +- **The IDs are computed from the interfaces, not hardcoded** — see `ComplianceInterfaceId`, `RuleInterfaceId` + and `ERC1404InterfaceId`, and `test/RuleEngine/IRuleInterfaceId.t.sol`, which pins each one to the wire value + above. `type(I).interfaceId` counts only the functions `I` declares *directly*, so every constant XORs the + interface with its parents. +- **Never use `type(IERC3643ComplianceExtended).interfaceId`.** That interface declares no function of its own — + it only combines `IERC3643Compliance` and `ITokenBindingExtended` — so the expression evaluates to + `0x00000000`. Use `ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID` (`0x646ba2be`), which is + computed from `ITokenBindingExtended`. #### Role list (RuleEngine only) @@ -413,7 +435,7 @@ It is set in the constructor when the contract is deployed. | ----------------------- | -------------------------------- | ------------------------------------------------------------ | | DEFAULT_ADMIN_ROLE | OpenZeppelin
AccessControl | 0x0000000000000000000000000000000000000000000000000000000000000000 | | **Modules** | | | -| COMPLIANCE_MANAGER_ROLE | ERC3643ComplianceModule | 0xe5c50d0927e06141e032cb9a67e1d7092dc85c0b0825191f7e1cede600028568 | +| COMPLIANCE_MANAGER_ROLE | ERC3643ComplianceRolesStorage | 0xe5c50d0927e06141e032cb9a67e1d7092dc85c0b0825191f7e1cede600028568 | | RULES_MANAGEMENT_ROLE | RulesManagementModuleInvariantStorage | 0xea5f4eb72290e50c32abd6c23e45de3d8300b3286e1cbc2e293114b92e034e5e | @@ -421,7 +443,10 @@ It is set in the constructor when the contract is deployed. #### Schema (RuleEngine) Here is a schema of the Access Control for `RuleEngine`. -![alt text](./security/accessControl/access-control-RuleEngine.png) + +![RuleEngine access control](./schema/plantuml/ruleengine-access-control.png) + +_Diagram source: [doc/schema/plantuml/ruleengine-access-control.puml](./schema/plantuml/ruleengine-access-control.puml)._ #### Role by modules (RuleEngine) @@ -438,10 +463,11 @@ For function signatures, struct arguments are represented with their correspondi | | `clearRules()` | public | - |-|RULES_MANAGEMENT_ROLE| | | `addRule(address rule_)` | public | `IRule rule_` |-|RULES_MANAGEMENT_ROLE| | | `removeRule(address rule_)` | public | `IRule rule_` |-|RULES_MANAGEMENT_ROLE| -| ERC3643ComplianceModule | | | | | | +| | `setMaxRules(uint256 maxRules_)` | public | `uint256 maxRules_` |-|DEFAULT_ADMIN_ROLE| +| TokenBindingModule | | | | | | | | `bindToken(address token)` | public | `address token` | - | COMPLIANCE_MANAGER_ROLE or approved token self-call | | | `unbindToken(address token)` | public | `address token` | - | COMPLIANCE_MANAGER_ROLE or approved token self-call | -| ERC3643ComplianceExtendedModule | | | | | | +| TokenBindingExtendedModule | | | | | | | | `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 | @@ -454,14 +480,17 @@ For function signatures, struct arguments are represented with their correspondi ### UML -Here is the UML of the main contracts: +Here is the UML of the main contracts. The diagrams are generated with +[sol2uml](https://github.com/naddison36/sol2uml), one per contract or interface, by +[doc/script/script_sol2uml.sh](./script/script_sol2uml.sh); rerun that script after changing a +contract's surface. #### RuleEngine -![RuleEngineUML](./schema/vscode-uml/RuleEngineUML.png) +![RuleEngineUML](./schema/sol2uml/RuleEngineUML.png) #### RuleEngineOwnable -![RuleEngineOwnableUML](./schema/vscode-uml/RuleEngineOwnableUML.png) +![RuleEngineOwnableUML](./schema/sol2uml/RuleEngineOwnableUML.png) `RuleEngineOwnable` shares the same base functionality as `RuleEngine` but uses ERC-173 ownership instead of RBAC. @@ -485,7 +514,7 @@ RuleEngineOwnable #### RuleEngineOwnable2Step -![RuleEngineOwnable2StepUML](./schema/vscode-uml/RuleEngineOwnable2StepUML.png) +![RuleEngineOwnable2StepUML](./schema/sol2uml/RuleEngineOwnable2StepUML.png) `RuleEngineOwnable2Step` shares the same base functionality as `RuleEngineOwnable` but uses OpenZeppelin's `Ownable2Step` for safer ownership handover. @@ -647,7 +676,7 @@ constructor( ### RuleEngineBase -![RuleEngineBaseUML](./schema/vscode-uml/RuleEngineBaseUML.png) +![RuleEngineBaseUML](./schema/sol2uml/RuleEngineBaseUML.png) #### Contracts Description Table @@ -678,7 +707,7 @@ constructor( #### IRuleEngine -![IRuleEngineUML](./schema/vscode-uml/IRuleEngineUML.png) +![IRuleEngineUML](./schema/sol2uml/IRuleEngineUML.png) ##### transferred(address spender, address from, address to, uint256 value) @@ -706,7 +735,7 @@ Must revert if the transfer is invalid. #### IERC7551Compliance -![IERC7551ComplianceUML](./schema/vscode-uml/IERC7551ComplianceUML.png) +![IERC7551ComplianceUML](./schema/sol2uml/IERC7551ComplianceUML.png) > Note: ERC-7551 is draft (not final). The `IERC7551Compliance` interface used here is a subset interface exposing the compliance check `canTransferFrom`. @@ -735,7 +764,7 @@ Does not check balances or access rights (Access Control). #### IERC3643ComplianceRead -![IERC3643ComplianceReadUML](./schema/vscode-uml/IERC3643ComplianceReadUML.png) +![IERC3643ComplianceReadUML](./schema/sol2uml/IERC3643ComplianceReadUML.png) ------ @@ -770,7 +799,7 @@ Does not check balances or access rights (Access Control). #### IERC3643IComplianceContract -![IERC3643IComplianceContractUML](./schema/vscode-uml/IERC3643IComplianceContractUML.png) +![IERC3643IComplianceContractUML](./schema/sol2uml/IERC3643IComplianceContractUML.png) ------ @@ -849,7 +878,7 @@ Called by the token contract when tokens are redeemed or burned. #### IERC1404 -![IERC1404UML](./schema/vscode-uml/IERC1404UML.png) +![IERC1404UML](./schema/sol2uml/IERC1404UML.png) ------ @@ -909,7 +938,7 @@ Implements {ERC-1404} standard message accessor. #### IERC1404Extend -![IERC1404ExtendUML](./schema/vscode-uml/IERC1404ExtendUML.png) +![IERC1404ExtendUML](./schema/sol2uml/IERC1404ExtendUML.png) ##### enum REJECTED_CODE_BASE @@ -962,7 +991,7 @@ This is an extension of {ERC-1404} with an additional `spender` parameter to enf ### VersionModule -![VersionModuleUML](./schema/vscode-uml/VersionModuleUML.png) +![VersionModuleUML](./schema/sol2uml/VersionModuleUML.png) #### Contracts Description Table @@ -999,9 +1028,59 @@ Useful for identifying which version of the smart contract is deployed and in us +### TokenBindingModule + +![TokenBindingModuleUML](./schema/sol2uml/TokenBindingModuleUML.png) + +`TokenBindingModule` holds the token binding registry itself: the set of tokens allowed to call the +bound-token entry points, `bindToken` / `unbindToken` / `isTokenBound`, and the `onlyBoundToken` +guard. It is standard-agnostic — it contains no ERC-3643, ERC-1404 or rule logic and depends only on +OpenZeppelin's `Context` and `EnumerableSet` — so it can be reused as-is by any project that has to +bind tokens. A deployment only has to provide the access control by implementing +`_onlyTokenBindingManager()`; `src/mocks/TokenBindingStandaloneMock.sol` is a minimal example of such +a reuse, outside any compliance context. + +![TokenBindingExtendedModuleUML](./schema/sol2uml/TokenBindingExtendedModuleUML.png) + +`TokenBindingExtendedModule` adds the conveniences of the registry that are not part of any token +standard: batch bind/unbind, token self-binding approval (used by ERC-3643 `setCompliance`), and +`getTokenBounds()`. It also replaces the default binding authorization with one that accepts an +approved token binding itself. + +The events and functions of both modules are documented in the events and functions reference below, +under `ERC3643ComplianceExtendedModule`, since that is the form in which the RuleEngine exposes them. + +#### Contracts Description Table + + +| Contract | Type | Bases | | | +| :----------------------------: | :---------------: | :-------------------------------------------: | :------------: | :----------------------: | +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +| | | | | | +| **TokenBindingModule** | Implementation | Context, ITokenBinding | | | +| └ | bindToken | Public ❗️ | 🛑 | NO❗️ | +| └ | unbindToken | Public ❗️ | 🛑 | NO❗️ | +| └ | isTokenBound | Public ❗️ | | NO❗️ | +| └ | _bindToken | Internal 🔒 | 🛑 | | +| └ | _unbindToken | Internal 🔒 | 🛑 | | +| **TokenBindingExtendedModule** | Implementation | TokenBindingModule, ITokenBindingExtended | | | +| └ | bindTokens | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | unbindTokens | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | setTokenSelfBindingApproval | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | setTokenSelfBindingApprovalBatch | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | isTokenSelfBindingApproved | Public ❗️ | | NO❗️ | +| └ | getTokenBounds | Public ❗️ | | NO❗️ | + ### ERC3643ComplianceModule -![ERC3643ComplianceModuleUML](./schema/vscode-uml/ERC3643ComplianceModuleUML.png) +![ERC3643ComplianceModuleUML](./schema/sol2uml/ERC3643ComplianceModuleUML.png) + +`ERC3643ComplianceModule` is a thin ERC-3643 adapter over `TokenBindingModule`: it adds the +ERC-3643 specific view `getTokenBound()` and names the binding manager in compliance terms, wiring +the generic `_onlyTokenBindingManager()` hook to `_onlyComplianceManager()`, which the deployable +contracts implement (`COMPLIANCE_MANAGER_ROLE` for `RuleEngine`, `onlyOwner` for the ownable +variants). The compliance callbacks `transferred`, `created` and `destroyed` are implemented by +`RuleEngineBase`, since they depend on the rules rather than on the binding. #### Contracts Description Table @@ -1010,17 +1089,15 @@ Useful for identifying which version of the smart contract is deployed and in us | :-------------------------: | :---------------: | :-------------------------------: | :------------: | :-----------: | | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | | | | | | | -| **ERC3643ComplianceModule** | Implementation | Context, IERC3643Compliance | | | -| └ | bindToken | Public ❗️ | 🛑 | onlyRole | -| └ | unbindToken | Public ❗️ | 🛑 | onlyRole | -| └ | isTokenBound | Public ❗️ | | NO❗️ | +| **ERC3643ComplianceModule** | Implementation | TokenBindingModule, IERC3643Compliance | | | | └ | getTokenBound | Public ❗️ | | NO❗️ | +| └ | _onlyTokenBindingManager | Internal 🔒 | 🛑 | | ### 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 🔒 | 🛑 | | +![ERC3643ComplianceExtendedModuleUML](./schema/sol2uml/ERC3643ComplianceExtendedModuleUML.png) + +`ERC3643ComplianceExtendedModule` combines `ERC3643ComplianceModule` with `TokenBindingExtendedModule` and declares `IERC3643ComplianceExtended`. It carries no logic of its own: the project-specific helpers that are not part of the ERC-3643 base interface (`IERC3643Compliance`) — batch bind/unbind, self-binding approval APIs and `getTokenBounds()` — are standard-agnostic and therefore implemented in `TokenBindingExtendedModule`. #### Events @@ -1067,11 +1144,14 @@ Emitted when a token is successfully unbound from the compliance contract. ```solidity function bindToken(address token) public override virtual -onlyRole(COMPLIANCE_MANAGER_ROLE) ``` Associates a token contract with this compliance contract. +Implemented by `TokenBindingModule`. Authorization goes through `_authorizeTokenBindingChange`, which +accepts the compliance manager (`COMPLIANCE_MANAGER_ROLE` for `RuleEngine`, the owner for the ownable +variants), or the token itself when its self-binding has been approved. + The compliance contract may restrict operations on the bound token according to its internal compliance logic. Reverts if the token is already bound. @@ -1090,11 +1170,12 @@ The compliance contract may restrict operations on the bound token according to ```solidity function unbindToken(address token) public override virtual -onlyRole(COMPLIANCE_MANAGER_ROLE) ``` Removes the association of a token contract from this compliance contract. +Implemented by `TokenBindingModule`, with the same authorization as `bindToken`. + Reverts if the token is not currently bound. **Input Parameters:** @@ -1186,7 +1267,7 @@ This is designed to mostly be used by view accessors that are queried without an ### RulesManagementModule -![RuleManagementModuleUML](./schema/vscode-uml/RuleManagementModuleUML.png) +![RuleManagementModuleUML](./schema/sol2uml/RuleManagementModuleUML.png) #### Events @@ -1473,6 +1554,7 @@ Here is the list of report performed with [Slither](https://github.com/crytic/sl | Version | Report | Assessment | | ------- | ------ | ---------- | +| v3.0.0-rc6 | [slither-report.md](./security/audits/tools/v3.0.0-rc6/slither-report.md) | [slither-report-feedback.md](./security/audits/tools/v3.0.0-rc6/slither-report-feedback.md) | | v3.0.0-rc5 | [slither-report.md](./security/audits/tools/v3.0.0-rc5/slither-report.md) | [slither-report-feedback.md](./security/audits/tools/v3.0.0-rc5/slither-report-feedback.md) | | v3.0.0-rc4 | [slither-report.md](./security/audits/tools/v3.0.0-rc4/slither-report.md) | [slither-report-feedback.md](./security/audits/tools/v3.0.0-rc4/slither-report-feedback.md) | | v3.0.0-rc3 | [slither-report.md](./security/audits/tools/v3.0.0-rc3/slither-report.md) | [slither-report-feedback.md](./security/audits/tools/v3.0.0-rc3/slither-report-feedback.md) | @@ -1482,12 +1564,13 @@ Here is the list of report performed with [Slither](https://github.com/crytic/sl slither . --checklist --filter-paths "openzeppelin-contracts|test|CMTAT|forge-std|mocks" > slither-report.md ``` -2 finding categories — 0 High · 0 Medium · 10 Low · 2 Informational +Latest run (v3.0.0-rc6, 108 contracts analysed, mocks excluded): 2 finding categories — +0 High · 0 Medium · 10 Low · 2 Informational. **Nothing to fix.** | ID | Detector | Impact | Instances | Assessment | |----|----------|--------|-----------|------------| | 0–9 | `calls-loop` | Low | 10 | Accepted by design — fan-out to rule contracts is the core architecture | -| 10–11 | `unindexed-event-address` | Informational | 2 | Deferred — adding `indexed` to `TokenBound`/`TokenUnbound` is interface-breaking | +| 10–11 | `unindexed-event-address` | Informational | 2 | Accepted by design — `TokenBound`/`TokenUnbound` keep the unindexed signature of the ERC-3643 reference | #### Aderyn @@ -1499,26 +1582,29 @@ aderyn -x mocks --output aderyn-report.md | Version | Report | Assessment | | ------- | ------ | ---------- | +| v3.0.0-rc6 | [aderyn-report.md](./security/audits/tools/v3.0.0-rc6/aderyn-report.md) | [aderyn-report-feedback.md](./security/audits/tools/v3.0.0-rc6/aderyn-report-feedback.md) | | v3.0.0-rc5 | [aderyn-report.md](./security/audits/tools/v3.0.0-rc5/aderyn-report.md) | [aderyn-report-feedback.md](./security/audits/tools/v3.0.0-rc5/aderyn-report-feedback.md) | | v3.0.0-rc4 | [aderyn-report.md](./security/audits/tools/v3.0.0-rc4/aderyn-report.md) | [aderyn-report-feedback.md](./security/audits/tools/v3.0.0-rc4/aderyn-report-feedback.md) | | v3.0.0-rc3 | [aderyn-report.md](./security/audits/tools/v3.0.0-rc3/aderyn-report.md) | [aderyn-report-feedback.md](./security/audits/tools/v3.0.0-rc3/aderyn-report-feedback.md) | | v3.0.0-rc2 | [aderyn-report.md](./security/audits/tools/v3.0.0-rc2/aderyn-report.md) | [aderyn-report-feedback.md](./security/audits/tools/v3.0.0-rc2/aderyn-report-feedback.md) | -Report scope: 24 Solidity files, 629 nSLOC. +Latest run (v3.0.0-rc6): 28 Solidity files, 683 nSLOC, mocks excluded. -0 High · 8 Low +0 High · 8 Low (84 instances). **Nothing to fix.** | ID | Finding | Instances | Assessment | |----|---------|-----------|------------| | L-1 | Centralization Risk | 14 | Accepted by design — privileged compliance tool | -| L-2 | Unspecific Solidity Pragma | 19 | Accepted by design — intentional for library reusability | -| L-3 | PUSH0 Opcode | 24 | Not applicable — project targets Prague EVM | +| L-2 | Unspecific Solidity Pragma | 23 | Accepted by design — intentional for library reusability | +| L-3 | PUSH0 Opcode | 28 | Not applicable — project targets Prague EVM | | L-4 | Modifier Invoked Only Once | 1 | Accepted by design — keeps hook-style access-control abstraction | | L-5 | Empty Block | 9 | Accepted by design — access-control hook pattern | | L-6 | Loop Contains `require`/`revert` | 4 | Accepted by design — `setRules` and `bindTokens`/`unbindTokens` are atomic batch operations | | L-7 | Costly Operations Inside Loop | 4 | Accepted — unavoidable `SSTORE` in batch operations | | L-8 | Unchecked Return | 1 | Accepted — `_grantRole` return is irrelevant in constructor | +Overview of every analysis performed: [AUDIT_OVERVIEW.md](./security/audits/AUDIT_OVERVIEW.md). + ## Documentation Here a summary of the main documentation @@ -1527,9 +1613,10 @@ Here a summary of the main documentation | ------------ | --------------------------------------- | | Integration with CMTAT | [doc/technical/RuleEngine-with-CMTAT.md](./technical/RuleEngine-with-CMTAT.md) | | Integration with ERC-3643 | [doc/technical/RuleEngine-with-ERC3643.md](./technical/RuleEngine-with-ERC3643.md) | +| Token binding module (reusable outside this project) | [doc/technical/TokenBinding-module.md](./technical/TokenBinding-module.md) | | Toolchain | [doc/TOOLCHAIN.md](./TOOLCHAIN.md) | | Surya report | [doc/schema/surya](./schema/surya/) | -| Code-quality review | [doc/security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md](./security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md) | +| Code-quality review | [doc/security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md](./security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md) | | Script review | [doc/security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS_SCRIPT.md](./security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS_SCRIPT.md) | | Audit overview | [doc/security/audits/AUDIT_OVERVIEW.md](./security/audits/AUDIT_OVERVIEW.md) | diff --git a/doc/coverage/code-coverage.png b/doc/coverage/code-coverage.png index 8ae5e00..64106d3 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 de6d7c8..61e8e0b 100644 --- a/doc/coverage/coverage/index-sort-b.html +++ b/doc/coverage/coverage/index-sort-b.html @@ -31,27 +31,27 @@ lcov.info Lines: - 270 - 274 - 98.5 % + 278 + 282 + 98.6 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 81 - 85 - 95.3 % + 86 + 90 + 95.6 % Branches: - 40 - 43 - 93.0 % + 39 + 41 + 95.1 % @@ -84,14 +84,14 @@ src/modules -
96.6%96.6%
+
96.8%96.8%
- 96.6 % - 114 / 118 - 89.5 % - 34 / 38 - 90.3 % - 28 / 31 + 96.8 % + 122 / 126 + 90.7 % + 39 / 43 + 93.1 % + 27 / 29 script diff --git a/doc/coverage/coverage/index-sort-f.html b/doc/coverage/coverage/index-sort-f.html index 0d8d1de..df683a4 100644 --- a/doc/coverage/coverage/index-sort-f.html +++ b/doc/coverage/coverage/index-sort-f.html @@ -31,27 +31,27 @@ lcov.info Lines: - 270 - 274 - 98.5 % + 278 + 282 + 98.6 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 81 - 85 - 95.3 % + 86 + 90 + 95.6 % Branches: - 40 - 43 - 93.0 % + 39 + 41 + 95.1 % @@ -84,14 +84,14 @@ src/modules -
96.6%96.6%
+
96.8%96.8%
- 96.6 % - 114 / 118 - 89.5 % - 34 / 38 - 90.3 % - 28 / 31 + 96.8 % + 122 / 126 + 90.7 % + 39 / 43 + 93.1 % + 27 / 29 script diff --git a/doc/coverage/coverage/index-sort-l.html b/doc/coverage/coverage/index-sort-l.html index c42e123..560f672 100644 --- a/doc/coverage/coverage/index-sort-l.html +++ b/doc/coverage/coverage/index-sort-l.html @@ -31,27 +31,27 @@ lcov.info Lines: - 270 - 274 - 98.5 % + 278 + 282 + 98.6 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 81 - 85 - 95.3 % + 86 + 90 + 95.6 % Branches: - 40 - 43 - 93.0 % + 39 + 41 + 95.1 % @@ -84,14 +84,14 @@ src/modules -
96.6%96.6%
+
96.8%96.8%
- 96.6 % - 114 / 118 - 89.5 % - 34 / 38 - 90.3 % - 28 / 31 + 96.8 % + 122 / 126 + 90.7 % + 39 / 43 + 93.1 % + 27 / 29 script diff --git a/doc/coverage/coverage/index.html b/doc/coverage/coverage/index.html index 8c088b1..0e6051a 100644 --- a/doc/coverage/coverage/index.html +++ b/doc/coverage/coverage/index.html @@ -31,27 +31,27 @@ lcov.info Lines: - 270 - 274 - 98.5 % + 278 + 282 + 98.6 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 81 - 85 - 95.3 % + 86 + 90 + 95.6 % Branches: - 40 - 43 - 93.0 % + 39 + 41 + 95.1 % @@ -120,14 +120,14 @@ src/modules -
96.6%96.6%
+
96.8%96.8%
- 96.6 % - 114 / 118 - 89.5 % - 34 / 38 - 90.3 % - 28 / 31 + 96.8 % + 122 / 126 + 90.7 % + 39 / 43 + 93.1 % + 27 / 29 diff --git a/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func-sort-c.html b/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func-sort-c.html index 2cf717f..7e6bec3 100644 --- a/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func-sort-c.html +++ b/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func-sort-c.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 @@ -69,7 +69,7 @@ Hit count Sort by hit count - CMTATWithRuleEngineScript.run + CMTATWithRuleEngineScript.run 1 diff --git a/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func.html b/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func.html index a4a33ae..3dc6a24 100644 --- a/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func.html +++ b/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.func.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 @@ -69,7 +69,7 @@ Hit count Sort by hit count - CMTATWithRuleEngineScript.run + CMTATWithRuleEngineScript.run 1 diff --git a/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.gcov.html b/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.gcov.html index df6245e..7f6dc16 100644 --- a/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.gcov.html +++ b/doc/coverage/coverage/script/CMTATWithRuleEngineScript.s.sol.gcov.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 @@ -79,8 +79,8 @@ 8 : : import {ICMTATConstructor, CMTATStandardStandalone} from "CMTAT/deployment/CMTATStandardStandalone.sol"; 9 : : import {IERC1643CMTAT} from "CMTAT/interfaces/tokenization/draft-IERC1643CMTAT.sol"; 10 : : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; - 11 : : import {RuleEngine} from "src/deployment/RuleEngine.sol"; - 12 : : import {RuleWhitelistMock} from "src/mocks/rules/validation/RuleWhitelistMock.sol"; + 11 : : import {RuleEngine} from "../src/deployment/RuleEngine.sol"; + 12 : : import {RuleWhitelistMock} from "../src/mocks/rules/validation/RuleWhitelistMock.sol"; 13 : : 14 : : /** 15 : : * @title Example deployment of a CMTAT, a mock RuleWhitelistMock and a RuleEngine @@ -88,39 +88,45 @@ 17 : : * It is not a production deployment recipe for rule contracts. 18 : : */ 19 : : contract CMTATWithRuleEngineScript is Script { - 20 : 1 : function run() external { - 21 : : // Get env variable - 22 : 1 : uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - 23 : 1 : address admin = vm.addr(deployerPrivateKey); - 24 : 1 : address trustedForwarder = address(0x0); - 25 : 1 : vm.startBroadcast(deployerPrivateKey); - 26 : : // CMTAT - 27 : 1 : ICMTATConstructor.ERC20Attributes memory erc20Attributes = - 28 : 1 : ICMTATConstructor.ERC20Attributes("CMTA Token", "CMTAT", 0); - 29 : 1 : ICMTATConstructor.ExtraInformationAttributes memory extraInformationAttributes = - 30 : 1 : ICMTATConstructor.ExtraInformationAttributes( - 31 : : "CMTAT_ISIN", - 32 : : IERC1643CMTAT.DocumentInfo( - 33 : : "Terms", "https://cmta.ch", 0x9ff867f6592aa9d6d039e7aad6bd71f1659720cbc4dd9eae1554f6eab490098b - 34 : : ), - 35 : : "CMTAT_info" - 36 : : ); - 37 : 1 : ICMTATConstructor.Engine memory engines = ICMTATConstructor.Engine(IRuleEngine(address(0))); - 38 : 1 : CMTATStandardStandalone cmtatContract = - 39 : 1 : new CMTATStandardStandalone(trustedForwarder, admin, erc20Attributes, extraInformationAttributes, engines); - 40 : 1 : console.log("CMTAT cmtatContract : ", address(cmtatContract)); - 41 : : // whitelist - 42 : 1 : RuleWhitelistMock ruleWhitelist = new RuleWhitelistMock(admin, trustedForwarder); - 43 : 1 : console.log("whitelist: ", address(ruleWhitelist)); - 44 : : // ruleEngine - 45 : 1 : RuleEngine ruleEngine = new RuleEngine(admin, trustedForwarder, address(cmtatContract)); - 46 : 1 : console.log("RuleEngine : ", address(ruleEngine)); - 47 : 1 : ruleEngine.addRule(ruleWhitelist); - 48 : 1 : cmtatContract.setRuleEngine(ruleEngine); - 49 : : - 50 : 1 : vm.stopBroadcast(); - 51 : : } - 52 : : } + 20 : : /** + 21 : : * @notice Deploys a CMTAT token, the demo whitelist rule and a RuleEngine, and wires them + 22 : : * together. + 23 : : * @dev Reads the deployer key from `PRIVATE_KEY`; the deployer becomes the token and engine + 24 : : * admin. + 25 : : */ + 26 : 1 : function run() external { + 27 : : // Get env variable + 28 : 1 : uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + 29 : 1 : address admin = vm.addr(deployerPrivateKey); + 30 : 1 : address trustedForwarder = address(0x0); + 31 : 1 : vm.startBroadcast(deployerPrivateKey); + 32 : : // CMTAT + 33 : 1 : ICMTATConstructor.ERC20Attributes memory erc20Attributes = + 34 : 1 : ICMTATConstructor.ERC20Attributes("CMTA Token", "CMTAT", 0); + 35 : 1 : ICMTATConstructor.ExtraInformationAttributes memory extraInformationAttributes = + 36 : 1 : ICMTATConstructor.ExtraInformationAttributes( + 37 : : "CMTAT_ISIN", + 38 : : IERC1643CMTAT.DocumentInfo( + 39 : : "Terms", "https://cmta.ch", 0x9ff867f6592aa9d6d039e7aad6bd71f1659720cbc4dd9eae1554f6eab490098b + 40 : : ), + 41 : : "CMTAT_info" + 42 : : ); + 43 : 1 : ICMTATConstructor.Engine memory engines = ICMTATConstructor.Engine(IRuleEngine(address(0))); + 44 : 1 : CMTATStandardStandalone cmtatContract = + 45 : 1 : new CMTATStandardStandalone(trustedForwarder, admin, erc20Attributes, extraInformationAttributes, engines); + 46 : 1 : console.log("CMTAT cmtatContract : ", address(cmtatContract)); + 47 : : // whitelist + 48 : 1 : RuleWhitelistMock ruleWhitelist = new RuleWhitelistMock(admin, trustedForwarder); + 49 : 1 : console.log("whitelist: ", address(ruleWhitelist)); + 50 : : // ruleEngine + 51 : 1 : RuleEngine ruleEngine = new RuleEngine(admin, trustedForwarder, address(cmtatContract)); + 52 : 1 : console.log("RuleEngine : ", address(ruleEngine)); + 53 : 1 : ruleEngine.addRule(ruleWhitelist); + 54 : 1 : cmtatContract.setRuleEngine(ruleEngine); + 55 : : + 56 : 1 : vm.stopBroadcast(); + 57 : : } + 58 : : } diff --git a/doc/coverage/coverage/script/RuleEngineScript.s.sol.func-sort-c.html b/doc/coverage/coverage/script/RuleEngineScript.s.sol.func-sort-c.html index 4717471..42007a0 100644 --- a/doc/coverage/coverage/script/RuleEngineScript.s.sol.func-sort-c.html +++ b/doc/coverage/coverage/script/RuleEngineScript.s.sol.func-sort-c.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 @@ -69,7 +69,7 @@ Hit count Sort by hit count - RuleEngineScript.run + RuleEngineScript.run 1 diff --git a/doc/coverage/coverage/script/RuleEngineScript.s.sol.func.html b/doc/coverage/coverage/script/RuleEngineScript.s.sol.func.html index b18ea40..5abf495 100644 --- a/doc/coverage/coverage/script/RuleEngineScript.s.sol.func.html +++ b/doc/coverage/coverage/script/RuleEngineScript.s.sol.func.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 @@ -69,7 +69,7 @@ Hit count Sort by hit count - RuleEngineScript.run + RuleEngineScript.run 1 diff --git a/doc/coverage/coverage/script/RuleEngineScript.s.sol.gcov.html b/doc/coverage/coverage/script/RuleEngineScript.s.sol.gcov.html index 9905e9d..d5bb9ae 100644 --- a/doc/coverage/coverage/script/RuleEngineScript.s.sol.gcov.html +++ b/doc/coverage/coverage/script/RuleEngineScript.s.sol.gcov.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 @@ -76,8 +76,8 @@ 5 : : pragma solidity ^0.8.20; 6 : : 7 : : import {Script, console} from "forge-std/Script.sol"; - 8 : : import {RuleEngine} from "src/deployment/RuleEngine.sol"; - 9 : : import {RuleWhitelistMock} from "src/mocks/rules/validation/RuleWhitelistMock.sol"; + 8 : : import {RuleEngine} from "../src/deployment/RuleEngine.sol"; + 9 : : import {RuleWhitelistMock} from "../src/mocks/rules/validation/RuleWhitelistMock.sol"; 10 : : import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; 11 : : import { 12 : : ValidationModuleRuleEngine @@ -92,7 +92,7 @@ 21 : : * on that token, otherwise {setRuleEngine} reverts. 22 : : * 23 : : * The token is bound to the engine through the constructor: without it, every transfer, mint and burn - 24 : : * reverts with `RuleEngine_ERC3643Compliance_UnauthorizedCaller`, because the compliance callbacks are + 24 : : * reverts with `TokenBinding_UnauthorizedCaller`, because the compliance callbacks are 25 : : * guarded by `onlyBoundToken`. 26 : : * 27 : : * The deployer and the zero address are added to the whitelist so the resulting deployment is usable @@ -100,31 +100,36 @@ 29 : : * participant. Replace this with the real address list for anything beyond a demo. 30 : : */ 31 : : contract RuleEngineScript is Script { - 32 : 1 : function run() external { - 33 : : // Get env variable - 34 : 1 : uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - 35 : 1 : address admin = vm.addr(deployerPrivateKey); - 36 : 1 : address cmtatAddress = vm.envAddress("CMTAT_ADDRESS"); - 37 : 1 : vm.startBroadcast(deployerPrivateKey); - 38 : : //whitelist - 39 : 1 : RuleWhitelistMock ruleWhitelist = new RuleWhitelistMock(admin, address(0)); - 40 : 1 : console.log("whitelist: ", address(ruleWhitelist)); - 41 : : // Seed the list so the demo deployment can actually transfer, mint and burn. - 42 : 1 : address[] memory listed = new address[](2); - 43 : 1 : listed[0] = admin; - 44 : 1 : listed[1] = address(0); - 45 : 1 : ruleWhitelist.addAddressesToTheList(listed); - 46 : : // ruleEngine, bound to the CMTAT token - 47 : 1 : RuleEngine ruleEngine = new RuleEngine(admin, address(0), cmtatAddress); - 48 : 1 : console.log("RuleEngine: ", address(ruleEngine)); - 49 : 1 : ruleEngine.addRule(ruleWhitelist); - 50 : : // Configure the new ruleEngine for CMTAT. - 51 : : // A typed call is used deliberately: a low-level `.call` would return success even when - 52 : : // `cmtatAddress` holds no code, silently producing an unconfigured deployment. - 53 : 1 : ValidationModuleRuleEngine(cmtatAddress).setRuleEngine(IRuleEngine(address(ruleEngine))); - 54 : 1 : vm.stopBroadcast(); - 55 : : } - 56 : : } + 32 : : /** + 33 : : * @notice Deploys the demo whitelist rule and a RuleEngine bound to `CMTAT_ADDRESS`, then sets + 34 : : * the engine on that token. + 35 : : * @dev Reads the deployer key from `PRIVATE_KEY` and the token address from `CMTAT_ADDRESS`. + 36 : : */ + 37 : 1 : function run() external { + 38 : : // Get env variable + 39 : 1 : uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + 40 : 1 : address admin = vm.addr(deployerPrivateKey); + 41 : 1 : address cmtatAddress = vm.envAddress("CMTAT_ADDRESS"); + 42 : 1 : vm.startBroadcast(deployerPrivateKey); + 43 : : //whitelist + 44 : 1 : RuleWhitelistMock ruleWhitelist = new RuleWhitelistMock(admin, address(0)); + 45 : 1 : console.log("whitelist: ", address(ruleWhitelist)); + 46 : : // Seed the list so the demo deployment can actually transfer, mint and burn. + 47 : 1 : address[] memory listed = new address[](2); + 48 : 1 : listed[0] = admin; + 49 : 1 : listed[1] = address(0); + 50 : 1 : ruleWhitelist.addAddressesToTheList(listed); + 51 : : // ruleEngine, bound to the CMTAT token + 52 : 1 : RuleEngine ruleEngine = new RuleEngine(admin, address(0), cmtatAddress); + 53 : 1 : console.log("RuleEngine: ", address(ruleEngine)); + 54 : 1 : ruleEngine.addRule(ruleWhitelist); + 55 : : // Configure the new ruleEngine for CMTAT. + 56 : : // A typed call is used deliberately: a low-level `.call` would return success even when + 57 : : // `cmtatAddress` holds no code, silently producing an unconfigured deployment. + 58 : 1 : ValidationModuleRuleEngine(cmtatAddress).setRuleEngine(IRuleEngine(address(ruleEngine))); + 59 : 1 : vm.stopBroadcast(); + 60 : : } + 61 : : } diff --git a/doc/coverage/coverage/script/index-sort-b.html b/doc/coverage/coverage/script/index-sort-b.html index 4eb2998..13436a6 100644 --- a/doc/coverage/coverage/script/index-sort-b.html +++ b/doc/coverage/coverage/script/index-sort-b.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 2 @@ -82,24 +82,24 @@ Branches Sort by branch coverage - RuleEngineScript.s.sol + CMTATWithRuleEngineScript.s.sol
100.0%
100.0 % - 16 / 16 + 20 / 20 100.0 % 1 / 1 - 0 / 0 - CMTATWithRuleEngineScript.s.sol + RuleEngineScript.s.sol
100.0%
100.0 % - 20 / 20 + 16 / 16 100.0 % 1 / 1 - diff --git a/doc/coverage/coverage/script/index-sort-f.html b/doc/coverage/coverage/script/index-sort-f.html index ba6c670..f362948 100644 --- a/doc/coverage/coverage/script/index-sort-f.html +++ b/doc/coverage/coverage/script/index-sort-f.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 2 @@ -82,24 +82,24 @@ Branches Sort by branch coverage - RuleEngineScript.s.sol + CMTATWithRuleEngineScript.s.sol
100.0%
100.0 % - 16 / 16 + 20 / 20 100.0 % 1 / 1 - 0 / 0 - CMTATWithRuleEngineScript.s.sol + RuleEngineScript.s.sol
100.0%
100.0 % - 20 / 20 + 16 / 16 100.0 % 1 / 1 - diff --git a/doc/coverage/coverage/script/index-sort-l.html b/doc/coverage/coverage/script/index-sort-l.html index 9bdb270..bed6291 100644 --- a/doc/coverage/coverage/script/index-sort-l.html +++ b/doc/coverage/coverage/script/index-sort-l.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 2 diff --git a/doc/coverage/coverage/script/index.html b/doc/coverage/coverage/script/index.html index d8e861d..6fa139a 100644 --- a/doc/coverage/coverage/script/index.html +++ b/doc/coverage/coverage/script/index.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 2 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 fe2eaff..9503bca 100644 --- a/doc/coverage/coverage/src/RuleEngineBase.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/RuleEngineBase.sol.func-sort-c.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 14 diff --git a/doc/coverage/coverage/src/RuleEngineBase.sol.func.html b/doc/coverage/coverage/src/RuleEngineBase.sol.func.html index 68b56cd..5b93a64 100644 --- a/doc/coverage/coverage/src/RuleEngineBase.sol.func.html +++ b/doc/coverage/coverage/src/RuleEngineBase.sol.func.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 14 diff --git a/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html b/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html index dc13b32..0b37a95 100644 --- a/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html +++ b/doc/coverage/coverage/src/RuleEngineBase.sol.gcov.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 14 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 b25b11e..c1b3657 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 6 diff --git a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html index e50dcd7..6952777 100644 --- a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html +++ b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.func.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 6 diff --git a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html index 95c3ed4..1de9f0e 100644 --- a/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html +++ b/doc/coverage/coverage/src/RuleEngineOwnableShared.sol.gcov.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 6 @@ -100,7 +100,7 @@ 29 : 1 : _bindToken(tokenContract); 30 : : } 31 : : // Emit the initial cap so the event log alone is enough to reconstruct maxRules. - 32 : 186 : emit SetMaxRules(_maxRules); + 32 : 186 : _setMaxRules(DEFAULT_MAX_RULES); 33 : : } 34 : : 35 : : /* ============ ERC-165 ============ */ 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 ef64590..f8da442 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 10 diff --git a/doc/coverage/coverage/src/deployment/RuleEngine.sol.func.html b/doc/coverage/coverage/src/deployment/RuleEngine.sol.func.html index 064dab1..8053edf 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 10 diff --git a/doc/coverage/coverage/src/deployment/RuleEngine.sol.gcov.html b/doc/coverage/coverage/src/deployment/RuleEngine.sol.gcov.html index 2e81e24..5bad67e 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 10 @@ -116,7 +116,7 @@ 45 : : } 46 : 183 : _grantRole(DEFAULT_ADMIN_ROLE, admin); 47 : : // Emit the initial cap so the event log alone is enough to reconstruct maxRules. - 48 : 183 : emit SetMaxRules(_maxRules); + 48 : 183 : _setMaxRules(DEFAULT_MAX_RULES); 49 : : } 50 : : 51 : : /* ============ ACCESS CONTROL ============ */ 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 59b93bf..955690b 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 7 diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.func.html index 6064cc1..5ba3ee5 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 7 diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.gcov.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable.sol.gcov.html index 5dd382d..a0742a6 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 7 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 0a7e92c..d25de90 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 @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 8 diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html index b7aa04b..4c008cb 100644 --- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html +++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.func.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 8 diff --git a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html index 495a15c..004fd7c 100644 --- a/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html +++ b/doc/coverage/coverage/src/deployment/RuleEngineOwnable2Step.sol.gcov.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 8 diff --git a/doc/coverage/coverage/src/deployment/index-sort-b.html b/doc/coverage/coverage/src/deployment/index-sort-b.html index f137e1a..34917b4 100644 --- a/doc/coverage/coverage/src/deployment/index-sort-b.html +++ b/doc/coverage/coverage/src/deployment/index-sort-b.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 25 diff --git a/doc/coverage/coverage/src/deployment/index-sort-f.html b/doc/coverage/coverage/src/deployment/index-sort-f.html index da50b8a..d90fa5e 100644 --- a/doc/coverage/coverage/src/deployment/index-sort-f.html +++ b/doc/coverage/coverage/src/deployment/index-sort-f.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 25 diff --git a/doc/coverage/coverage/src/deployment/index-sort-l.html b/doc/coverage/coverage/src/deployment/index-sort-l.html index 1742e21..cb2b2ae 100644 --- a/doc/coverage/coverage/src/deployment/index-sort-l.html +++ b/doc/coverage/coverage/src/deployment/index-sort-l.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 25 diff --git a/doc/coverage/coverage/src/deployment/index.html b/doc/coverage/coverage/src/deployment/index.html index 3d9fc50..e0c9e37 100644 --- a/doc/coverage/coverage/src/deployment/index.html +++ b/doc/coverage/coverage/src/deployment/index.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 25 diff --git a/doc/coverage/coverage/src/index-sort-b.html b/doc/coverage/coverage/src/index-sort-b.html index cec55fe..a7c6455 100644 --- a/doc/coverage/coverage/src/index-sort-b.html +++ b/doc/coverage/coverage/src/index-sort-b.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 20 diff --git a/doc/coverage/coverage/src/index-sort-f.html b/doc/coverage/coverage/src/index-sort-f.html index c40f388..00830d9 100644 --- a/doc/coverage/coverage/src/index-sort-f.html +++ b/doc/coverage/coverage/src/index-sort-f.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 20 diff --git a/doc/coverage/coverage/src/index-sort-l.html b/doc/coverage/coverage/src/index-sort-l.html index 4318656..26a45c5 100644 --- a/doc/coverage/coverage/src/index-sort-l.html +++ b/doc/coverage/coverage/src/index-sort-l.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 20 diff --git a/doc/coverage/coverage/src/index.html b/doc/coverage/coverage/src/index.html index a4aebf5..5237a13 100644 --- a/doc/coverage/coverage/src/index.html +++ b/doc/coverage/coverage/src/index.html @@ -37,7 +37,7 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 20 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 index e1fde91..2691f8e 100644 --- a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func-sort-c.html +++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func-sort-c.html @@ -31,17 +31,17 @@ lcov.info Lines: - 24 - 24 + 2 + 2 100.0 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 7 - 7 + 1 + 1 100.0 % @@ -49,9 +49,9 @@ Branches: - 5 - 5 - 100.0 % + 0 + 0 + - @@ -69,31 +69,7 @@ Hit count Sort by hit count - ERC3643ComplianceExtendedModule.getTokenBounds - 4 - - - ERC3643ComplianceExtendedModule.isTokenSelfBindingApproved - 6 - - - ERC3643ComplianceExtendedModule.unbindTokens - 9 - - - ERC3643ComplianceExtendedModule.setTokenSelfBindingApprovalBatch - 12 - - - ERC3643ComplianceExtendedModule.bindTokens - 18 - - - ERC3643ComplianceExtendedModule.setTokenSelfBindingApproval - 27 - - - ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange + ERC3643ComplianceExtendedModule._authorizeTokenBindingChange 87 diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html index 9dab461..4905467 100644 --- a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html +++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.func.html @@ -31,17 +31,17 @@ lcov.info Lines: - 24 - 24 + 2 + 2 100.0 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 7 - 7 + 1 + 1 100.0 % @@ -49,9 +49,9 @@ Branches: - 5 - 5 - 100.0 % + 0 + 0 + - @@ -69,33 +69,9 @@ Hit count Sort by hit count - ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange + ERC3643ComplianceExtendedModule._authorizeTokenBindingChange 87 - - ERC3643ComplianceExtendedModule.bindTokens - 18 - - - ERC3643ComplianceExtendedModule.getTokenBounds - 4 - - - ERC3643ComplianceExtendedModule.isTokenSelfBindingApproved - 6 - - - ERC3643ComplianceExtendedModule.setTokenSelfBindingApproval - 27 - - - ERC3643ComplianceExtendedModule.setTokenSelfBindingApprovalBatch - 12 - - - ERC3643ComplianceExtendedModule.unbindTokens - 9 -
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html index 6b680ec..f15334a 100644 --- a/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html +++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceExtendedModule.sol.gcov.html @@ -31,17 +31,17 @@ lcov.info Lines: - 24 - 24 + 2 + 2 100.0 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 7 - 7 + 1 + 1 100.0 % @@ -49,9 +49,9 @@ Branches: - 5 - 5 - 100.0 % + 0 + 0 + - @@ -73,86 +73,41 @@ 2 : : 3 : : pragma solidity ^0.8.20; 4 : : - 5 : : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - 6 : : /* ==== Interface and other library === */ - 7 : : import {IERC3643ComplianceExtended} from "../interfaces/IERC3643ComplianceExtended.sol"; - 8 : : import {ERC3643ComplianceModule} from "./ERC3643ComplianceModule.sol"; - 9 : : - 10 : : /** - 11 : : * @title ERC3643ComplianceExtendedModule - 12 : : * @notice Extends the core ERC-3643 compliance module with batch binding and token self-binding. - 13 : : */ - 14 : : abstract contract ERC3643ComplianceExtendedModule is ERC3643ComplianceModule, IERC3643ComplianceExtended { - 15 : : using EnumerableSet for EnumerableSet.AddressSet; - 16 : : - 17 : : /** - 18 : : * @notice Tracks which tokens are allowed to bind and unbind themselves. - 19 : : */ - 20 : : mapping(address token => bool approved) private _tokenSelfBindingApproval; - 21 : : - 22 : : /** - 23 : : * @inheritdoc IERC3643ComplianceExtended - 24 : : * @custom:security-note See {bindToken} for multi-tenant accounting risks. All tokens bound - 25 : : * in this batch share the same rule state. Only bind tokens that are equally trusted and - 26 : : * governed together. - 27 : : */ - 28 : 18 : function bindTokens(address[] calldata tokens) public virtual override onlyComplianceManager { - 29 : 15 : for (uint256 i = 0; i < tokens.length; ++i) { - 30 : 24 : _bindToken(tokens[i]); - 31 : : } - 32 : : } - 33 : : - 34 : : /// @inheritdoc IERC3643ComplianceExtended - 35 : 9 : function unbindTokens(address[] calldata tokens) public virtual override onlyComplianceManager { - 36 : 6 : for (uint256 i = 0; i < tokens.length; ++i) { - 37 : 12 : _unbindToken(tokens[i]); - 38 : : } - 39 : : } - 40 : : - 41 : : /// @inheritdoc IERC3643ComplianceExtended - 42 : 27 : function setTokenSelfBindingApproval(address token, bool approved) public virtual override onlyComplianceManager { - 43 [ + + ]: 24 : require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress()); - 44 : 21 : _tokenSelfBindingApproval[token] = approved; - 45 : 21 : emit TokenSelfBindingApprovalSet(token, approved); - 46 : : } - 47 : : - 48 : : /// @inheritdoc IERC3643ComplianceExtended - 49 : 12 : function setTokenSelfBindingApprovalBatch(address[] calldata tokens, bool approved) - 50 : : public - 51 : : virtual - 52 : : override - 53 : : onlyComplianceManager - 54 : : { - 55 : 9 : for (uint256 i = 0; i < tokens.length; ++i) { - 56 : 18 : address token = tokens[i]; - 57 [ + + ]: 18 : require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress()); - 58 : 15 : _tokenSelfBindingApproval[token] = approved; - 59 : : } - 60 : 6 : emit TokenSelfBindingApprovalBatchSet(tokens, approved); - 61 : : } - 62 : : - 63 : : /// @inheritdoc IERC3643ComplianceExtended - 64 : 6 : function isTokenSelfBindingApproved(address token) public view virtual override returns (bool) { - 65 : 6 : return _tokenSelfBindingApproval[token]; - 66 : : } - 67 : : - 68 : : /// @inheritdoc IERC3643ComplianceExtended - 69 : 4 : function getTokenBounds() public view virtual override returns (address[] memory) { - 70 : 4 : return _boundTokens.values(); - 71 : : } - 72 : : - 73 : : /** - 74 : : * @dev Authorizes bind/unbind operations. - 75 : : * Allows compliance manager, or approved token self-calls for T-REX compatibility. - 76 : : * @param token The token being bound or unbound. - 77 : : */ - 78 : 87 : function _authorizeComplianceBindingChange(address token) internal virtual override { - 79 [ + ]: 87 : if (_msgSender() == token && _tokenSelfBindingApproval[token]) { - 80 : 87 : return; - 81 : : } - 82 : 61 : _onlyComplianceManager(); - 83 : : } - 84 : : } + 5 : : /* ==== Modules === */ + 6 : : import {ERC3643ComplianceModule} from "./ERC3643ComplianceModule.sol"; + 7 : : import {TokenBindingExtendedModule} from "./TokenBindingExtendedModule.sol"; + 8 : : import {TokenBindingModule} from "./TokenBindingModule.sol"; + 9 : : /* ==== Interface and other library === */ + 10 : : import {IERC3643ComplianceExtended} from "../interfaces/IERC3643ComplianceExtended.sol"; + 11 : : + 12 : : /** + 13 : : * @title ERC3643ComplianceExtendedModule + 14 : : * @notice ERC-3643 flavour of the extended token binding registry: it combines the ERC-3643 + 15 : : * adapter {ERC3643ComplianceModule} with the batch binding and token self-binding provided by + 16 : : * {TokenBindingExtendedModule}. + 17 : : * @dev No logic of its own. Batch binding, self-binding approval and {getTokenBounds} are + 18 : : * standard-agnostic and therefore implemented in {TokenBindingExtendedModule}; this contract only + 19 : : * declares that the ERC-3643 deployment exposes them through {IERC3643ComplianceExtended}. + 20 : : */ + 21 : : abstract contract ERC3643ComplianceExtendedModule is + 22 : : TokenBindingExtendedModule, + 23 : : ERC3643ComplianceModule, + 24 : : IERC3643ComplianceExtended + 25 : : { + 26 : : /** + 27 : : * @dev Resolves the two inherited definitions of the binding authorization hook, reached + 28 : : * through {TokenBindingExtendedModule} and through {ERC3643ComplianceModule}. The extended + 29 : : * behaviour wins: the compliance manager, or an approved token binding itself. + 30 : : * @param token The token being bound or unbound. + 31 : : */ + 32 : 87 : function _authorizeTokenBindingChange(address token) + 33 : : internal + 34 : : virtual + 35 : : override(TokenBindingModule, TokenBindingExtendedModule) + 36 : : { + 37 : 87 : TokenBindingExtendedModule._authorizeTokenBindingChange(token); + 38 : : } + 39 : : } 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 a47f253..175a9b1 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,26 +31,26 @@ lcov.info Lines: - 26 - 28 - 92.9 % + 6 + 7 + 85.7 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 9 - 11 - 81.8 % + 2 + 3 + 66.7 % Branches: - 9 - 9 + 2 + 2 100.0 % @@ -69,48 +69,16 @@ Hit count Sort by hit count - ERC3643ComplianceModule._authorizeComplianceBindingChange + ERC3643ComplianceModule._onlyComplianceManager 0 - ERC3643ComplianceModule._onlyComplianceManager - 0 - - - ERC3643ComplianceModule.getTokenBound + ERC3643ComplianceModule.getTokenBound 7 - ERC3643ComplianceModule.onlyBoundToken - 7 - - - ERC3643ComplianceModule.onlyComplianceManager - 9 - - - ERC3643ComplianceModule.unbindToken - 21 - - - ERC3643ComplianceModule._unbindToken - 26 - - - ERC3643ComplianceModule._checkBoundToken - 41 - - - ERC3643ComplianceModule.isTokenBound - 41 - - - ERC3643ComplianceModule.bindToken - 66 - - - ERC3643ComplianceModule._bindToken - 115 + ERC3643ComplianceModule._onlyTokenBindingManager + 127
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html index dd3cb6a..24397e5 100644 --- a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html +++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.func.html @@ -31,26 +31,26 @@ lcov.info Lines: - 26 - 28 - 92.9 % + 6 + 7 + 85.7 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 9 - 11 - 81.8 % + 2 + 3 + 66.7 % Branches: - 9 - 9 + 2 + 2 100.0 % @@ -69,49 +69,17 @@ Hit count Sort by hit count - ERC3643ComplianceModule._authorizeComplianceBindingChange + ERC3643ComplianceModule._onlyComplianceManager 0 - ERC3643ComplianceModule._bindToken - 115 + ERC3643ComplianceModule._onlyTokenBindingManager + 127 - ERC3643ComplianceModule._checkBoundToken - 41 - - - ERC3643ComplianceModule._onlyComplianceManager - 0 - - - ERC3643ComplianceModule._unbindToken - 26 - - - ERC3643ComplianceModule.bindToken - 66 - - - ERC3643ComplianceModule.getTokenBound + ERC3643ComplianceModule.getTokenBound 7 - - ERC3643ComplianceModule.isTokenBound - 41 - - - ERC3643ComplianceModule.onlyBoundToken - 7 - - - ERC3643ComplianceModule.onlyComplianceManager - 9 - - - ERC3643ComplianceModule.unbindToken - 21 -
diff --git a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html index e60e9f2..6ee9f0f 100644 --- a/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html +++ b/doc/coverage/coverage/src/modules/ERC3643ComplianceModule.sol.gcov.html @@ -31,26 +31,26 @@ lcov.info Lines: - 26 - 28 - 92.9 % + 6 + 7 + 85.7 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 9 - 11 - 81.8 % + 2 + 3 + 66.7 % Branches: - 9 - 9 + 2 + 2 100.0 % @@ -75,131 +75,70 @@ 4 : : 5 : : /* ==== OpenZeppelin === */ 6 : : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - 7 : : import {Context} from "@openzeppelin/contracts/utils/Context.sol"; - 8 : : /* ==== Interface and other library === */ - 9 : : import {IERC3643Compliance} from "../interfaces/IERC3643Compliance.sol"; - 10 : : import {ERC3643ComplianceModuleInvariantStorage} from "./library/ERC3643ComplianceModuleInvariantStorage.sol"; + 7 : : /* ==== Modules === */ + 8 : : import {TokenBindingModule} from "./TokenBindingModule.sol"; + 9 : : /* ==== Interface and other library === */ + 10 : : import {IERC3643Compliance} from "../interfaces/IERC3643Compliance.sol"; 11 : : 12 : : /** 13 : : * @title ERC3643ComplianceModule - 14 : : * @notice Core ERC-3643 compliance module: tracks the tokens bound to this engine. - 15 : : */ - 16 : : abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance, ERC3643ComplianceModuleInvariantStorage { - 17 : : /* ==== Type declaration === */ - 18 : : using EnumerableSet for EnumerableSet.AddressSet; - 19 : : /* ==== State Variables === */ - 20 : : // Token binding tracking - 21 : : /** - 22 : : * @notice Set of tokens allowed to call the compliance callbacks. - 23 : : */ - 24 : : EnumerableSet.AddressSet internal _boundTokens; - 25 : : - 26 : : /* ==== Modifier === */ - 27 : 7 : modifier onlyBoundToken() { - 28 : 7 : _checkBoundToken(); - 29 : : _; - 30 : : } - 31 : : - 32 : 9 : modifier onlyComplianceManager() { - 33 : 9 : _onlyComplianceManager(); - 34 : : _; - 35 : : } - 36 : : - 37 : : /*////////////////////////////////////////////////////////////// - 38 : : PUBLIC/public FUNCTIONS - 39 : : //////////////////////////////////////////////////////////////*/ - 40 : : - 41 : : /* ============ State functions ============ */ - 42 : : /** - 43 : : * @inheritdoc IERC3643Compliance - 44 : : * @dev Operator warning: "multi-tenant" means one RuleEngine is shared by - 45 : : * multiple token contracts. In that setup, bind only tokens that are equally - 46 : : * trusted and governed together. - 47 : : * @custom:security-note Operation rules (stateful rules such as `RuleConditionalTransferLightMock` - 48 : : * or `RuleMintAllowanceMock`) maintain per-address accounting that is shared across all bound tokens. - 49 : : * Binding tokens from different issuers to the same engine will silently cross-contaminate - 50 : : * their accounting. Only bind tokens that are equally trusted and governed together. - 51 : : */ - 52 : 66 : function bindToken(address token) public virtual override { - 53 : 66 : _authorizeComplianceBindingChange(token); - 54 : 58 : _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 : : */ - 63 : 21 : function unbindToken(address token) public virtual override { - 64 : 21 : _authorizeComplianceBindingChange(token); - 65 : 14 : _unbindToken(token); - 66 : : } - 67 : : - 68 : : /// @inheritdoc IERC3643Compliance - 69 : 41 : function isTokenBound(address token) public view virtual override returns (bool) { - 70 : 41 : return _boundTokens.contains(token); - 71 : : } - 72 : : - 73 : : /// @inheritdoc IERC3643Compliance - 74 : 7 : function getTokenBound() public view virtual override returns (address) { - 75 [ + + ]: 7 : if (_boundTokens.length() > 0) { - 76 : : // Note that there are no guarantees on the ordering of values inside the array, - 77 : : // and it may change when more values are added or removed. - 78 : 5 : return _boundTokens.pos(0); - 79 : : } else { - 80 : 2 : return address(0); - 81 : : } - 82 : : } - 83 : : - 84 : : /*////////////////////////////////////////////////////////////// - 85 : : INTERNAL/PRIVATE FUNCTIONS - 86 : : //////////////////////////////////////////////////////////////*/ - 87 : : - 88 : : /** - 89 : : * @dev Removes a token from the bound set. - 90 : : * @param token The token to unbind; reverts when it is not currently bound. - 91 : : */ - 92 : 26 : function _unbindToken(address token) internal virtual { - 93 : : // remove() returns false when the token was not bound, so a separate - 94 : : // contains() lookup is unnecessary. - 95 [ + + ]: 26 : require(_boundTokens.remove(token), RuleEngine_ERC3643Compliance_TokenNotBound()); - 96 : : - 97 : 21 : emit TokenUnbound(token); - 98 : : } - 99 : : - 100 : : /** - 101 : : * @dev Adds a token to the bound set. - 102 : : * @param token The token to bind; reverts on the zero address or when already bound. - 103 : : */ - 104 : 115 : function _bindToken(address token) internal virtual { - 105 [ + + ]: 115 : require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress()); - 106 : : // add() returns false when the token is already bound, so a separate - 107 : : // contains() lookup is unnecessary. - 108 [ + + ]: 110 : require(_boundTokens.add(token), RuleEngine_ERC3643Compliance_TokenAlreadyBound()); - 109 : 105 : emit TokenBound(token); - 110 : : } - 111 : : - 112 : : /** - 113 : : * @dev Authorization hook for bind/unbind, implemented by the deployable contracts. - 114 : : * @param token The token being bound or unbound. - 115 : : */ - 116 : 0 : function _authorizeComplianceBindingChange(address token) internal virtual; - 117 : : - 118 : : /** - 119 : : * @dev Access control hook guarding compliance management operations. - 120 : : */ - 121 : 0 : function _onlyComplianceManager() internal virtual; - 122 : : - 123 : : /** - 124 : : * @dev Reverts when the caller is not a bound token. - 125 : : */ - 126 : 41 : function _checkBoundToken() internal view virtual { - 127 [ + ]: 41 : if (!_boundTokens.contains(_msgSender())) { - 128 : 10 : revert RuleEngine_ERC3643Compliance_UnauthorizedCaller(); - 129 : : } - 130 : : } - 131 : : } + 14 : : * @notice ERC-3643 adapter over the standard-agnostic {TokenBindingModule}: it adds the + 15 : : * ERC-3643 specific view {getTokenBound} and names the binding manager in compliance terms. + 16 : : * @dev The binding registry itself (storage, `bindToken` / `unbindToken` / `isTokenBound`, the + 17 : : * `onlyBoundToken` modifier) lives in {TokenBindingModule} and can be reused outside any + 18 : : * compliance context. Everything ERC-3643 specific is here: + 19 : : * - {getTokenBound}, the single-token view required by the ERC-3643 compliance interface; + 20 : : * - {_onlyComplianceManager}, the access control hook the deployable contracts implement, wired + 21 : : * to the generic {_onlyTokenBindingManager} hook. + 22 : : * + 23 : : * The ERC-3643 compliance callbacks themselves (`transferred`, `created`, `destroyed`) are + 24 : : * implemented by `RuleEngineBase`, since they depend on the rules rather than on the binding. + 25 : : * @custom:security-note Operation rules (stateful rules such as `RuleConditionalTransferLightMock` + 26 : : * or `RuleMintAllowanceMock`) maintain per-address accounting that is shared across all bound + 27 : : * tokens, and the ERC-3643 callbacks do not carry the calling token address to the rules. + 28 : : * Binding tokens from different issuers to the same engine will silently cross-contaminate their + 29 : : * accounting. Only bind tokens that are equally trusted and governed together. + 30 : : */ + 31 : : abstract contract ERC3643ComplianceModule is TokenBindingModule, IERC3643Compliance { + 32 : : /* ==== Type declaration === */ + 33 : : using EnumerableSet for EnumerableSet.AddressSet; + 34 : : + 35 : : /*////////////////////////////////////////////////////////////// + 36 : : PUBLIC/public FUNCTIONS + 37 : : //////////////////////////////////////////////////////////////*/ + 38 : : + 39 : : /* ============ View functions ============ */ + 40 : : /// @inheritdoc IERC3643Compliance + 41 : 7 : function getTokenBound() public view virtual override returns (address) { + 42 [ + + ]: 7 : if (_boundTokens.length() > 0) { + 43 : : // Note that there are no guarantees on the ordering of values inside the array, + 44 : : // and it may change when more values are added or removed. + 45 : 5 : return _boundTokens.pos(0); + 46 : : } else { + 47 : 2 : return address(0); + 48 : : } + 49 : : } + 50 : : + 51 : : /*////////////////////////////////////////////////////////////// + 52 : : INTERNAL/PRIVATE FUNCTIONS + 53 : : //////////////////////////////////////////////////////////////*/ + 54 : : + 55 : : /** + 56 : : * @dev In an ERC-3643 deployment, the account managing the token bindings is the compliance + 57 : : * manager, so the generic binding manager hook delegates to {_onlyComplianceManager}. + 58 : : */ + 59 : 127 : function _onlyTokenBindingManager() internal virtual override { + 60 : 127 : _onlyComplianceManager(); + 61 : : } + 62 : : + 63 : : /** + 64 : : * @dev Access control hook guarding compliance management operations, implemented by the + 65 : : * deployable contracts. Binding management is gated by this hook through + 66 : : * {_onlyTokenBindingManager}; the generic `onlyTokenBindingManager` modifier of + 67 : : * {TokenBindingModule} is therefore the compliance manager check in an ERC-3643 deployment. + 68 : : */ + 69 : 0 : function _onlyComplianceManager() internal virtual; + 70 : : } 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 1c5153f..f4db989 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 @@ -31,27 +31,27 @@ lcov.info Lines: - 62 64 - 96.9 % + 66 + 97.0 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 17 19 - 89.5 % + 21 + 90.5 % Branches: - 14 - 17 - 82.4 % + 13 + 15 + 86.7 % @@ -69,23 +69,23 @@ Hit count Sort by hit count - RulesManagementModule._onlyRulesLimitManager + RulesManagementModule._onlyRulesLimitManager 0 - RulesManagementModule._onlyRulesManager + RulesManagementModule._onlyRulesManager 0 - RulesManagementModule.rule + RulesManagementModule.rule 5 - RulesManagementModule._transferred.1 + RulesManagementModule._transferred.1 6 - RulesManagementModule.maxRules + RulesManagementModule.maxRules 8 @@ -93,23 +93,23 @@ 9 - RulesManagementModule.setMaxRules + RulesManagementModule.setMaxRules 9 - RulesManagementModule._removeRule + RulesManagementModule._removeRule 13 - RulesManagementModule.rules + RulesManagementModule.rules 15 - RulesManagementModule.removeRule + RulesManagementModule.removeRule 18 - RulesManagementModule.clearRules + RulesManagementModule.clearRules 19 @@ -117,7 +117,7 @@ 19 - RulesManagementModule._transferred.0 + RulesManagementModule._transferred.0 25 @@ -125,25 +125,33 @@ 53 - RulesManagementModule._clearRules + RulesManagementModule._clearRules 54 - RulesManagementModule.containsRule + RulesManagementModule.containsRule 82 - RulesManagementModule.rulesCount + RulesManagementModule.rulesCount 191 - RulesManagementModule.addRule + RulesManagementModule.addRule 203 - RulesManagementModule._checkRule + RulesManagementModule._addRule 277 + + RulesManagementModule._checkRule + 277 + + + RulesManagementModule._setMaxRules + 374 +
diff --git a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html index 6ffd6ea..b620865 100644 --- a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html +++ b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.func.html @@ -31,27 +31,27 @@ lcov.info Lines: - 62 64 - 96.9 % + 66 + 97.0 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 17 19 - 89.5 % + 21 + 90.5 % Branches: - 14 - 17 - 82.4 % + 13 + 15 + 86.7 % @@ -69,47 +69,55 @@ Hit count Sort by hit count - RulesManagementModule._checkRule + RulesManagementModule._addRule 277 - RulesManagementModule._clearRules + RulesManagementModule._checkRule + 277 + + + RulesManagementModule._clearRules 54 - RulesManagementModule._onlyRulesLimitManager + RulesManagementModule._onlyRulesLimitManager 0 - RulesManagementModule._onlyRulesManager + RulesManagementModule._onlyRulesManager 0 - RulesManagementModule._removeRule + RulesManagementModule._removeRule 13 - RulesManagementModule._transferred.0 + RulesManagementModule._setMaxRules + 374 + + + RulesManagementModule._transferred.0 25 - RulesManagementModule._transferred.1 + RulesManagementModule._transferred.1 6 - RulesManagementModule.addRule + RulesManagementModule.addRule 203 - RulesManagementModule.clearRules + RulesManagementModule.clearRules 19 - RulesManagementModule.containsRule + RulesManagementModule.containsRule 82 - RulesManagementModule.maxRules + RulesManagementModule.maxRules 8 @@ -121,23 +129,23 @@ 19 - RulesManagementModule.removeRule + RulesManagementModule.removeRule 18 - RulesManagementModule.rule + RulesManagementModule.rule 5 - RulesManagementModule.rules + RulesManagementModule.rules 15 - RulesManagementModule.rulesCount + RulesManagementModule.rulesCount 191 - RulesManagementModule.setMaxRules + RulesManagementModule.setMaxRules 9 diff --git a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html index 61fda73..80f799f 100644 --- a/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html +++ b/doc/coverage/coverage/src/modules/RulesManagementModule.sol.gcov.html @@ -31,27 +31,27 @@ lcov.info Lines: - 62 64 - 96.9 % + 66 + 97.0 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 17 19 - 89.5 % + 21 + 90.5 % Branches: - 14 - 17 - 82.4 % + 13 + 15 + 86.7 % @@ -131,182 +131,203 @@ 60 : 38 : _clearRules(); 61 : : } 62 : 44 : for (uint256 i = 0; i < rules_.length; ++i) { - 63 : 81 : _checkRule(address(rules_[i])); - 64 : : // Should never revert because we check the presence of the rule before - 65 [ # + ]: 76 : require(_rules.add(address(rules_[i])), RuleEngine_RulesManagementModule_OperationNotSuccessful()); - 66 : 76 : emit AddRule(rules_[i]); - 67 : : } - 68 : : } - 69 : : - 70 : : /** - 71 : : * @inheritdoc IRulesManagementModule - 72 : : */ - 73 : 19 : function clearRules() public virtual override(IRulesManagementModule) onlyRulesManager { - 74 : 16 : _clearRules(); - 75 : : } - 76 : : - 77 : : /** - 78 : : * @inheritdoc IRulesManagementModule - 79 : : * @dev Reverts when the configured maximum number of rules is already reached. - 80 : : * Security convention: do not grant {RULES_MANAGEMENT_ROLE} to rule contracts. - 81 : : */ - 82 : 203 : function addRule(IRule rule_) public virtual override(IRulesManagementModule) onlyRulesManager { - 83 [ + ]: 198 : if (_rules.length() >= _maxRules) { - 84 : 2 : revert RuleEngine_RulesManagementModule_MaxRulesExceeded(_maxRules); - 85 : : } - 86 : 196 : _checkRule(address(rule_)); - 87 [ # + ]: 186 : require(_rules.add(address(rule_)), RuleEngine_RulesManagementModule_OperationNotSuccessful()); - 88 : 186 : emit AddRule(rule_); - 89 : : } - 90 : : - 91 : : /** - 92 : : * @inheritdoc IRulesManagementModule - 93 : : */ - 94 : 9 : function setMaxRules(uint256 maxRules_) public virtual override(IRulesManagementModule) onlyRulesLimitManager { - 95 [ + ]: 5 : if (maxRules_ == 0) { - 96 : 1 : revert RuleEngine_RulesManagementModule_MaxRulesZeroNotAllowed(); - 97 : : } - 98 : 4 : _maxRules = maxRules_; - 99 : 4 : emit SetMaxRules(maxRules_); - 100 : : } - 101 : : + 63 : 81 : _addRule(rules_[i]); + 64 : : } + 65 : : } + 66 : : + 67 : : /** + 68 : : * @inheritdoc IRulesManagementModule + 69 : : */ + 70 : 19 : function clearRules() public virtual override(IRulesManagementModule) onlyRulesManager { + 71 : 16 : _clearRules(); + 72 : : } + 73 : : + 74 : : /** + 75 : : * @inheritdoc IRulesManagementModule + 76 : : * @dev Reverts when the configured maximum number of rules is already reached. + 77 : : * Security convention: do not grant {RULES_MANAGEMENT_ROLE} to rule contracts. + 78 : : */ + 79 : 203 : function addRule(IRule rule_) public virtual override(IRulesManagementModule) onlyRulesManager { + 80 [ + ]: 198 : if (_rules.length() >= _maxRules) { + 81 : 2 : revert RuleEngine_RulesManagementModule_MaxRulesExceeded(_maxRules); + 82 : : } + 83 : 196 : _addRule(rule_); + 84 : : } + 85 : : + 86 : : /** + 87 : : * @inheritdoc IRulesManagementModule + 88 : : */ + 89 : 9 : function setMaxRules(uint256 maxRules_) public virtual override(IRulesManagementModule) onlyRulesLimitManager { + 90 : 5 : _setMaxRules(maxRules_); + 91 : : } + 92 : : + 93 : : /** + 94 : : * @inheritdoc IRulesManagementModule + 95 : : */ + 96 : 18 : function removeRule(IRule rule_) public virtual override(IRulesManagementModule) onlyRulesManager { + 97 [ + + ]: 16 : require(_rules.contains(address(rule_)), RuleEngine_RulesManagementModule_RuleDoNotMatch()); + 98 : 13 : _removeRule(rule_); + 99 : : } + 100 : : + 101 : : /* ============ View functions ============ */ 102 : : /** 103 : : * @inheritdoc IRulesManagementModule 104 : : */ - 105 : 18 : function removeRule(IRule rule_) public virtual override(IRulesManagementModule) onlyRulesManager { - 106 [ + + ]: 16 : require(_rules.contains(address(rule_)), RuleEngine_RulesManagementModule_RuleDoNotMatch()); - 107 : 13 : _removeRule(rule_); - 108 : : } - 109 : : - 110 : : /* ============ View functions ============ */ - 111 : : /** - 112 : : * @inheritdoc IRulesManagementModule - 113 : : */ - 114 : 8 : function maxRules() public view virtual override(IRulesManagementModule) returns (uint256) { - 115 : 8 : return _maxRules; - 116 : : } - 117 : : - 118 : : /** - 119 : : * @inheritdoc IRulesManagementModule - 120 : : */ - 121 : 191 : function rulesCount() public view virtual override(IRulesManagementModule) returns (uint256) { - 122 : 323 : return _rules.length(); - 123 : : } - 124 : : - 125 : : /** - 126 : : * @inheritdoc IRulesManagementModule - 127 : : */ - 128 : 82 : function containsRule(IRule rule_) public view virtual override(IRulesManagementModule) returns (bool) { - 129 : 92 : return _rules.contains(address(rule_)); - 130 : : } - 131 : : - 132 : : /** - 133 : : * @inheritdoc IRulesManagementModule - 134 : : */ - 135 : 5 : function rule(uint256 ruleId) public view virtual override(IRulesManagementModule) returns (address) { - 136 [ + + ]: 152 : if (ruleId < _rules.length()) { - 137 : : // Note that there are no guarantees on the ordering of values inside the array, - 138 : : // and it may change when more values are added or removed. - 139 : 150 : return _rules.pos(ruleId); - 140 : : } else { - 141 : 2 : return address(0); - 142 : : } - 143 : : } - 144 : : - 145 : : /** - 146 : : * @inheritdoc IRulesManagementModule - 147 : : */ - 148 : 15 : function rules() public view virtual override(IRulesManagementModule) returns (address[] memory) { - 149 : 15 : return _rules.values(); - 150 : : } - 151 : : - 152 : : /*////////////////////////////////////////////////////////////// - 153 : : INTERNAL/PRIVATE FUNCTIONS - 154 : : //////////////////////////////////////////////////////////////*/ + 105 : 8 : function maxRules() public view virtual override(IRulesManagementModule) returns (uint256) { + 106 : 8 : return _maxRules; + 107 : : } + 108 : : + 109 : : /** + 110 : : * @inheritdoc IRulesManagementModule + 111 : : */ + 112 : 191 : function rulesCount() public view virtual override(IRulesManagementModule) returns (uint256) { + 113 : 323 : return _rules.length(); + 114 : : } + 115 : : + 116 : : /** + 117 : : * @inheritdoc IRulesManagementModule + 118 : : */ + 119 : 82 : function containsRule(IRule rule_) public view virtual override(IRulesManagementModule) returns (bool) { + 120 : 92 : return _rules.contains(address(rule_)); + 121 : : } + 122 : : + 123 : : /** + 124 : : * @inheritdoc IRulesManagementModule + 125 : : */ + 126 : 5 : function rule(uint256 ruleId) public view virtual override(IRulesManagementModule) returns (address) { + 127 [ + + ]: 152 : if (ruleId < _rules.length()) { + 128 : : // Note that there are no guarantees on the ordering of values inside the array, + 129 : : // and it may change when more values are added or removed. + 130 : 150 : return _rules.pos(ruleId); + 131 : : } else { + 132 : 2 : return address(0); + 133 : : } + 134 : : } + 135 : : + 136 : : /** + 137 : : * @inheritdoc IRulesManagementModule + 138 : : */ + 139 : 15 : function rules() public view virtual override(IRulesManagementModule) returns (address[] memory) { + 140 : 15 : return _rules.values(); + 141 : : } + 142 : : + 143 : : /*////////////////////////////////////////////////////////////// + 144 : : INTERNAL/PRIVATE FUNCTIONS + 145 : : //////////////////////////////////////////////////////////////*/ + 146 : : /** + 147 : : * @notice Clear all the rules of the array of rules + 148 : : * + 149 : : */ + 150 : 54 : function _clearRules() internal virtual { + 151 : 54 : emit ClearRules(); + 152 : 54 : _rules.clear(); + 153 : : } + 154 : : 155 : : /** - 156 : : * @notice Clear all the rules of the array of rules - 157 : : * - 158 : : */ - 159 : 54 : function _clearRules() internal virtual { - 160 : 54 : emit ClearRules(); - 161 : 54 : _rules.clear(); - 162 : : } - 163 : : - 164 : : /** - 165 : : * @notice Remove a rule from the array of rules - 166 : : * Revert if the rule found at the specified index does not match the rule in argument - 167 : : * @param rule_ address of the target rule - 168 : : * - 169 : : * - 170 : : */ - 171 : 13 : function _removeRule(IRule rule_) internal virtual { - 172 : : // Should never revert because we check the presence of the rule before - 173 [ # + ]: 13 : require(_rules.remove(address(rule_)), RuleEngine_RulesManagementModule_OperationNotSuccessful()); - 174 : 13 : emit RemoveRule(rule_); - 175 : : } - 176 : : - 177 : : /* ============ Transferred functions ============ */ - 178 : : - 179 : : /** - 180 : : * @notice Go through all the rule to know if a restriction exists on the transfer - 181 : : * @dev Complexity is O(number of configured rules). Large rule sets can make - 182 : : * transfers too expensive on chains with lower block gas limits. - 183 : : * Security convention: rule contracts are expected to be trusted and must not - 184 : : * hold {RULES_MANAGEMENT_ROLE}. - 185 : : * @param from the origin address - 186 : : * @param to the destination address - 187 : : * @param value to transfer - 188 : : * - 189 : : */ - 190 : 25 : function _transferred(address from, address to, uint256 value) internal virtual { - 191 : 25 : uint256 rulesLength = _rules.length(); - 192 : 25 : for (uint256 i = 0; i < rulesLength; ++i) { - 193 : 19 : IRule(_rules.pos(i)).transferred(from, to, value); - 194 : : } - 195 : : } - 196 : : - 197 : : /** - 198 : : * @notice Go through all the rule to know if a restriction exists on the transfer - 199 : : * @dev Complexity is O(number of configured rules). Large rule sets can make - 200 : : * transfers too expensive on chains with lower block gas limits. - 201 : : * Security convention: rule contracts are expected to be trusted and must not - 202 : : * hold {RULES_MANAGEMENT_ROLE}. - 203 : : * @param spender the spender address (transferFrom) - 204 : : * @param from the origin address - 205 : : * @param to the destination address - 206 : : * @param value to transfer - 207 : : * - 208 : : */ - 209 : 6 : function _transferred(address spender, address from, address to, uint256 value) internal virtual { - 210 : 6 : uint256 rulesLength = _rules.length(); - 211 : 6 : for (uint256 i = 0; i < rulesLength; ++i) { - 212 : 6 : IRule(_rules.pos(i)).transferred(spender, from, to, value); - 213 : : } - 214 : : } - 215 : : - 216 : : /** - 217 : : * @dev Access control hook guarding rule management operations. - 218 : : */ - 219 : 0 : function _onlyRulesManager() internal virtual; - 220 : : - 221 : : /** - 222 : : * @dev Access control hook guarding updates to the rule cap. - 223 : : */ - 224 : 0 : function _onlyRulesLimitManager() internal virtual; - 225 : : - 226 : : /** - 227 : : * @dev check if a rule is valid, revert otherwise - 228 : : * @param rule_ The candidate rule address to validate. + 156 : : * @notice Set the maximum number of rules and emit the corresponding event + 157 : : * @dev Single point where `_maxRules` is written, so the invariant "every change to the cap emits + 158 : : * {SetMaxRules}" holds structurally rather than by convention. Called by {setMaxRules} and by the + 159 : : * deployable contracts' constructors, which emit the initial cap so the event log alone is enough to + 160 : : * reconstruct it. + 161 : : * @param maxRules_ New maximum number of rules; must not be zero. + 162 : : */ + 163 : 374 : function _setMaxRules(uint256 maxRules_) internal virtual { + 164 [ + ]: 374 : if (maxRules_ == 0) { + 165 : 1 : revert RuleEngine_RulesManagementModule_MaxRulesZeroNotAllowed(); + 166 : : } + 167 : 373 : _maxRules = maxRules_; + 168 : 373 : emit SetMaxRules(maxRules_); + 169 : : } + 170 : : + 171 : : /** + 172 : : * @notice Validate a rule, add it to the array of rules and emit the corresponding event + 173 : : * @dev Single point where a rule is inserted, so the invariant "every rule added emits {AddRule}" holds + 174 : : * structurally. The `maxRules` cap is deliberately *not* checked here: {addRule} checks it per insertion + 175 : : * while {setRules} checks the whole batch up front, so the two callers need different cap logic. + 176 : : * @param rule_ The rule to validate and add. + 177 : : */ + 178 : 277 : function _addRule(IRule rule_) internal virtual { + 179 : 277 : _checkRule(address(rule_)); + 180 : : // Should never revert because we check the presence of the rule before + 181 [ # + ]: 262 : require(_rules.add(address(rule_)), RuleEngine_RulesManagementModule_OperationNotSuccessful()); + 182 : 262 : emit AddRule(rule_); + 183 : : } + 184 : : + 185 : : /** + 186 : : * @notice Remove a rule from the array of rules + 187 : : * Revert if the rule found at the specified index does not match the rule in argument + 188 : : * @param rule_ address of the target rule + 189 : : * + 190 : : * + 191 : : */ + 192 : 13 : function _removeRule(IRule rule_) internal virtual { + 193 : : // Should never revert because we check the presence of the rule before + 194 [ # + ]: 13 : require(_rules.remove(address(rule_)), RuleEngine_RulesManagementModule_OperationNotSuccessful()); + 195 : 13 : emit RemoveRule(rule_); + 196 : : } + 197 : : + 198 : : /* ============ Transferred functions ============ */ + 199 : : + 200 : : /** + 201 : : * @notice Go through all the rule to know if a restriction exists on the transfer + 202 : : * @dev Complexity is O(number of configured rules). Large rule sets can make + 203 : : * transfers too expensive on chains with lower block gas limits. + 204 : : * Security convention: rule contracts are expected to be trusted and must not + 205 : : * hold {RULES_MANAGEMENT_ROLE}. + 206 : : * @param from the origin address + 207 : : * @param to the destination address + 208 : : * @param value to transfer + 209 : : * + 210 : : */ + 211 : 25 : function _transferred(address from, address to, uint256 value) internal virtual { + 212 : 25 : uint256 rulesLength = _rules.length(); + 213 : 25 : for (uint256 i = 0; i < rulesLength; ++i) { + 214 : 19 : IRule(_rules.pos(i)).transferred(from, to, value); + 215 : : } + 216 : : } + 217 : : + 218 : : /** + 219 : : * @notice Go through all the rule to know if a restriction exists on the transfer + 220 : : * @dev Complexity is O(number of configured rules). Large rule sets can make + 221 : : * transfers too expensive on chains with lower block gas limits. + 222 : : * Security convention: rule contracts are expected to be trusted and must not + 223 : : * hold {RULES_MANAGEMENT_ROLE}. + 224 : : * @param spender the spender address (transferFrom) + 225 : : * @param from the origin address + 226 : : * @param to the destination address + 227 : : * @param value to transfer + 228 : : * 229 : : */ - 230 : 277 : function _checkRule(address rule_) internal view virtual { - 231 [ + ]: 277 : if (rule_ == address(0x0)) { - 232 : 3 : revert RuleEngine_RulesManagementModule_RuleAddressZeroNotAllowed(); - 233 : : } - 234 [ + ]: 274 : if (_rules.contains(rule_)) { - 235 : 6 : revert RuleEngine_RulesManagementModule_RuleAlreadyExists(); - 236 : : } - 237 : : } - 238 : : } + 230 : 6 : function _transferred(address spender, address from, address to, uint256 value) internal virtual { + 231 : 6 : uint256 rulesLength = _rules.length(); + 232 : 6 : for (uint256 i = 0; i < rulesLength; ++i) { + 233 : 6 : IRule(_rules.pos(i)).transferred(spender, from, to, value); + 234 : : } + 235 : : } + 236 : : + 237 : : /** + 238 : : * @dev Access control hook guarding rule management operations. + 239 : : */ + 240 : 0 : function _onlyRulesManager() internal virtual; + 241 : : + 242 : : /** + 243 : : * @dev Access control hook guarding updates to the rule cap. + 244 : : */ + 245 : 0 : function _onlyRulesLimitManager() internal virtual; + 246 : : + 247 : : /** + 248 : : * @dev check if a rule is valid, revert otherwise + 249 : : * @param rule_ The candidate rule address to validate. + 250 : : */ + 251 : 277 : function _checkRule(address rule_) internal view virtual { + 252 [ + ]: 277 : if (rule_ == address(0x0)) { + 253 : 3 : revert RuleEngine_RulesManagementModule_RuleAddressZeroNotAllowed(); + 254 : : } + 255 [ + ]: 274 : if (_rules.contains(rule_)) { + 256 : 6 : revert RuleEngine_RulesManagementModule_RuleAlreadyExists(); + 257 : : } + 258 : : } + 259 : : } diff --git a/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.func-sort-c.html new file mode 100644 index 0000000..4b4cef1 --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.func-sort-c.html @@ -0,0 +1,109 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingExtendedModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingExtendedModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:2424100.0 %
Date:2026-08-20 17:53:43Functions:77100.0 %
Branches:55100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenBindingExtendedModule.getTokenBounds4
TokenBindingExtendedModule.isTokenSelfBindingApproved6
TokenBindingExtendedModule.unbindTokens9
TokenBindingExtendedModule.setTokenSelfBindingApprovalBatch12
TokenBindingExtendedModule.bindTokens18
TokenBindingExtendedModule.setTokenSelfBindingApproval27
TokenBindingExtendedModule._authorizeTokenBindingChange87
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.func.html b/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.func.html new file mode 100644 index 0000000..d247f64 --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.func.html @@ -0,0 +1,109 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingExtendedModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingExtendedModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:2424100.0 %
Date:2026-08-20 17:53:43Functions:77100.0 %
Branches:55100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenBindingExtendedModule._authorizeTokenBindingChange87
TokenBindingExtendedModule.bindTokens18
TokenBindingExtendedModule.getTokenBounds4
TokenBindingExtendedModule.isTokenSelfBindingApproved6
TokenBindingExtendedModule.setTokenSelfBindingApproval27
TokenBindingExtendedModule.setTokenSelfBindingApprovalBatch12
TokenBindingExtendedModule.unbindTokens9
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.gcov.html b/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.gcov.html new file mode 100644 index 0000000..98bacca --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingExtendedModule.sol.gcov.html @@ -0,0 +1,184 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingExtendedModule.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingExtendedModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:2424100.0 %
Date:2026-08-20 17:53:43Functions:77100.0 %
Branches:55100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : //SPDX-License-Identifier: MPL-2.0
+       2                 :            : 
+       3                 :            : pragma solidity ^0.8.20;
+       4                 :            : 
+       5                 :            : /* ==== OpenZeppelin === */
+       6                 :            : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+       7                 :            : /* ==== Interface and other library === */
+       8                 :            : import {ITokenBindingExtended} from "../interfaces/ITokenBindingExtended.sol";
+       9                 :            : import {TokenBindingModule} from "./TokenBindingModule.sol";
+      10                 :            : 
+      11                 :            : /**
+      12                 :            :  * @title TokenBindingExtendedModule
+      13                 :            :  * @notice Extends the standard-agnostic {TokenBindingModule} with batch binding, token
+      14                 :            :  * self-binding and enumeration of the bound tokens.
+      15                 :            :  * @dev Like its parent, this module carries no token-standard semantics: self-binding exists
+      16                 :            :  * because a token contract may want to register itself (as ERC-3643 `setCompliance` does), but
+      17                 :            :  * nothing here is specific to ERC-3643.
+      18                 :            :  */
+      19                 :            : abstract contract TokenBindingExtendedModule is TokenBindingModule, ITokenBindingExtended {
+      20                 :            :     using EnumerableSet for EnumerableSet.AddressSet;
+      21                 :            : 
+      22                 :            :     /**
+      23                 :            :      * @notice Tracks which tokens are allowed to bind and unbind themselves.
+      24                 :            :      */
+      25                 :            :     mapping(address token => bool approved) private _tokenSelfBindingApproval;
+      26                 :            : 
+      27                 :            :     /*//////////////////////////////////////////////////////////////
+      28                 :            :                             PUBLIC/public FUNCTIONS
+      29                 :            :     //////////////////////////////////////////////////////////////*/
+      30                 :            : 
+      31                 :            :     /* ============ State functions ============ */
+      32                 :            :     /**
+      33                 :            :      * @inheritdoc ITokenBindingExtended
+      34                 :            :      * @custom:security-note See {bindToken} for multi-tenant state risks. All tokens bound
+      35                 :            :      * in this batch share the same downstream state. Only bind tokens that are equally trusted
+      36                 :            :      * and governed together.
+      37                 :            :      */
+      38                 :         18 :     function bindTokens(address[] calldata tokens) public virtual override onlyTokenBindingManager {
+      39                 :         15 :         for (uint256 i = 0; i < tokens.length; ++i) {
+      40                 :         24 :             _bindToken(tokens[i]);
+      41                 :            :         }
+      42                 :            :     }
+      43                 :            : 
+      44                 :            :     /// @inheritdoc ITokenBindingExtended
+      45                 :          9 :     function unbindTokens(address[] calldata tokens) public virtual override onlyTokenBindingManager {
+      46                 :          6 :         for (uint256 i = 0; i < tokens.length; ++i) {
+      47                 :         12 :             _unbindToken(tokens[i]);
+      48                 :            :         }
+      49                 :            :     }
+      50                 :            : 
+      51                 :            :     /// @inheritdoc ITokenBindingExtended
+      52                 :         27 :     function setTokenSelfBindingApproval(address token, bool approved) public virtual override onlyTokenBindingManager {
+      53         [ +  + ]:         24 :         require(token != address(0), TokenBinding_InvalidTokenAddress());
+      54                 :         21 :         _tokenSelfBindingApproval[token] = approved;
+      55                 :         21 :         emit TokenSelfBindingApprovalSet(token, approved);
+      56                 :            :     }
+      57                 :            : 
+      58                 :            :     /// @inheritdoc ITokenBindingExtended
+      59                 :         12 :     function setTokenSelfBindingApprovalBatch(address[] calldata tokens, bool approved)
+      60                 :            :         public
+      61                 :            :         virtual
+      62                 :            :         override
+      63                 :            :         onlyTokenBindingManager
+      64                 :            :     {
+      65                 :          9 :         for (uint256 i = 0; i < tokens.length; ++i) {
+      66                 :         18 :             address token = tokens[i];
+      67         [ +  + ]:         18 :             require(token != address(0), TokenBinding_InvalidTokenAddress());
+      68                 :         15 :             _tokenSelfBindingApproval[token] = approved;
+      69                 :            :         }
+      70                 :          6 :         emit TokenSelfBindingApprovalBatchSet(tokens, approved);
+      71                 :            :     }
+      72                 :            : 
+      73                 :            :     /* ============ View functions ============ */
+      74                 :            :     /// @inheritdoc ITokenBindingExtended
+      75                 :          6 :     function isTokenSelfBindingApproved(address token) public view virtual override returns (bool) {
+      76                 :          6 :         return _tokenSelfBindingApproval[token];
+      77                 :            :     }
+      78                 :            : 
+      79                 :            :     /// @inheritdoc ITokenBindingExtended
+      80                 :          4 :     function getTokenBounds() public view virtual override returns (address[] memory) {
+      81                 :          4 :         return _boundTokens.values();
+      82                 :            :     }
+      83                 :            : 
+      84                 :            :     /*//////////////////////////////////////////////////////////////
+      85                 :            :                             INTERNAL/PRIVATE FUNCTIONS
+      86                 :            :     //////////////////////////////////////////////////////////////*/
+      87                 :            : 
+      88                 :            :     /**
+      89                 :            :      * @dev Authorizes bind/unbind operations.
+      90                 :            :      * Allows the binding manager, or approved token self-calls (used by ERC-3643 `setCompliance`).
+      91                 :            :      * @param token The token being bound or unbound.
+      92                 :            :      */
+      93                 :         87 :     function _authorizeTokenBindingChange(address token) internal virtual override {
+      94            [ + ]:         87 :         if (_msgSender() == token && _tokenSelfBindingApproval[token]) {
+      95                 :         87 :             return;
+      96                 :            :         }
+      97                 :         61 :         _onlyTokenBindingManager();
+      98                 :            :     }
+      99                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func-sort-c.html b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func-sort-c.html new file mode 100644 index 0000000..59f8c41 --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func-sort-c.html @@ -0,0 +1,121 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:242596.0 %
Date:2026-08-20 17:53:43Functions:91090.0 %
Branches:77100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenBindingModule._onlyTokenBindingManager0
TokenBindingModule.onlyBoundToken9
TokenBindingModule.onlyTokenBindingManager9
TokenBindingModule._authorizeTokenBindingChange12
TokenBindingModule.unbindToken24
TokenBindingModule._unbindToken28
TokenBindingModule._checkBoundToken43
TokenBindingModule.isTokenBound44
TokenBindingModule.bindToken75
TokenBindingModule._bindToken122
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func.html b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func.html new file mode 100644 index 0000000..8ad924f --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.func.html @@ -0,0 +1,121 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingModule.sol - functions + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:242596.0 %
Date:2026-08-20 17:53:43Functions:91090.0 %
Branches:77100.0 %
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Function Name Sort by function nameHit count Sort by hit count
TokenBindingModule._authorizeTokenBindingChange12
TokenBindingModule._bindToken122
TokenBindingModule._checkBoundToken43
TokenBindingModule._onlyTokenBindingManager0
TokenBindingModule._unbindToken28
TokenBindingModule.bindToken75
TokenBindingModule.isTokenBound44
TokenBindingModule.onlyBoundToken9
TokenBindingModule.onlyTokenBindingManager9
TokenBindingModule.unbindToken24
+
+
+ + + +
Generated by: LCOV version 1.16
+
+ + + diff --git a/doc/coverage/coverage/src/modules/TokenBindingModule.sol.gcov.html b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.gcov.html new file mode 100644 index 0000000..9f27f61 --- /dev/null +++ b/doc/coverage/coverage/src/modules/TokenBindingModule.sol.gcov.html @@ -0,0 +1,233 @@ + + + + + + + LCOV - lcov.info - src/modules/TokenBindingModule.sol + + + + + + + + + + + + + + +
LCOV - code coverage report
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current view:top level - src/modules - TokenBindingModule.sol (source / functions)HitTotalCoverage
Test:lcov.infoLines:242596.0 %
Date:2026-08-20 17:53:43Functions:91090.0 %
Branches:77100.0 %
+
+ + + + + + + + +

+
           Branch data     Line data    Source code
+
+       1                 :            : //SPDX-License-Identifier: MPL-2.0
+       2                 :            : 
+       3                 :            : pragma solidity ^0.8.20;
+       4                 :            : 
+       5                 :            : /* ==== OpenZeppelin === */
+       6                 :            : import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+       7                 :            : import {Context} from "@openzeppelin/contracts/utils/Context.sol";
+       8                 :            : /* ==== Interface and other library === */
+       9                 :            : import {ITokenBinding} from "../interfaces/ITokenBinding.sol";
+      10                 :            : import {TokenBindingModuleInvariantStorage} from "./library/TokenBindingModuleInvariantStorage.sol";
+      11                 :            : 
+      12                 :            : /**
+      13                 :            :  * @title TokenBindingModule
+      14                 :            :  * @notice Standard-agnostic token binding registry (an allowlist) implementing {ITokenBinding}:
+      15                 :            :  * it stores the set of tokens allowed to call the bound-token entry points of the contract
+      16                 :            :  * embedding it.
+      17                 :            :  * @dev This module deliberately knows nothing about ERC-3643, ERC-1404, rules or the RuleEngine.
+      18                 :            :  * It only depends on OpenZeppelin's {Context} and {EnumerableSet}, so it can be reused as-is by
+      19                 :            :  * any project that has to bind tokens. It provides:
+      20                 :            :  *  - the allowlist storage and `bindToken` / `unbindToken` / `isTokenBound`;
+      21                 :            :  *  - the {onlyBoundToken} modifier gating the bound-token entry points;
+      22                 :            :  *  - two access control hooks left to the deployment: {_authorizeTokenBindingChange}, which
+      23                 :            :  *    authorizes a bind/unbind, and {_onlyTokenBindingManager}, the manager check it defaults to.
+      24                 :            :  *
+      25                 :            :  * The ERC-3643 vocabulary (`compliance`, `getTokenBound`, `created` / `destroyed`) lives in
+      26                 :            :  * {ERC3643ComplianceModule}, which is a thin adapter over this module.
+      27                 :            :  */
+      28                 :            : abstract contract TokenBindingModule is Context, ITokenBinding, TokenBindingModuleInvariantStorage {
+      29                 :            :     /* ==== Type declaration === */
+      30                 :            :     using EnumerableSet for EnumerableSet.AddressSet;
+      31                 :            : 
+      32                 :            :     /* ==== State Variables === */
+      33                 :            :     // Token binding tracking
+      34                 :            :     /**
+      35                 :            :      * @notice Set of tokens allowed to call the bound-token entry points.
+      36                 :            :      */
+      37                 :            :     EnumerableSet.AddressSet internal _boundTokens;
+      38                 :            : 
+      39                 :            :     /* ==== Modifier === */
+      40                 :            :     /**
+      41                 :            :      * @dev Restricts a function to the tokens currently bound.
+      42                 :            :      */
+      43                 :          9 :     modifier onlyBoundToken() {
+      44                 :          9 :         _checkBoundToken();
+      45                 :            :         _;
+      46                 :            :     }
+      47                 :            : 
+      48                 :            :     /**
+      49                 :            :      * @dev Restricts a function to the account allowed to manage the bindings.
+      50                 :            :      */
+      51                 :          9 :     modifier onlyTokenBindingManager() {
+      52                 :          9 :         _onlyTokenBindingManager();
+      53                 :            :         _;
+      54                 :            :     }
+      55                 :            : 
+      56                 :            :     /*//////////////////////////////////////////////////////////////
+      57                 :            :                             PUBLIC/public FUNCTIONS
+      58                 :            :     //////////////////////////////////////////////////////////////*/
+      59                 :            : 
+      60                 :            :     /* ============ State functions ============ */
+      61                 :            :     /**
+      62                 :            :      * @inheritdoc ITokenBinding
+      63                 :            :      * @dev Authorized by {_authorizeTokenBindingChange}.
+      64                 :            :      * @custom:security-note "Multi-tenant" means one instance is shared by multiple token
+      65                 :            :      * contracts. Downstream state (for the RuleEngine: the per-address accounting held by
+      66                 :            :      * stateful rules) is shared across all bound tokens, so binding tokens from different
+      67                 :            :      * issuers silently cross-contaminates it. Only bind tokens that are equally trusted and
+      68                 :            :      * governed together.
+      69                 :            :      */
+      70                 :         75 :     function bindToken(address token) public virtual override {
+      71                 :         75 :         _authorizeTokenBindingChange(token);
+      72                 :         65 :         _bindToken(token);
+      73                 :            :     }
+      74                 :            : 
+      75                 :            :     /**
+      76                 :            :      * @inheritdoc ITokenBinding
+      77                 :            :      * @dev Authorized by {_authorizeTokenBindingChange}.
+      78                 :            :      * Operator warning: unbinding is an administrative operation and does not erase any state
+      79                 :            :      * already stored downstream in a previously shared ("multi-tenant") setup.
+      80                 :            :      */
+      81                 :         24 :     function unbindToken(address token) public virtual override {
+      82                 :         24 :         _authorizeTokenBindingChange(token);
+      83                 :         16 :         _unbindToken(token);
+      84                 :            :     }
+      85                 :            : 
+      86                 :            :     /* ============ View functions ============ */
+      87                 :            :     /// @inheritdoc ITokenBinding
+      88                 :         44 :     function isTokenBound(address token) public view virtual override returns (bool) {
+      89                 :         44 :         return _boundTokens.contains(token);
+      90                 :            :     }
+      91                 :            : 
+      92                 :            :     /*//////////////////////////////////////////////////////////////
+      93                 :            :                             INTERNAL/PRIVATE FUNCTIONS
+      94                 :            :     //////////////////////////////////////////////////////////////*/
+      95                 :            : 
+      96                 :            :     /**
+      97                 :            :      * @dev Adds a token to the bound set.
+      98                 :            :      * @param token The token to bind; reverts on the zero address or when already bound.
+      99                 :            :      */
+     100                 :        122 :     function _bindToken(address token) internal virtual {
+     101         [ +  + ]:        122 :         require(token != address(0), TokenBinding_InvalidTokenAddress());
+     102                 :            :         // add() returns false when the token is already bound, so a separate
+     103                 :            :         // contains() lookup is unnecessary.
+     104         [ +  + ]:        116 :         require(_boundTokens.add(token), TokenBinding_TokenAlreadyBound());
+     105                 :        110 :         emit TokenBound(token);
+     106                 :            :     }
+     107                 :            : 
+     108                 :            :     /**
+     109                 :            :      * @dev Removes a token from the bound set.
+     110                 :            :      * @param token The token to unbind; reverts when it is not currently bound.
+     111                 :            :      */
+     112                 :         28 :     function _unbindToken(address token) internal virtual {
+     113                 :            :         // remove() returns false when the token was not bound, so a separate
+     114                 :            :         // contains() lookup is unnecessary.
+     115         [ +  + ]:         28 :         require(_boundTokens.remove(token), TokenBinding_TokenNotBound());
+     116                 :            : 
+     117                 :         22 :         emit TokenUnbound(token);
+     118                 :            :     }
+     119                 :            : 
+     120                 :            :     /**
+     121                 :            :      * @dev Authorization hook for bind/unbind, receiving the token being bound or unbound.
+     122                 :            :      * Defaults to the binding manager check, which ignores the token; {TokenBindingExtendedModule}
+     123                 :            :      * overrides it to also allow approved token self-calls.
+     124                 :            :      */
+     125                 :         12 :     function _authorizeTokenBindingChange(
+     126                 :            :         address /* token */
+     127                 :            :     )
+     128                 :            :         internal
+     129                 :            :         virtual
+     130                 :            :     {
+     131                 :         12 :         _onlyTokenBindingManager();
+     132                 :            :     }
+     133                 :            : 
+     134                 :            :     /**
+     135                 :            :      * @dev Access control hook guarding binding management operations, implemented by the
+     136                 :            :      * deployable contracts.
+     137                 :            :      */
+     138                 :          0 :     function _onlyTokenBindingManager() internal virtual;
+     139                 :            : 
+     140                 :            :     /**
+     141                 :            :      * @dev Reverts when the caller is not a bound token.
+     142                 :            :      */
+     143                 :         43 :     function _checkBoundToken() internal view virtual {
+     144            [ + ]:         43 :         if (!_boundTokens.contains(_msgSender())) {
+     145                 :         11 :             revert TokenBinding_UnauthorizedCaller();
+     146                 :            :         }
+     147                 :            :     }
+     148                 :            : }
+
+
+
+ + + + +
Generated by: LCOV version 1.16
+
+ + + 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 2924bb5..acb8cf7 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.func.html b/doc/coverage/coverage/src/modules/VersionModule.sol.func.html index 04b7354..e29f487 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 diff --git a/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html b/doc/coverage/coverage/src/modules/VersionModule.sol.gcov.html index ca50c83..1fed096 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 @@ Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: 1 diff --git a/doc/coverage/coverage/src/modules/index-sort-b.html b/doc/coverage/coverage/src/modules/index-sort-b.html index 445f53e..29451bb 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 @@ lcov.info Lines: - 114 - 118 - 96.6 % + 122 + 126 + 96.8 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 34 - 38 - 89.5 % + 39 + 43 + 90.7 % Branches: - 28 - 31 - 90.3 % + 27 + 29 + 93.1 % @@ -84,14 +84,14 @@ RulesManagementModule.sol -
96.9%96.9%
+
97.0%97.0%
- 96.9 % - 62 / 64 - 89.5 % - 17 / 19 - 82.4 % - 14 / 17 + 97.0 % + 64 / 66 + 90.5 % + 19 / 21 + 86.7 % + 13 / 15 VersionModule.sol @@ -111,6 +111,30 @@
100.0%
100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + ERC3643ComplianceModule.sol + +
85.7%85.7%
+ + 85.7 % + 6 / 7 + 66.7 % + 2 / 3 + 100.0 % + 2 / 2 + + + TokenBindingExtendedModule.sol + +
100.0%
+ + 100.0 % 24 / 24 100.0 % 7 / 7 @@ -118,16 +142,16 @@ 5 / 5 - ERC3643ComplianceModule.sol + TokenBindingModule.sol -
92.9%92.9%
+
96.0%96.0%
- 92.9 % - 26 / 28 - 81.8 % - 9 / 11 + 96.0 % + 24 / 25 + 90.0 % + 9 / 10 100.0 % - 9 / 9 + 7 / 7 diff --git a/doc/coverage/coverage/src/modules/index-sort-f.html b/doc/coverage/coverage/src/modules/index-sort-f.html index f1d44ca..7ca545c 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 @@ lcov.info Lines: - 114 - 118 - 96.6 % + 122 + 126 + 96.8 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 34 - 38 - 89.5 % + 39 + 43 + 90.7 % Branches: - 28 - 31 - 90.3 % + 27 + 29 + 93.1 % @@ -84,26 +84,38 @@ ERC3643ComplianceModule.sol -
92.9%92.9%
+
85.7%85.7%
- 92.9 % - 26 / 28 - 81.8 % - 9 / 11 + 85.7 % + 6 / 7 + 66.7 % + 2 / 3 100.0 % - 9 / 9 + 2 / 2 + + + TokenBindingModule.sol + +
96.0%96.0%
+ + 96.0 % + 24 / 25 + 90.0 % + 9 / 10 + 100.0 % + 7 / 7 RulesManagementModule.sol -
96.9%96.9%
+
97.0%97.0%
- 96.9 % - 62 / 64 - 89.5 % - 17 / 19 - 82.4 % - 14 / 17 + 97.0 % + 64 / 66 + 90.5 % + 19 / 21 + 86.7 % + 13 / 15 VersionModule.sol @@ -123,6 +135,18 @@
100.0%
100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + TokenBindingExtendedModule.sol + +
100.0%
+ + 100.0 % 24 / 24 100.0 % 7 / 7 diff --git a/doc/coverage/coverage/src/modules/index-sort-l.html b/doc/coverage/coverage/src/modules/index-sort-l.html index 9f7566d..c7acd77 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 @@ lcov.info Lines: - 114 - 118 - 96.6 % + 122 + 126 + 96.8 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 34 - 38 - 89.5 % + 39 + 43 + 90.7 % Branches: - 28 - 31 - 90.3 % + 27 + 29 + 93.1 % @@ -84,26 +84,38 @@ ERC3643ComplianceModule.sol -
92.9%92.9%
+
85.7%85.7%
- 92.9 % - 26 / 28 - 81.8 % - 9 / 11 + 85.7 % + 6 / 7 + 66.7 % + 2 / 3 100.0 % - 9 / 9 + 2 / 2 + + + TokenBindingModule.sol + +
96.0%96.0%
+ + 96.0 % + 24 / 25 + 90.0 % + 9 / 10 + 100.0 % + 7 / 7 RulesManagementModule.sol -
96.9%96.9%
+
97.0%97.0%
- 96.9 % - 62 / 64 - 89.5 % - 17 / 19 - 82.4 % - 14 / 17 + 97.0 % + 64 / 66 + 90.5 % + 19 / 21 + 86.7 % + 13 / 15 VersionModule.sol @@ -123,6 +135,18 @@
100.0%
100.0 % + 2 / 2 + 100.0 % + 1 / 1 + - + 0 / 0 + + + TokenBindingExtendedModule.sol + +
100.0%
+ + 100.0 % 24 / 24 100.0 % 7 / 7 diff --git a/doc/coverage/coverage/src/modules/index.html b/doc/coverage/coverage/src/modules/index.html index b87c0a2..6f5af5e 100644 --- a/doc/coverage/coverage/src/modules/index.html +++ b/doc/coverage/coverage/src/modules/index.html @@ -31,27 +31,27 @@ lcov.info Lines: - 114 - 118 - 96.6 % + 122 + 126 + 96.8 % Date: - 2026-08-13 15:50:18 + 2026-08-20 17:53:43 Functions: - 34 - 38 - 89.5 % + 39 + 43 + 90.7 % Branches: - 28 - 31 - 90.3 % + 27 + 29 + 93.1 % @@ -87,35 +87,59 @@
100.0%
100.0 % - 24 / 24 - 100.0 % - 7 / 7 + 2 / 2 100.0 % - 5 / 5 + 1 / 1 + - + 0 / 0 ERC3643ComplianceModule.sol -
92.9%92.9%
+
85.7%85.7%
- 92.9 % - 26 / 28 - 81.8 % - 9 / 11 + 85.7 % + 6 / 7 + 66.7 % + 2 / 3 100.0 % - 9 / 9 + 2 / 2 RulesManagementModule.sol -
96.9%96.9%
+
97.0%97.0%
+ + 97.0 % + 64 / 66 + 90.5 % + 19 / 21 + 86.7 % + 13 / 15 + + + TokenBindingExtendedModule.sol + +
100.0%
- 96.9 % - 62 / 64 - 89.5 % - 17 / 19 - 82.4 % - 14 / 17 + 100.0 % + 24 / 24 + 100.0 % + 7 / 7 + 100.0 % + 5 / 5 + + + TokenBindingModule.sol + +
96.0%96.0%
+ + 96.0 % + 24 / 25 + 90.0 % + 9 / 10 + 100.0 % + 7 / 7 VersionModule.sol diff --git a/doc/coverage/lcov.info b/doc/coverage/lcov.info index e97410e..3c92aac 100644 --- a/doc/coverage/lcov.info +++ b/doc/coverage/lcov.info @@ -1,27 +1,27 @@ TN: SF:script/CMTATWithRuleEngineScript.s.sol -DA:20,1 -FN:20,CMTATWithRuleEngineScript.run +DA:26,1 +FN:26,CMTATWithRuleEngineScript.run FNDA:1,CMTATWithRuleEngineScript.run -DA:22,1 -DA:23,1 -DA:24,1 -DA:25,1 -DA:27,1 DA:28,1 DA:29,1 DA:30,1 -DA:37,1 -DA:38,1 -DA:39,1 -DA:40,1 -DA:42,1 +DA:31,1 +DA:33,1 +DA:34,1 +DA:35,1 +DA:36,1 DA:43,1 +DA:44,1 DA:45,1 DA:46,1 -DA:47,1 DA:48,1 -DA:50,1 +DA:49,1 +DA:51,1 +DA:52,1 +DA:53,1 +DA:54,1 +DA:56,1 FNF:1 FNH:1 LF:20 @@ -31,24 +31,24 @@ BRH:0 end_of_record TN: SF:script/RuleEngineScript.s.sol -DA:32,1 -FN:32,RuleEngineScript.run -FNDA:1,RuleEngineScript.run -DA:34,1 -DA:35,1 -DA:36,1 DA:37,1 +FN:37,RuleEngineScript.run +FNDA:1,RuleEngineScript.run DA:39,1 DA:40,1 +DA:41,1 DA:42,1 -DA:43,1 DA:44,1 DA:45,1 DA:47,1 DA:48,1 DA:49,1 +DA:50,1 +DA:52,1 DA:53,1 DA:54,1 +DA:58,1 +DA:59,1 FNF:1 FNH:1 LF:16 @@ -325,123 +325,40 @@ BRH:0 end_of_record TN: SF:src/modules/ERC3643ComplianceExtendedModule.sol -DA:28,18 -FN:28,ERC3643ComplianceExtendedModule.bindTokens -FNDA:18,ERC3643ComplianceExtendedModule.bindTokens -DA:29,15 -DA:30,24 -DA:35,9 -FN:35,ERC3643ComplianceExtendedModule.unbindTokens -FNDA:9,ERC3643ComplianceExtendedModule.unbindTokens -DA:36,6 -DA:37,12 -DA:42,27 -FN:42,ERC3643ComplianceExtendedModule.setTokenSelfBindingApproval -FNDA:27,ERC3643ComplianceExtendedModule.setTokenSelfBindingApproval -DA:43,24 -BRDA:43,0,0,3 -BRDA:43,0,1,21 -DA:44,21 -DA:45,21 -DA:49,12 -FN:49,ERC3643ComplianceExtendedModule.setTokenSelfBindingApprovalBatch -FNDA:12,ERC3643ComplianceExtendedModule.setTokenSelfBindingApprovalBatch -DA:55,9 -DA:56,18 -DA:57,18 -BRDA:57,1,0,3 -BRDA:57,1,1,15 -DA:58,15 -DA:60,6 -DA:64,6 -FN:64,ERC3643ComplianceExtendedModule.isTokenSelfBindingApproved -FNDA:6,ERC3643ComplianceExtendedModule.isTokenSelfBindingApproved -DA:65,6 -DA:69,4 -FN:69,ERC3643ComplianceExtendedModule.getTokenBounds -FNDA:4,ERC3643ComplianceExtendedModule.getTokenBounds -DA:70,4 -DA:78,87 -FN:78,ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange -FNDA:87,ERC3643ComplianceExtendedModule._authorizeComplianceBindingChange -DA:79,87 -BRDA:79,2,0,87 -DA:80,87 -DA:82,61 -FNF:7 -FNH:7 -LF:24 -LH:24 -BRF:5 -BRH:5 +DA:32,87 +FN:32,ERC3643ComplianceExtendedModule._authorizeTokenBindingChange +FNDA:87,ERC3643ComplianceExtendedModule._authorizeTokenBindingChange +DA:37,87 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 end_of_record TN: SF:src/modules/ERC3643ComplianceModule.sol -DA:27,7 -FN:27,ERC3643ComplianceModule.onlyBoundToken -FNDA:7,ERC3643ComplianceModule.onlyBoundToken -DA:28,7 -DA:32,9 -FN:32,ERC3643ComplianceModule.onlyComplianceManager -FNDA:9,ERC3643ComplianceModule.onlyComplianceManager -DA:33,9 -DA:52,66 -FN:52,ERC3643ComplianceModule.bindToken -FNDA:66,ERC3643ComplianceModule.bindToken -DA:53,66 -DA:54,58 -DA:63,21 -FN:63,ERC3643ComplianceModule.unbindToken -FNDA:21,ERC3643ComplianceModule.unbindToken -DA:64,21 -DA:65,14 -DA:69,41 -FN:69,ERC3643ComplianceModule.isTokenBound -FNDA:41,ERC3643ComplianceModule.isTokenBound -DA:70,41 -DA:74,7 -FN:74,ERC3643ComplianceModule.getTokenBound +DA:41,7 +FN:41,ERC3643ComplianceModule.getTokenBound FNDA:7,ERC3643ComplianceModule.getTokenBound -DA:75,7 -BRDA:75,0,0,5 -BRDA:75,0,1,2 -DA:78,5 -DA:80,2 -DA:92,26 -FN:92,ERC3643ComplianceModule._unbindToken -FNDA:26,ERC3643ComplianceModule._unbindToken -DA:95,26 -BRDA:95,1,0,5 -BRDA:95,1,1,21 -DA:97,21 -DA:104,115 -FN:104,ERC3643ComplianceModule._bindToken -FNDA:115,ERC3643ComplianceModule._bindToken -DA:105,115 -BRDA:105,2,0,5 -BRDA:105,2,1,110 -DA:108,110 -BRDA:108,3,0,5 -BRDA:108,3,1,105 -DA:109,105 -DA:116,0 -FN:116,ERC3643ComplianceModule._authorizeComplianceBindingChange -FNDA:0,ERC3643ComplianceModule._authorizeComplianceBindingChange -DA:121,0 -FN:121,ERC3643ComplianceModule._onlyComplianceManager +DA:42,7 +BRDA:42,0,0,5 +BRDA:42,0,1,2 +DA:45,5 +DA:47,2 +DA:59,127 +FN:59,ERC3643ComplianceModule._onlyTokenBindingManager +FNDA:127,ERC3643ComplianceModule._onlyTokenBindingManager +DA:60,127 +DA:69,0 +FN:69,ERC3643ComplianceModule._onlyComplianceManager FNDA:0,ERC3643ComplianceModule._onlyComplianceManager -DA:126,41 -FN:126,ERC3643ComplianceModule._checkBoundToken -FNDA:41,ERC3643ComplianceModule._checkBoundToken -DA:127,41 -BRDA:127,4,0,10 -DA:128,10 -FNF:11 -FNH:9 -LF:28 -LH:26 -BRF:9 -BRH:9 +FNF:3 +FNH:2 +LF:7 +LH:6 +BRF:2 +BRH:2 end_of_record TN: SF:src/modules/RulesManagementModule.sol @@ -467,109 +384,226 @@ BRDA:59,2,0,38 DA:60,38 DA:62,44 DA:63,81 -DA:65,76 -BRDA:65,3,0,- -BRDA:65,3,1,76 -DA:66,76 -DA:73,19 -FN:73,RulesManagementModule.clearRules +DA:70,19 +FN:70,RulesManagementModule.clearRules FNDA:19,RulesManagementModule.clearRules -DA:74,16 -DA:82,203 -FN:82,RulesManagementModule.addRule +DA:71,16 +DA:79,203 +FN:79,RulesManagementModule.addRule FNDA:203,RulesManagementModule.addRule -DA:83,198 -BRDA:83,4,0,2 -DA:84,2 -DA:86,196 -DA:87,186 -BRDA:87,5,0,- -BRDA:87,5,1,186 -DA:88,186 -DA:94,9 -FN:94,RulesManagementModule.setMaxRules +DA:80,198 +BRDA:80,3,0,2 +DA:81,2 +DA:83,196 +DA:89,9 +FN:89,RulesManagementModule.setMaxRules FNDA:9,RulesManagementModule.setMaxRules -DA:95,5 -BRDA:95,6,0,1 -DA:96,1 -DA:98,4 -DA:99,4 -DA:105,18 -FN:105,RulesManagementModule.removeRule +DA:90,5 +DA:96,18 +FN:96,RulesManagementModule.removeRule FNDA:18,RulesManagementModule.removeRule -DA:106,16 -BRDA:106,7,0,3 -BRDA:106,7,1,13 -DA:107,13 -DA:114,8 -FN:114,RulesManagementModule.maxRules +DA:97,16 +BRDA:97,4,0,3 +BRDA:97,4,1,13 +DA:98,13 +DA:105,8 +FN:105,RulesManagementModule.maxRules FNDA:8,RulesManagementModule.maxRules -DA:115,8 -DA:121,191 -FN:121,RulesManagementModule.rulesCount +DA:106,8 +DA:112,191 +FN:112,RulesManagementModule.rulesCount FNDA:191,RulesManagementModule.rulesCount -DA:122,323 -DA:128,82 -FN:128,RulesManagementModule.containsRule +DA:113,323 +DA:119,82 +FN:119,RulesManagementModule.containsRule FNDA:82,RulesManagementModule.containsRule -DA:129,92 -DA:135,5 -FN:135,RulesManagementModule.rule +DA:120,92 +DA:126,5 +FN:126,RulesManagementModule.rule FNDA:5,RulesManagementModule.rule -DA:136,152 -BRDA:136,8,0,150 -BRDA:136,8,1,2 -DA:139,150 -DA:141,2 -DA:148,15 -FN:148,RulesManagementModule.rules +DA:127,152 +BRDA:127,5,0,150 +BRDA:127,5,1,2 +DA:130,150 +DA:132,2 +DA:139,15 +FN:139,RulesManagementModule.rules FNDA:15,RulesManagementModule.rules -DA:149,15 -DA:159,54 -FN:159,RulesManagementModule._clearRules +DA:140,15 +DA:150,54 +FN:150,RulesManagementModule._clearRules FNDA:54,RulesManagementModule._clearRules -DA:160,54 -DA:161,54 -DA:171,13 -FN:171,RulesManagementModule._removeRule +DA:151,54 +DA:152,54 +DA:163,374 +FN:163,RulesManagementModule._setMaxRules +FNDA:374,RulesManagementModule._setMaxRules +DA:164,374 +BRDA:164,6,0,1 +DA:165,1 +DA:167,373 +DA:168,373 +DA:178,277 +FN:178,RulesManagementModule._addRule +FNDA:277,RulesManagementModule._addRule +DA:179,277 +DA:181,262 +BRDA:181,7,0,- +BRDA:181,7,1,262 +DA:182,262 +DA:192,13 +FN:192,RulesManagementModule._removeRule FNDA:13,RulesManagementModule._removeRule -DA:173,13 -BRDA:173,9,0,- -BRDA:173,9,1,13 -DA:174,13 -DA:190,25 -FN:190,RulesManagementModule._transferred.0 +DA:194,13 +BRDA:194,8,0,- +BRDA:194,8,1,13 +DA:195,13 +DA:211,25 +FN:211,RulesManagementModule._transferred.0 FNDA:25,RulesManagementModule._transferred.0 -DA:191,25 -DA:192,25 -DA:193,19 -DA:209,6 -FN:209,RulesManagementModule._transferred.1 +DA:212,25 +DA:213,25 +DA:214,19 +DA:230,6 +FN:230,RulesManagementModule._transferred.1 FNDA:6,RulesManagementModule._transferred.1 -DA:210,6 -DA:211,6 -DA:212,6 -DA:219,0 -FN:219,RulesManagementModule._onlyRulesManager +DA:231,6 +DA:232,6 +DA:233,6 +DA:240,0 +FN:240,RulesManagementModule._onlyRulesManager FNDA:0,RulesManagementModule._onlyRulesManager -DA:224,0 -FN:224,RulesManagementModule._onlyRulesLimitManager +DA:245,0 +FN:245,RulesManagementModule._onlyRulesLimitManager FNDA:0,RulesManagementModule._onlyRulesLimitManager -DA:230,277 -FN:230,RulesManagementModule._checkRule +DA:251,277 +FN:251,RulesManagementModule._checkRule FNDA:277,RulesManagementModule._checkRule -DA:231,277 -BRDA:231,10,0,3 -DA:232,3 -DA:234,274 -BRDA:234,11,0,6 -DA:235,6 -FNF:19 -FNH:17 -LF:64 -LH:62 -BRF:17 -BRH:14 +DA:252,277 +BRDA:252,9,0,3 +DA:253,3 +DA:255,274 +BRDA:255,10,0,6 +DA:256,6 +FNF:21 +FNH:19 +LF:66 +LH:64 +BRF:15 +BRH:13 +end_of_record +TN: +SF:src/modules/TokenBindingExtendedModule.sol +DA:38,18 +FN:38,TokenBindingExtendedModule.bindTokens +FNDA:18,TokenBindingExtendedModule.bindTokens +DA:39,15 +DA:40,24 +DA:45,9 +FN:45,TokenBindingExtendedModule.unbindTokens +FNDA:9,TokenBindingExtendedModule.unbindTokens +DA:46,6 +DA:47,12 +DA:52,27 +FN:52,TokenBindingExtendedModule.setTokenSelfBindingApproval +FNDA:27,TokenBindingExtendedModule.setTokenSelfBindingApproval +DA:53,24 +BRDA:53,0,0,3 +BRDA:53,0,1,21 +DA:54,21 +DA:55,21 +DA:59,12 +FN:59,TokenBindingExtendedModule.setTokenSelfBindingApprovalBatch +FNDA:12,TokenBindingExtendedModule.setTokenSelfBindingApprovalBatch +DA:65,9 +DA:66,18 +DA:67,18 +BRDA:67,1,0,3 +BRDA:67,1,1,15 +DA:68,15 +DA:70,6 +DA:75,6 +FN:75,TokenBindingExtendedModule.isTokenSelfBindingApproved +FNDA:6,TokenBindingExtendedModule.isTokenSelfBindingApproved +DA:76,6 +DA:80,4 +FN:80,TokenBindingExtendedModule.getTokenBounds +FNDA:4,TokenBindingExtendedModule.getTokenBounds +DA:81,4 +DA:93,87 +FN:93,TokenBindingExtendedModule._authorizeTokenBindingChange +FNDA:87,TokenBindingExtendedModule._authorizeTokenBindingChange +DA:94,87 +BRDA:94,2,0,87 +DA:95,87 +DA:97,61 +FNF:7 +FNH:7 +LF:24 +LH:24 +BRF:5 +BRH:5 +end_of_record +TN: +SF:src/modules/TokenBindingModule.sol +DA:43,9 +FN:43,TokenBindingModule.onlyBoundToken +FNDA:9,TokenBindingModule.onlyBoundToken +DA:44,9 +DA:51,9 +FN:51,TokenBindingModule.onlyTokenBindingManager +FNDA:9,TokenBindingModule.onlyTokenBindingManager +DA:52,9 +DA:70,75 +FN:70,TokenBindingModule.bindToken +FNDA:75,TokenBindingModule.bindToken +DA:71,75 +DA:72,65 +DA:81,24 +FN:81,TokenBindingModule.unbindToken +FNDA:24,TokenBindingModule.unbindToken +DA:82,24 +DA:83,16 +DA:88,44 +FN:88,TokenBindingModule.isTokenBound +FNDA:44,TokenBindingModule.isTokenBound +DA:89,44 +DA:100,122 +FN:100,TokenBindingModule._bindToken +FNDA:122,TokenBindingModule._bindToken +DA:101,122 +BRDA:101,0,0,6 +BRDA:101,0,1,116 +DA:104,116 +BRDA:104,1,0,6 +BRDA:104,1,1,110 +DA:105,110 +DA:112,28 +FN:112,TokenBindingModule._unbindToken +FNDA:28,TokenBindingModule._unbindToken +DA:115,28 +BRDA:115,2,0,6 +BRDA:115,2,1,22 +DA:117,22 +DA:125,12 +FN:125,TokenBindingModule._authorizeTokenBindingChange +FNDA:12,TokenBindingModule._authorizeTokenBindingChange +DA:131,12 +DA:138,0 +FN:138,TokenBindingModule._onlyTokenBindingManager +FNDA:0,TokenBindingModule._onlyTokenBindingManager +DA:143,43 +FN:143,TokenBindingModule._checkBoundToken +FNDA:43,TokenBindingModule._checkBoundToken +DA:144,43 +BRDA:144,3,0,11 +DA:145,11 +FNF:10 +FNH:9 +LF:25 +LH:24 +BRF:7 +BRH:7 end_of_record TN: SF:src/modules/VersionModule.sol diff --git a/doc/schema/plantuml/ruleengine-access-control.png b/doc/schema/plantuml/ruleengine-access-control.png new file mode 100644 index 0000000..5e6475b Binary files /dev/null and b/doc/schema/plantuml/ruleengine-access-control.png differ diff --git a/doc/schema/plantuml/ruleengine-access-control.puml b/doc/schema/plantuml/ruleengine-access-control.puml new file mode 100644 index 0000000..83f112f --- /dev/null +++ b/doc/schema/plantuml/ruleengine-access-control.puml @@ -0,0 +1,103 @@ +@startuml +title RuleEngine - access control (RBAC deployment) + +skinparam shadowing false +skinparam componentStyle rectangle +left to right direction +skinparam ArrowFontSize 11 +skinparam NoteFontSize 11 + +skinparam usecase { + BackgroundColor #F5F7FA + BorderColor #4A6785 +} +skinparam rectangle { + BackgroundColor<> #FFFFFF + BorderColor<> #4A6785 + BackgroundColor<> #FBF6E9 + BorderColor<> #B08B2E +} + +usecase "DEFAULT_ADMIN_ROLE" as ADMIN +usecase "RULES_MANAGEMENT_ROLE" as RULES +usecase "COMPLIANCE_MANAGER_ROLE" as COMPLIANCE + +rectangle "Rule management\n(RulesManagementModule)" { + rectangle "setRules" <> as setRules + rectangle "clearRules" <> as clearRules + rectangle "addRule" <> as addRule + rectangle "removeRule" <> as removeRule +} + +rectangle "Rule cap\n(RulesManagementModule)" { + rectangle "setMaxRules" <> as setMaxRules +} + +rectangle "Token binding\n(TokenBindingModule)" { + rectangle "bindToken" <> as bindToken + rectangle "unbindToken" <> as unbindToken +} + +rectangle "Token binding, extended\n(TokenBindingExtendedModule)" { + rectangle "bindTokens" <> as bindTokens + rectangle "unbindTokens" <> as unbindTokens + rectangle "setTokenSelfBindingApproval" <> as setApproval + rectangle "setTokenSelfBindingApprovalBatch" <> as setApprovalBatch +} + +rectangle "Compliance callbacks\n(RuleEngineBase)" { + rectangle "transferred" <> as transferred + rectangle "created" <> as created + rectangle "destroyed" <> as destroyed +} + +rectangle "Bound token" <> as BoundToken +rectangle "Token approved\nfor self-binding" <> as SelfBindToken + +ADMIN --> RULES : manage / is +ADMIN --> COMPLIANCE : manage / is + +RULES --> setRules +RULES --> clearRules +RULES --> addRule +RULES --> removeRule + +ADMIN --> setMaxRules + +COMPLIANCE --> bindToken +COMPLIANCE --> unbindToken +COMPLIANCE --> bindTokens +COMPLIANCE --> unbindTokens +COMPLIANCE --> setApproval +COMPLIANCE --> setApprovalBatch + +SelfBindToken ..> bindToken : self-bind +SelfBindToken ..> unbindToken : self-unbind + +BoundToken ..> transferred : onlyBoundToken +BoundToken ..> created : onlyBoundToken +BoundToken ..> destroyed : onlyBoundToken + +note bottom of ADMIN + The default admin holds every role: RuleEngine + overrides hasRole() to answer true for any role. +end note + +note bottom of SelfBindToken + Self-binding is opt-in per token, granted by the + compliance manager with setTokenSelfBindingApproval. + It exists for the ERC-3643 setCompliance handshake. +end note + +note bottom of BoundToken + Data plane, not a role: the compliance callbacks are + guarded by the caller being a bound token. +end note + +legend bottom + RuleEngineOwnable / RuleEngineOwnable2Step: the same functions are + guarded by onlyOwner instead of roles, so the single owner replaces + the three role nodes above. +endlegend + +@enduml diff --git a/doc/schema/sol2uml/ERC3643ComplianceExtendedModuleUML.png b/doc/schema/sol2uml/ERC3643ComplianceExtendedModuleUML.png new file mode 100644 index 0000000..cf0332f Binary files /dev/null and b/doc/schema/sol2uml/ERC3643ComplianceExtendedModuleUML.png differ diff --git a/doc/schema/sol2uml/ERC3643ComplianceModuleUML.png b/doc/schema/sol2uml/ERC3643ComplianceModuleUML.png new file mode 100644 index 0000000..011ebf5 Binary files /dev/null and b/doc/schema/sol2uml/ERC3643ComplianceModuleUML.png differ diff --git a/doc/schema/sol2uml/IERC1404ExtendUML.png b/doc/schema/sol2uml/IERC1404ExtendUML.png new file mode 100644 index 0000000..54e9045 Binary files /dev/null and b/doc/schema/sol2uml/IERC1404ExtendUML.png differ diff --git a/doc/schema/sol2uml/IERC1404UML.png b/doc/schema/sol2uml/IERC1404UML.png new file mode 100644 index 0000000..9f089d8 Binary files /dev/null and b/doc/schema/sol2uml/IERC1404UML.png differ diff --git a/doc/schema/sol2uml/IERC3643ComplianceReadUML.png b/doc/schema/sol2uml/IERC3643ComplianceReadUML.png new file mode 100644 index 0000000..0454980 Binary files /dev/null and b/doc/schema/sol2uml/IERC3643ComplianceReadUML.png differ diff --git a/doc/schema/sol2uml/IERC3643IComplianceContractUML.png b/doc/schema/sol2uml/IERC3643IComplianceContractUML.png new file mode 100644 index 0000000..0acb763 Binary files /dev/null and b/doc/schema/sol2uml/IERC3643IComplianceContractUML.png differ diff --git a/doc/schema/sol2uml/IERC7551ComplianceUML.png b/doc/schema/sol2uml/IERC7551ComplianceUML.png new file mode 100644 index 0000000..4f82a45 Binary files /dev/null and b/doc/schema/sol2uml/IERC7551ComplianceUML.png differ diff --git a/doc/schema/sol2uml/IRuleEngineUML.png b/doc/schema/sol2uml/IRuleEngineUML.png new file mode 100644 index 0000000..756ffe6 Binary files /dev/null and b/doc/schema/sol2uml/IRuleEngineUML.png differ diff --git a/doc/schema/sol2uml/RuleEngineBaseUML.png b/doc/schema/sol2uml/RuleEngineBaseUML.png new file mode 100644 index 0000000..d90b8bf Binary files /dev/null and b/doc/schema/sol2uml/RuleEngineBaseUML.png differ diff --git a/doc/schema/sol2uml/RuleEngineOwnable2StepUML.png b/doc/schema/sol2uml/RuleEngineOwnable2StepUML.png new file mode 100644 index 0000000..21094aa Binary files /dev/null and b/doc/schema/sol2uml/RuleEngineOwnable2StepUML.png differ diff --git a/doc/schema/sol2uml/RuleEngineOwnableUML.png b/doc/schema/sol2uml/RuleEngineOwnableUML.png new file mode 100644 index 0000000..c7edef0 Binary files /dev/null and b/doc/schema/sol2uml/RuleEngineOwnableUML.png differ diff --git a/doc/schema/sol2uml/RuleEngineUML.png b/doc/schema/sol2uml/RuleEngineUML.png new file mode 100644 index 0000000..79c497e Binary files /dev/null and b/doc/schema/sol2uml/RuleEngineUML.png differ diff --git a/doc/schema/sol2uml/RuleManagementModuleUML.png b/doc/schema/sol2uml/RuleManagementModuleUML.png new file mode 100644 index 0000000..1366bcd Binary files /dev/null and b/doc/schema/sol2uml/RuleManagementModuleUML.png differ diff --git a/doc/schema/sol2uml/TokenBindingExtendedModuleUML.png b/doc/schema/sol2uml/TokenBindingExtendedModuleUML.png new file mode 100644 index 0000000..f276bd1 Binary files /dev/null and b/doc/schema/sol2uml/TokenBindingExtendedModuleUML.png differ diff --git a/doc/schema/sol2uml/TokenBindingModuleUML.png b/doc/schema/sol2uml/TokenBindingModuleUML.png new file mode 100644 index 0000000..a90404c Binary files /dev/null and b/doc/schema/sol2uml/TokenBindingModuleUML.png differ diff --git a/doc/schema/sol2uml/VersionModuleUML.png b/doc/schema/sol2uml/VersionModuleUML.png new file mode 100644 index 0000000..295e778 Binary files /dev/null and b/doc/schema/sol2uml/VersionModuleUML.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceExtendedModule.sol.png b/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceExtendedModule.sol.png index 58d7d23..5258af7 100644 Binary files a/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceExtendedModule.sol.png and b/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceExtendedModule.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceModule.sol.png b/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceModule.sol.png index c044050..44712fe 100644 Binary files a/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceModule.sol.png and b/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceModule.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_IERC3643Compliance.sol.png b/doc/schema/surya/surya_graph/surya_graph_IERC3643Compliance.sol.png index c2d15d2..2a4a86d 100644 Binary files a/doc/schema/surya/surya_graph/surya_graph_IERC3643Compliance.sol.png and b/doc/schema/surya/surya_graph/surya_graph_IERC3643Compliance.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_IERC3643ComplianceExtended.sol.png b/doc/schema/surya/surya_graph/surya_graph_IERC3643ComplianceExtended.sol.png index 300576b..5b4fa6b 100644 Binary files a/doc/schema/surya/surya_graph/surya_graph_IERC3643ComplianceExtended.sol.png and b/doc/schema/surya/surya_graph/surya_graph_IERC3643ComplianceExtended.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_ITokenBinding.sol.png b/doc/schema/surya/surya_graph/surya_graph_ITokenBinding.sol.png new file mode 100644 index 0000000..00d1b1f Binary files /dev/null and b/doc/schema/surya/surya_graph/surya_graph_ITokenBinding.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_ITokenBindingExtended.sol.png b/doc/schema/surya/surya_graph/surya_graph_ITokenBindingExtended.sol.png new file mode 100644 index 0000000..3c19609 Binary files /dev/null and b/doc/schema/surya/surya_graph/surya_graph_ITokenBindingExtended.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_TokenBindingExtendedModule.sol.png b/doc/schema/surya/surya_graph/surya_graph_TokenBindingExtendedModule.sol.png new file mode 100644 index 0000000..c40b68a Binary files /dev/null and b/doc/schema/surya/surya_graph/surya_graph_TokenBindingExtendedModule.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_TokenBindingModule.sol.png b/doc/schema/surya/surya_graph/surya_graph_TokenBindingModule.sol.png new file mode 100644 index 0000000..c78e3e5 Binary files /dev/null and b/doc/schema/surya/surya_graph/surya_graph_TokenBindingModule.sol.png differ diff --git a/doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceModuleInvariantStorage.sol.png b/doc/schema/surya/surya_graph/surya_graph_TokenBindingModuleInvariantStorage.sol.png similarity index 100% rename from doc/schema/surya/surya_graph/surya_graph_ERC3643ComplianceModuleInvariantStorage.sol.png rename to doc/schema/surya/surya_graph/surya_graph_TokenBindingModuleInvariantStorage.sol.png diff --git a/doc/schema/surya/surya_graph/surya_graph_TokenBindingStandaloneMock.sol.png b/doc/schema/surya/surya_graph/surya_graph_TokenBindingStandaloneMock.sol.png new file mode 100644 index 0000000..cfb5e80 Binary files /dev/null and b/doc/schema/surya/surya_graph/surya_graph_TokenBindingStandaloneMock.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceExtendedModule.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceExtendedModule.sol.png index 6207848..281bdb7 100644 Binary files a/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceExtendedModule.sol.png and b/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceExtendedModule.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModule.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModule.sol.png index ca44e14..f3336c5 100644 Binary files a/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModule.sol.png and b/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModule.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModuleInvariantStorage.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModuleInvariantStorage.sol.png deleted file mode 100644 index c1e34ba..0000000 Binary files a/doc/schema/surya/surya_inheritance/surya_inheritance_ERC3643ComplianceModuleInvariantStorage.sol.png and /dev/null differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643Compliance.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643Compliance.sol.png index 33880da..f8bd342 100644 Binary files a/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643Compliance.sol.png and b/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643Compliance.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643ComplianceExtended.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643ComplianceExtended.sol.png index 8c03295..ad79d97 100644 Binary files a/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643ComplianceExtended.sol.png and b/doc/schema/surya/surya_inheritance/surya_inheritance_IERC3643ComplianceExtended.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_ITokenBinding.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_ITokenBinding.sol.png new file mode 100644 index 0000000..f9d8287 Binary files /dev/null and b/doc/schema/surya/surya_inheritance/surya_inheritance_ITokenBinding.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_ITokenBindingExtended.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_ITokenBindingExtended.sol.png new file mode 100644 index 0000000..30ea103 Binary files /dev/null and b/doc/schema/surya/surya_inheritance/surya_inheritance_ITokenBindingExtended.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingExtendedModule.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingExtendedModule.sol.png new file mode 100644 index 0000000..5816c88 Binary files /dev/null and b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingExtendedModule.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingModule.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingModule.sol.png new file mode 100644 index 0000000..45900c2 Binary files /dev/null and b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingModule.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingModuleInvariantStorage.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingModuleInvariantStorage.sol.png new file mode 100644 index 0000000..b5a88c1 Binary files /dev/null and b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingModuleInvariantStorage.sol.png differ diff --git a/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingStandaloneMock.sol.png b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingStandaloneMock.sol.png new file mode 100644 index 0000000..bf95034 Binary files /dev/null and b/doc/schema/surya/surya_inheritance/surya_inheritance_TokenBindingStandaloneMock.sol.png differ diff --git a/doc/schema/surya/surya_report/surya_report_ComplianceInterfaceId.sol.md b/doc/schema/surya/surya_report/surya_report_ComplianceInterfaceId.sol.md index e7cb630..2ad1c8c 100644 --- a/doc/schema/surya/surya_report/surya_report_ComplianceInterfaceId.sol.md +++ b/doc/schema/surya/surya_report/surya_report_ComplianceInterfaceId.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/library/ComplianceInterfaceId.sol | 11d4725317d16e41444556546ee0f3c584b4d1f2 | +| ./modules/library/ComplianceInterfaceId.sol | 1d2e44d2adb90df418028c279d4c2c52b49d4219 | ### Contracts Description Table diff --git a/doc/schema/surya/surya_report/surya_report_ERC1404InterfaceId.sol.md b/doc/schema/surya/surya_report/surya_report_ERC1404InterfaceId.sol.md index 91af8aa..58f389e 100644 --- a/doc/schema/surya/surya_report/surya_report_ERC1404InterfaceId.sol.md +++ b/doc/schema/surya/surya_report/surya_report_ERC1404InterfaceId.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/library/ERC1404InterfaceId.sol | 40ffb6676f92b7f9941e9b5a57e9c37fa94b10ff | +| ./modules/library/ERC1404InterfaceId.sol | a33a85519a39a7dc75bd9ed7bcb88a51ec6dd4b5 | ### Contracts Description Table diff --git a/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceExtendedModule.sol.md b/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceExtendedModule.sol.md index 5c670c1..142b322 100644 --- a/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceExtendedModule.sol.md +++ b/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceExtendedModule.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/ERC3643ComplianceExtendedModule.sol | b724099045f572c919a690c34d113af3c0855ffc | +| ./modules/ERC3643ComplianceExtendedModule.sol | 889c64f0f42969f3ddbf09e19b350058bc8cc955 | ### Contracts Description Table @@ -15,14 +15,8 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **ERC3643ComplianceExtendedModule** | Implementation | ERC3643ComplianceModule, IERC3643ComplianceExtended ||| -| └ | bindTokens | Public ❗️ | 🛑 | onlyComplianceManager | -| └ | unbindTokens | Public ❗️ | 🛑 | onlyComplianceManager | -| └ | setTokenSelfBindingApproval | Public ❗️ | 🛑 | onlyComplianceManager | -| └ | setTokenSelfBindingApprovalBatch | Public ❗️ | 🛑 | onlyComplianceManager | -| └ | isTokenSelfBindingApproved | Public ❗️ | |NO❗️ | -| └ | getTokenBounds | Public ❗️ | |NO❗️ | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | 🛑 | | +| **ERC3643ComplianceExtendedModule** | Implementation | TokenBindingExtendedModule, ERC3643ComplianceModule, IERC3643ComplianceExtended ||| +| └ | _authorizeTokenBindingChange | Internal 🔒 | 🛑 | | ### Legend diff --git a/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModule.sol.md b/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModule.sol.md index 9566c30..484eeba 100644 --- a/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModule.sol.md +++ b/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModule.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/ERC3643ComplianceModule.sol | 20481b94eee7c734ad6866811f4708824db63461 | +| ./modules/ERC3643ComplianceModule.sol | 85c740155a17502a2f306f82eaa7335e186dc40a | ### Contracts Description Table @@ -15,16 +15,10 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **ERC3643ComplianceModule** | Implementation | Context, IERC3643Compliance, ERC3643ComplianceModuleInvariantStorage ||| -| └ | bindToken | Public ❗️ | 🛑 |NO❗️ | -| └ | unbindToken | Public ❗️ | 🛑 |NO❗️ | -| └ | isTokenBound | Public ❗️ | |NO❗️ | +| **ERC3643ComplianceModule** | Implementation | TokenBindingModule, IERC3643Compliance ||| | └ | getTokenBound | Public ❗️ | |NO❗️ | -| └ | _unbindToken | Internal 🔒 | 🛑 | | -| └ | _bindToken | Internal 🔒 | 🛑 | | -| └ | _authorizeComplianceBindingChange | Internal 🔒 | 🛑 | | +| └ | _onlyTokenBindingManager | Internal 🔒 | 🛑 | | | └ | _onlyComplianceManager | Internal 🔒 | 🛑 | | -| └ | _checkBoundToken | Internal 🔒 | | | ### Legend diff --git a/doc/schema/surya/surya_report/surya_report_IERC3643Compliance.sol.md b/doc/schema/surya/surya_report/surya_report_IERC3643Compliance.sol.md index 078200d..83bcd80 100644 --- a/doc/schema/surya/surya_report/surya_report_IERC3643Compliance.sol.md +++ b/doc/schema/surya/surya_report/surya_report_IERC3643Compliance.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./interfaces/IERC3643Compliance.sol | 10b359e1a7dacc574ca3f3b1b4924a55ca25447a | +| ./interfaces/IERC3643Compliance.sol | c7bae1e0e5bd1e565e9c2fb3177577618f0cb9b6 | ### Contracts Description Table @@ -15,12 +15,9 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **IERC3643Compliance** | Interface | IERC3643ComplianceRead, IERC3643IComplianceContract ||| -| └ | bindToken | External ❗️ | 🛑 |NO❗️ | -| └ | unbindToken | External ❗️ | 🛑 |NO❗️ | +| **IERC3643Compliance** | Interface | IERC3643ComplianceRead, IERC3643IComplianceContract, ITokenBinding ||| | └ | created | External ❗️ | 🛑 |NO❗️ | | └ | destroyed | External ❗️ | 🛑 |NO❗️ | -| └ | isTokenBound | External ❗️ | |NO❗️ | | └ | getTokenBound | External ❗️ | |NO❗️ | diff --git a/doc/schema/surya/surya_report/surya_report_IERC3643ComplianceExtended.sol.md b/doc/schema/surya/surya_report/surya_report_IERC3643ComplianceExtended.sol.md index 4be31ca..42633cc 100644 --- a/doc/schema/surya/surya_report/surya_report_IERC3643ComplianceExtended.sol.md +++ b/doc/schema/surya/surya_report/surya_report_IERC3643ComplianceExtended.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./interfaces/IERC3643ComplianceExtended.sol | 9965d8909904d46c4574224c634fa2526b803f3e | +| ./interfaces/IERC3643ComplianceExtended.sol | a7e8ff1a670e67b5a8704ffb8f35fba446502c11 | ### Contracts Description Table @@ -15,13 +15,7 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **IERC3643ComplianceExtended** | Interface | IERC3643Compliance ||| -| └ | bindTokens | External ❗️ | 🛑 |NO❗️ | -| └ | unbindTokens | External ❗️ | 🛑 |NO❗️ | -| └ | setTokenSelfBindingApproval | External ❗️ | 🛑 |NO❗️ | -| └ | setTokenSelfBindingApprovalBatch | External ❗️ | 🛑 |NO❗️ | -| └ | isTokenSelfBindingApproved | External ❗️ | |NO❗️ | -| └ | getTokenBounds | External ❗️ | |NO❗️ | +| **IERC3643ComplianceExtended** | Interface | IERC3643Compliance, ITokenBindingExtended ||| ### Legend diff --git a/doc/schema/surya/surya_report/surya_report_ITokenBinding.sol.md b/doc/schema/surya/surya_report/surya_report_ITokenBinding.sol.md new file mode 100644 index 0000000..2fd66d1 --- /dev/null +++ b/doc/schema/surya/surya_report/surya_report_ITokenBinding.sol.md @@ -0,0 +1,29 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./interfaces/ITokenBinding.sol | 3517eb93c9b988780a306eee58afeeaae1097505 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **ITokenBinding** | Interface | ||| +| └ | bindToken | External ❗️ | 🛑 |NO❗️ | +| └ | unbindToken | External ❗️ | 🛑 |NO❗️ | +| └ | isTokenBound | External ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/schema/surya/surya_report/surya_report_ITokenBindingExtended.sol.md b/doc/schema/surya/surya_report/surya_report_ITokenBindingExtended.sol.md new file mode 100644 index 0000000..3d80d9c --- /dev/null +++ b/doc/schema/surya/surya_report/surya_report_ITokenBindingExtended.sol.md @@ -0,0 +1,32 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./interfaces/ITokenBindingExtended.sol | 07d4c02721bfac635a599088a2966730295d2570 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **ITokenBindingExtended** | Interface | ITokenBinding ||| +| └ | bindTokens | External ❗️ | 🛑 |NO❗️ | +| └ | unbindTokens | External ❗️ | 🛑 |NO❗️ | +| └ | setTokenSelfBindingApproval | External ❗️ | 🛑 |NO❗️ | +| └ | setTokenSelfBindingApprovalBatch | External ❗️ | 🛑 |NO❗️ | +| └ | isTokenSelfBindingApproved | External ❗️ | |NO❗️ | +| └ | getTokenBounds | External ❗️ | |NO❗️ | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/schema/surya/surya_report/surya_report_RuleInterfaceId.sol.md b/doc/schema/surya/surya_report/surya_report_RuleInterfaceId.sol.md index 49096cf..d410ddf 100644 --- a/doc/schema/surya/surya_report/surya_report_RuleInterfaceId.sol.md +++ b/doc/schema/surya/surya_report/surya_report_RuleInterfaceId.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/library/RuleInterfaceId.sol | 9290257eac1724325368150b93b9dabc44babafd | +| ./modules/library/RuleInterfaceId.sol | 65c302818900904547afb065eb4fb5fb6a13d5af | ### Contracts Description Table diff --git a/doc/schema/surya/surya_report/surya_report_TokenBindingExtendedModule.sol.md b/doc/schema/surya/surya_report/surya_report_TokenBindingExtendedModule.sol.md new file mode 100644 index 0000000..f8385a1 --- /dev/null +++ b/doc/schema/surya/surya_report/surya_report_TokenBindingExtendedModule.sol.md @@ -0,0 +1,33 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./modules/TokenBindingExtendedModule.sol | d04d9b119b3a34f264d56830a6f41e57d9bc11de | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **TokenBindingExtendedModule** | Implementation | TokenBindingModule, ITokenBindingExtended ||| +| └ | bindTokens | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | unbindTokens | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | setTokenSelfBindingApproval | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | setTokenSelfBindingApprovalBatch | Public ❗️ | 🛑 | onlyTokenBindingManager | +| └ | isTokenSelfBindingApproved | Public ❗️ | |NO❗️ | +| └ | getTokenBounds | Public ❗️ | |NO❗️ | +| └ | _authorizeTokenBindingChange | Internal 🔒 | 🛑 | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/schema/surya/surya_report/surya_report_TokenBindingModule.sol.md b/doc/schema/surya/surya_report/surya_report_TokenBindingModule.sol.md new file mode 100644 index 0000000..5745f0a --- /dev/null +++ b/doc/schema/surya/surya_report/surya_report_TokenBindingModule.sol.md @@ -0,0 +1,34 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./modules/TokenBindingModule.sol | db8a2e23dc811ccc424861b398b305fb425b3e6c | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **TokenBindingModule** | Implementation | Context, ITokenBinding, TokenBindingModuleInvariantStorage ||| +| └ | bindToken | Public ❗️ | 🛑 |NO❗️ | +| └ | unbindToken | Public ❗️ | 🛑 |NO❗️ | +| └ | isTokenBound | Public ❗️ | |NO❗️ | +| └ | _bindToken | Internal 🔒 | 🛑 | | +| └ | _unbindToken | Internal 🔒 | 🛑 | | +| └ | _authorizeTokenBindingChange | Internal 🔒 | 🛑 | | +| └ | _onlyTokenBindingManager | Internal 🔒 | 🛑 | | +| └ | _checkBoundToken | Internal 🔒 | | | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModuleInvariantStorage.sol.md b/doc/schema/surya/surya_report/surya_report_TokenBindingModuleInvariantStorage.sol.md similarity index 76% rename from doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModuleInvariantStorage.sol.md rename to doc/schema/surya/surya_report/surya_report_TokenBindingModuleInvariantStorage.sol.md index 6a66dee..1688fd9 100644 --- a/doc/schema/surya/surya_report/surya_report_ERC3643ComplianceModuleInvariantStorage.sol.md +++ b/doc/schema/surya/surya_report/surya_report_TokenBindingModuleInvariantStorage.sol.md @@ -5,7 +5,7 @@ | File Name | SHA-1 Hash | |-------------|--------------| -| ./modules/library/ERC3643ComplianceModuleInvariantStorage.sol | 8ba2ee79d1f96d691db5ac2bf939b8a93b715195 | +| ./modules/library/TokenBindingModuleInvariantStorage.sol | c1ae50c8c8acc84cdf06c1ef436e4d1bf03b554b | ### Contracts Description Table @@ -15,7 +15,7 @@ |:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| | └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | |||||| -| **ERC3643ComplianceModuleInvariantStorage** | Implementation | ||| +| **TokenBindingModuleInvariantStorage** | Implementation | ||| ### Legend diff --git a/doc/schema/surya/surya_report/surya_report_TokenBindingStandaloneMock.sol.md b/doc/schema/surya/surya_report/surya_report_TokenBindingStandaloneMock.sol.md new file mode 100644 index 0000000..d293e28 --- /dev/null +++ b/doc/schema/surya/surya_report/surya_report_TokenBindingStandaloneMock.sol.md @@ -0,0 +1,29 @@ +## Sūrya's Description Report + +### Files Description Table + + +| File Name | SHA-1 Hash | +|-------------|--------------| +| ./mocks/TokenBindingStandaloneMock.sol | 69481db2ed47a2c69e7263f690fc92c9aa442070 | + + +### Contracts Description Table + + +| Contract | Type | Bases | | | +|:----------:|:-------------------:|:----------------:|:----------------:|:---------------:| +| └ | **Function Name** | **Visibility** | **Mutability** | **Modifiers** | +|||||| +| **TokenBindingStandaloneMock** | Implementation | TokenBindingModule, Ownable ||| +| └ | | Public ❗️ | 🛑 | Ownable | +| └ | notify | Public ❗️ | 🛑 | onlyBoundToken | +| └ | _onlyTokenBindingManager | Internal 🔒 | 🛑 | onlyOwner | + + +### Legend + +| Symbol | Meaning | +|:--------:|-----------| +| 🛑 | Function can modify state | +| 💵 | Function is payable | diff --git a/doc/schema/vscode-uml/ERC3643ComplianceModuleUML.png b/doc/schema/vscode-uml/ERC3643ComplianceModuleUML.png deleted file mode 100644 index 97b1741..0000000 Binary files a/doc/schema/vscode-uml/ERC3643ComplianceModuleUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/IERC1404ExtendUML.png b/doc/schema/vscode-uml/IERC1404ExtendUML.png deleted file mode 100644 index ff2570c..0000000 Binary files a/doc/schema/vscode-uml/IERC1404ExtendUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/IERC1404UML.png b/doc/schema/vscode-uml/IERC1404UML.png deleted file mode 100644 index aec3f3b..0000000 Binary files a/doc/schema/vscode-uml/IERC1404UML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/IERC3643ComplianceReadUML.png b/doc/schema/vscode-uml/IERC3643ComplianceReadUML.png deleted file mode 100644 index d8d9f49..0000000 Binary files a/doc/schema/vscode-uml/IERC3643ComplianceReadUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/IERC3643IComplianceContractUML.png b/doc/schema/vscode-uml/IERC3643IComplianceContractUML.png deleted file mode 100644 index 8eb9993..0000000 Binary files a/doc/schema/vscode-uml/IERC3643IComplianceContractUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/IERC7551ComplianceUML.png b/doc/schema/vscode-uml/IERC7551ComplianceUML.png deleted file mode 100644 index cb4d836..0000000 Binary files a/doc/schema/vscode-uml/IERC7551ComplianceUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/IRuleEngineUML.png b/doc/schema/vscode-uml/IRuleEngineUML.png deleted file mode 100644 index a72713e..0000000 Binary files a/doc/schema/vscode-uml/IRuleEngineUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/RuleEngineBaseUML.png b/doc/schema/vscode-uml/RuleEngineBaseUML.png deleted file mode 100644 index cd577ee..0000000 Binary files a/doc/schema/vscode-uml/RuleEngineBaseUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/RuleEngineOwnable2StepUML.png b/doc/schema/vscode-uml/RuleEngineOwnable2StepUML.png deleted file mode 100644 index 8dd3a50..0000000 Binary files a/doc/schema/vscode-uml/RuleEngineOwnable2StepUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/RuleEngineOwnableUML.png b/doc/schema/vscode-uml/RuleEngineOwnableUML.png deleted file mode 100644 index 0b36f8e..0000000 Binary files a/doc/schema/vscode-uml/RuleEngineOwnableUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/RuleEngineUML.png b/doc/schema/vscode-uml/RuleEngineUML.png deleted file mode 100644 index 1595b1f..0000000 Binary files a/doc/schema/vscode-uml/RuleEngineUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/RuleManagementModuleUML.png b/doc/schema/vscode-uml/RuleManagementModuleUML.png deleted file mode 100644 index e010041..0000000 Binary files a/doc/schema/vscode-uml/RuleManagementModuleUML.png and /dev/null differ diff --git a/doc/schema/vscode-uml/VersionModuleUML.png b/doc/schema/vscode-uml/VersionModuleUML.png deleted file mode 100644 index b035d88..0000000 Binary files a/doc/schema/vscode-uml/VersionModuleUML.png and /dev/null differ diff --git a/doc/script/script_sol2uml.sh b/doc/script/script_sol2uml.sh new file mode 100755 index 0000000..cf3f924 --- /dev/null +++ b/doc/script/script_sol2uml.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Generate the class diagrams referenced by doc/README.md with sol2uml. +# Output: doc/schema/sol2uml/ (committed, unlike the docOut/ scripts next to this one) +# +# One diagram per contract or interface, filtered with -b/-d 0 so a file holding several +# interfaces yields one image each. Paths stay relative to the repository root: sol2uml prints +# the path it is given under the class name, so an absolute one would write machine-specific +# paths into the committed images. +set -euo pipefail + +cd "$(dirname "$0")/../../" +DIR_OUT="doc/schema/sol2uml" +mkdir -p "$DIR_OUT" + +# "||" +DIAGRAMS=( + "RuleEngineUML|src/deployment/RuleEngine.sol|RuleEngine" + "RuleEngineOwnableUML|src/deployment/RuleEngineOwnable.sol|RuleEngineOwnable" + "RuleEngineOwnable2StepUML|src/deployment/RuleEngineOwnable2Step.sol|RuleEngineOwnable2Step" + "RuleEngineBaseUML|src/RuleEngineBase.sol|RuleEngineBase" + "VersionModuleUML|src/modules/VersionModule.sol|VersionModule" + "RuleManagementModuleUML|src/modules/RulesManagementModule.sol|RulesManagementModule" + "TokenBindingModuleUML|src/modules/TokenBindingModule.sol|TokenBindingModule" + "TokenBindingExtendedModuleUML|src/modules/TokenBindingExtendedModule.sol|TokenBindingExtendedModule" + "ERC3643ComplianceModuleUML|src/modules/ERC3643ComplianceModule.sol|ERC3643ComplianceModule" + "ERC3643ComplianceExtendedModuleUML|src/modules/ERC3643ComplianceExtendedModule.sol|ERC3643ComplianceExtendedModule" + "IRuleEngineUML|lib/CMTAT/contracts/interfaces/engine/IRuleEngine.sol|IRuleEngine" + "IERC1404UML|lib/CMTAT/contracts/interfaces/tokenization/draft-IERC1404.sol|IERC1404" + "IERC1404ExtendUML|lib/CMTAT/contracts/interfaces/tokenization/draft-IERC1404.sol|IERC1404Extend" + "IERC7551ComplianceUML|lib/CMTAT/contracts/interfaces/tokenization/draft-IERC7551.sol|IERC7551Compliance" + "IERC3643ComplianceReadUML|lib/CMTAT/contracts/interfaces/tokenization/IERC3643Partial.sol|IERC3643ComplianceRead" + "IERC3643IComplianceContractUML|lib/CMTAT/contracts/interfaces/tokenization/IERC3643Partial.sol|IERC3643IComplianceContract" +) + +for entry in "${DIAGRAMS[@]}"; do + IFS='|' read -r image source contract <<< "$entry" + if [ ! -f "$source" ]; then + echo "Missing source: $source (submodule not initialized?)" >&2 + exit 1 + fi + npx sol2uml class "$source" -b "$contract" -d 0 -f png -o "${DIR_OUT}/${image}.png" +done diff --git a/doc/security/audits/AUDIT_OVERVIEW.md b/doc/security/audits/AUDIT_OVERVIEW.md index ad02f28..dfb7cfa 100644 --- a/doc/security/audits/AUDIT_OVERVIEW.md +++ b/doc/security/audits/AUDIT_OVERVIEW.md @@ -7,7 +7,7 @@ This is an *overview of analyses*. For vulnerability reporting, see | | | |---|---| -| **Current version** | v3.0.0-rc5 | +| **Current version** | v3.0.0-rc6 | | **Compiler** | solc 0.8.36, EVM Prague, optimizer on (200 runs) | | **Audited?** | **No.** v1.0.2 was audited by ABDK in March 2022; the 3.0.0 line has not been audited. | @@ -25,6 +25,9 @@ concern a mock are labelled as such and do not apply to production deployments. | Analysis | Version | Report | Assessment | |---|---|---|---| +| Slither | v3.0.0-rc6 | [slither-report.md](./tools/v3.0.0-rc6/slither-report.md) | [feedback](./tools/v3.0.0-rc6/slither-report-feedback.md) | +| Aderyn | v3.0.0-rc6 | [aderyn-report.md](./tools/v3.0.0-rc6/aderyn-report.md) | [feedback](./tools/v3.0.0-rc6/aderyn-report-feedback.md) | +| Code-quality review | v3.0.0-rc6 | [CLAUDE_ANALYSIS.md](./tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md) | — | | Slither | v3.0.0-rc5 | [slither-report.md](./tools/v3.0.0-rc5/slither-report.md) | [feedback](./tools/v3.0.0-rc5/slither-report-feedback.md) | | Aderyn | v3.0.0-rc5 | [aderyn-report.md](./tools/v3.0.0-rc5/aderyn-report.md) | [feedback](./tools/v3.0.0-rc5/aderyn-report-feedback.md) | | Code-quality review | v3.0.0-rc5 | [CLAUDE_ANALYSIS.md](./tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md) | — | @@ -34,22 +37,24 @@ concern a mock are labelled as such and do not apply to production deployments. | Nethermind AuditAgent | v3.0.0-rc1 | [report](./tools/nethermind-audit-agent/v3.0.0-rc1/audit_agent_report_1_v3.0.0-rc1.pdf) | [feedback](./tools/nethermind-audit-agent/v3.0.0-rc1/audit_agent_report_1_v3.0.0-rc1-feedback.md) | | ABDK (external audit) | v1.0.1 -> v1.0.2 | [ABDK report (CMTAT repo)](https://github.com/CMTA/CMTAT/blob/master/doc/audits/ABDK_CMTA_CMTATRuleEngine_v_1_0/ABDK_CMTA_CMTATRuleEngine_v_1_0.pdf) | — | -## Static analysis results — v3.0.0-rc5 +## Static analysis results — v3.0.0-rc6 | Tool | High | Medium | Low | Info | Relevant to fix? | |---|---|---|---|---|---| | Slither 0.11.5 | 0 | 0 | 10 | 2 | **No** | -| Aderyn 0.6.5 | 0 | — | 8 (76 instances) | — | **No** | +| Aderyn 0.6.5 | 0 | — | 8 (84 instances) | — | **No** | Every finding is by design, cosmetic, or a verified false positive. Highlights: - **`calls-loop` (Slither, 10)** — the engine iterating its rule set is the product. Bounded on-chain by `maxRules` (default 10). -- **`unindexed-event-address` (Slither, 2)** — `TokenBound` / `TokenUnbound` match the ERC-3643 reference - interface, which declares them unindexed. Conformance, not an oversight. +- **`unindexed-event-address` (Slither, 2)** — `TokenBound` / `TokenUnbound`, now declared in `ITokenBinding`, + match the ERC-3643 reference interface, which declares them unindexed. Conformance, not an oversight. - **`L-8 Unchecked Return` (Aderyn, 1)** — `_grantRole` in a constructor cannot return `false`. False positive. -No change in counts from v3.0.0-rc4 for either tool. +Slither: no change in counts from v3.0.0-rc5. Aderyn: same 8 findings, 76 -> 84 instances, entirely from the +file count — the token binding split added a net four files, and the two per-file detectors (`L-2` pragma, +`L-3` PUSH0) each moved by exactly four. No new finding came from the refactor. ## Substantive findings that were fixed diff --git a/doc/security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md b/doc/security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md new file mode 100644 index 0000000..176e258 --- /dev/null +++ b/doc/security/audits/tools/v3.0.0-rc6/CLAUDE_ANALYSIS.md @@ -0,0 +1,465 @@ +# RuleEngine — Code Quality Review (v3.0.0-rc6) + +| | | +|---|---| +| **Scope** | `src/` (52 Solidity files) and `script/` (2 files) | +| **Base commit** | `dc7a4d3` + the rc6 working tree | +| **Compiler** | solc 0.8.36, `evm_version = prague`, optimizer on, 200 runs | +| **Date** | 2026-08-20 | +| **Produced with** | Claude Code | + +## This is not a security audit + +Nothing in this report is a vulnerability. No finding below lets an unauthorized party move value, bypass a +transfer restriction, or brick a contract. This review targets the rc6 change — the separation of token +binding from the ERC-3643 compliance code — and re-checks the findings carried over from rc5. + +Every gas number was measured with a benchmark harness, not derived from opcode costs. Each variant sat in its +own contract with an identically-named function so selector-dispatch depth could not skew the comparison, and +both were measured after an identical warm-up. The modularity verdict is a **compile result**, not an opinion: +two probe contracts were written and built. All probe and benchmark files were deleted afterwards; the test +count reconciles exactly: 346 before, 348 after — the two added being the `F-2` interface-id pins, with every +probe and benchmark file deleted. + +## Disposition summary + +| ID | Finding | Outcome | +|----|---------|---------| +| A-1 | Loop increment form and `unchecked` | ⬜ no finding — `++i` on 0.8.36, `unchecked` would buy nothing | +| A-2 | Unbounded iteration over caller-supplied arrays | ⬜ left as is — admin-gated, carried from rc5 `A-3` | +| B-1 | Repeated storage reads in the new binding modules | ⬜ no finding — mutation return values already used | +| C-1 | Every event has exactly one emit site | ⬜ no finding — verified structurally, 8/8 events | +| C-2 | Batch self-binding approval emits input, not per-token effect | ⚠️ still open — carried from rc5, unchanged by the split | +| D-1 | Duplication across the three deployables | ⬜ no finding — reduced by the split, remainder is compiler-mandated | +| E-1 | `virtual` on every internal function | ⬜ no finding — 0 non-`virtual` internals in `src/` | +| F-1 | Advertised ERC-165 ids unchanged by the split | ⬜ verified — `0x3144991c` / `0x646ba2be` both hold | +| F-2 | `type(IERC3643ComplianceExtended).interfaceId` is `0x00000000`, and the ids were hardcoded literals | ✅ fixed — all five ids now computed from the interfaces, values unchanged, pinned by 2 new tests | +| G-1 | NatSpec length and doc pointers in contract comments | ⬜ no finding — median 4 lines, max 19, zero `.md` pointers | +| H-1 | View approves a mint that enforcement rejects | ⬜ documented — carried from rc5, unchanged | +| I-1 | `IRule` demands two functions the engine never calls | ⚠️ decide — stands; the narrowed id would be `0xb1a69752` | +| I-2 | That standalone-rule configuration is advertised by ERC-165 but untested and undocumented | ⚠️ decide — test and document it, or stop advertising it | +| I-3 | Should the engine call a rule's `canTransfer` rather than `detectTransferRestriction`? | ⬜ no — the code is the stronger primitive, and the engine owes the token a code | +| J-1 | Binding registry embeddable in a foreign host | ⬜ verified by compile probe — inconvenience only, no blocker | +| J-2 | ERC-3643 adapter indirection costs 35 gas per bind | ⬜ left as is — measured, negligible | + +**Counted: 16 rows — 1 fixed, 3 left as is, 9 no-finding/verified, 3 open decisions.** One superseded correction +is kept visible under `I-1`. + +## Outstanding + +| ID | Item | Why it is still open | +|----|------|----------------------| +| C-2 | `setTokenSelfBindingApprovalBatch` emits only a batch event | Unchanged since rc5. Fixing it alters the emitted event stream, which is API-visible to indexers. Product call. | +| I-1 | `IRule` requires `canTransfer` / `canTransferFrom` | Narrowing to `0xb1a69752` is a breaking change for the separate [CMTA/Rules](https://github.com/CMTA/Rules) repository: every rule must advertise the new id before an updated engine accepts it. | +| I-2 | A rule attached directly to a token as its engine | Every rule advertises `RULE_ENGINE_INTERFACE_ID`, so the configuration works, but no test exercises it and no document mentions it. Support it deliberately or drop the claim. | +| H-1 | The view/enforcement divergence | Inherent to the ERC-1404 3-argument signature. Documented rather than fixed — see the rc5 report. | + +--- + +## A. Loops and iteration + +### A-1. Increment form — no finding + +Every loop in `src/` uses `++i` and none wraps the counter in `unchecked`. The project compiles with **0.8.36**, +where the overflow check on a bounded loop counter is elided by the compiler since 0.8.22. Recommending +`unchecked { ++i }` here would add noise for zero gas. Recorded so the next review does not re-raise it. + +### A-2. Unbounded iteration over caller-supplied arrays — left as is + +`bindTokens`, `unbindTokens`, `setTokenSelfBindingApprovalBatch` (now in `TokenBindingExtendedModule`) and +`setRules` iterate a caller-supplied array. All four are gated on the binding manager or rules manager, so the +only party who can pass an oversized array is the operator, and the only consequence is their own transaction +running out of gas. Same conclusion as rc5 `A-3`; the split moved three of the four loops without changing them. + +## B. Storage reads + +### B-1. The new binding modules — no finding + +`_bindToken` and `_unbindToken` use the `EnumerableSet` mutation return value rather than a preceding +`contains()`, which is the rc5 `B-1` fix carried into the extracted module: + +```solidity +require(_boundTokens.add(token), TokenBinding_TokenAlreadyBound()); +``` + +`isTokenBound`, `getTokenBound` and `getTokenBounds` each touch the set once. No read is separated from another +by an external call, so there is nothing the optimizer is not already forwarding — hand-caching here would be a +pessimisation. + +## C. Events + +### C-1. One emit site per event — no finding, verified structurally + +The rc5 `C-1` fix introduced `_setMaxRules` so that "every change to the cap emits `SetMaxRules`" holds +structurally rather than by convention. That property now holds for **every** event in `src/`: + +``` +TokenBound: 1 TokenUnbound: 1 TokenSelfBindingApprovalSet: 1 TokenSelfBindingApprovalBatchSet: 1 +AddRule: 1 RemoveRule: 1 ClearRules: 1 SetMaxRules: 1 +``` + +Each event is emitted from exactly one place, and in each case that place is the internal writer the public +functions and the constructors both call. A new write path cannot silently skip the event, because there is no +second way to reach the storage. Worth stating as a positive result: this is the invariant the split had the +most opportunity to break, since it moved four of the eight events into a new file. + +### C-2. Batch self-binding approval reports the input, not the effect — still open + +`TokenBindingExtendedModule.setTokenSelfBindingApprovalBatch` writes each token then emits the whole input +array: + +```solidity +for (uint256 i = 0; i < tokens.length; ++i) { + address token = tokens[i]; + require(token != address(0), TokenBinding_InvalidTokenAddress()); + _tokenSelfBindingApproval[token] = approved; +} +emit TokenSelfBindingApprovalBatchSet(tokens, approved); +``` + +An indexer cannot tell which entries actually changed state, and the single-token setter emits a different event +(`TokenSelfBindingApprovalSet`, indexed) for the same state transition — so a consumer must handle two shapes. +This is rc5 `C-2` verbatim; the split relocated the function without altering it. Still a product call rather +than a code call: emitting per-token would change the event stream that indexers key on. + +## D. Duplication + +### D-1. Reduced by the split — no finding + +The rc6 change removed the main duplication candidate rather than adding one: the binding registry existed once +before and exists once now, with the ERC-3643 layer holding only `getTokenBound()` and one hook. The remaining +duplication is the ERC-2771 context trio across the ownable variants (rc5 `D-1`), which is compiler-mandated: +each contract must name its own bases in the `override(...)` list. + +## E. `virtual` convention + +### E-1. Full coverage — no finding + +`CLAUDE.md` requires every `internal` function to be `virtual` so inheriting contracts can override it. A scan +of `src/` (mocks excluded) returns **zero** internal functions missing the keyword, including the eleven added +by the new modules. The project's style checker reports `src/` and `script/` clean on all six of its checks. + +## F. ERC / specification conformance + +### F-1. The advertised interface ids survived the split — verified + +The split moved `bindToken`, `unbindToken` and `isTokenBound` out of `IERC3643Compliance` into `ITokenBinding`, +which changes `type(IERC3643Compliance).interfaceId` (that expression never counted inherited selectors) while +leaving the union of selectors identical. The advertised constants are computed from flattened helper +interfaces, so they are unaffected — measured, not assumed: + +| Expression | Value | +|---|---| +| `ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID` | `0x3144991c` | +| `type(ICompliance).interfaceId` (flattened helper) | `0x3144991c` | +| `ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID` | `0x646ba2be` | +| `type(IERC3643ComplianceExtendedSubset).interfaceId` (flattened helper) | `0x646ba2be` | +| `type(IERC3643Compliance).interfaceId` (naive) | `0xb89d9289` — changed, and used by nothing | + +An integrator's `supportsInterface` call therefore behaves exactly as in rc5. This is the check that would have +caught the split silently breaking ERC-165 detection, and it passes. + +### F-2. The ids were hardcoded literals, and the marker interface computes to zero — fixed + +Two related observations from the same measurement, and one fix for both. + +**`type(IERC3643ComplianceExtended).interfaceId` is `0x00000000`.** The interface is a pure marker — it declares +no function of its own and only combines `IERC3643Compliance` and `ITokenBindingExtended` — so the XOR over its +*directly declared* selectors is over the empty set. Any integrator reaching for that expression instead of the +advertised constant gets a meaningless id, and `supportsInterface(0x00000000)` returns false. + +**Every project id was a hardcoded literal.** `IRULE_INTERFACE_ID`, the three `ComplianceInterfaceId` constants +and `IERC1404_INTERFACE_ID` were written out by hand, with the correspondence to the actual interfaces held by a +comment and, for two of them, by a test double (`IRuleInterfaceIdHelper`, `IERC3643ComplianceExtendedSubset`) +kept in sync manually. The naive expression could not be used because it never counts inherited selectors — which +is precisely the reason the literals existed. + +**Fix: compute each id from the interfaces, XOR-ing in the parents explicitly.** + +```solidity +// ComplianceInterfaceId.sol +bytes4 public constant ERC3643_COMPLIANCE_INTERFACE_ID = type(IERC3643Compliance).interfaceId + ^ type(ITokenBinding).interfaceId ^ type(IERC3643ComplianceRead).interfaceId + ^ type(IERC3643IComplianceContract).interfaceId; // 0x3144991c + +bytes4 public constant ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID = type(ITokenBindingExtended).interfaceId; + // 0x646ba2be +bytes4 public constant IERC7551_COMPLIANCE_INTERFACE_ID = type(IERC7551Compliance).interfaceId; + // 0x7157797f +// RuleInterfaceId.sol — eight parents, IERC165 included +bytes4 public constant IRULE_INTERFACE_ID = type(IRule).interfaceId ^ type(IRuleEngine).interfaceId + ^ type(IERC7551Compliance).interfaceId ^ type(IERC3643ComplianceRead).interfaceId + ^ type(IERC3643IComplianceContract).interfaceId ^ type(IERC1404).interfaceId + ^ type(IERC1404Extend).interfaceId ^ type(IERC165).interfaceId; // 0x2497d6cb + +// ERC1404InterfaceId.sol +bytes4 public constant IERC1404_INTERFACE_ID = type(IERC1404).interfaceId; // 0xab84a5c8 +``` + +**Every value is unchanged**, which is the property that matters: these constants are advertised by deployed +contracts, so a different number would silently break `supportsInterface` for existing integrators. Two details +were easy to get wrong, and are recorded here for the next person to touch these constants: + +- The rule id needs **`IERC165`**. Without it the expression computes `0x25681f6c`; `supportsInterface` is part + of the flattened hierarchy because `IRuleEngine` inherits `IERC165`. +- `IRuleEngineERC1404` contributes **nothing** — it declares no function, its own id is `0x00000000`, and its + parents are XOR-ed in individually. The same marker-interface property that made the extended compliance id a + trap. + +The extended compliance id is now derived from `ITokenBindingExtended`, which declares the six extended +functions in full, so its own id *is* the flattened one — a consequence of the rc6 split that did not exist +before it. + +**Verification.** Two tests were added to `IRuleInterfaceId.t.sol`: +`testInterfaceIdConstantsMatchTheirWireValues` pins all five constants to their literal values, so an upstream +CMTAT interface change fails a test rather than silently altering what the engine advertises; and +`testMarkerInterfaceHasZeroNaiveIdAndIsNotUsedAsSuch` pins `type(IERC3643ComplianceExtended).interfaceId == 0` +together with the fact that the constant comes from `ITokenBindingExtended` instead. The existing +`supportsInterface` suites and the `ICompliance` / `IERC3643ComplianceExtendedSubset` flattened helpers still +pass unchanged, giving a second, independent check on the values. 346 -> 348 tests. + +`IERC3643ComplianceExtended` also gained a NatSpec `WARNING:` stating that its naive id is `0x00000000` and +naming the constant to use instead — the part of this finding that a reader of the verified source needs. + +**Not converted:** `OwnableInterfaceId` (ERC-173, `0x7f5828d0`) and `Ownable2StepInterfaceId` (`0x9ab669ef`). +Neither has an interface declaration in scope — OpenZeppelin ships `Ownable` as a contract, and the +`Ownable2Step` subset is this project's own selection of two functions. The only declarations available are +test doubles under `src/mocks/`, and importing a mock into `src/` to compute a production constant would be +worse than the literal. Both keep their derivation in NatSpec. + +## G. Code / documentation mismatch + +### G-1. NatSpec proportion and doc pointers — no finding + +Two mechanical checks, both clean: + +- **Block length.** 153 NatSpec blocks in `src/` (mocks excluded): median **4** lines, 90th percentile **9**, + maximum **19**. There is no block of 20 lines or more, so the long-tail pattern that makes contract headers + unreadable is absent — including in the five files added this release. +- **Documentation pointers.** Zero occurrences of `.md`, `doc/technical` or `docs/` in `src/` comments. No + NatSpec block delegates its substance to a file that a reader of the verified source on a block explorer + cannot open, and no comment will be invalidated by a future docs reorganisation. + +The rc6 documentation additions (`doc/technical/TokenBinding-module.md`, the reworked `doc/README.md` sections) +were checked against the code as they were written; the error rename is reflected everywhere, including +`script/RuleEngineScript.s.sol`, whose header cites `TokenBinding_UnauthorizedCaller`. + +## H. Weird behaviour + +### H-1. The view/enforcement divergence — carried forward, unchanged + +`detectTransferRestriction` / `canTransfer` carry no `spender`, so a rule keyed by spender cannot evaluate the +operation and must answer "no restriction"; the engine aggregates that answer and can report a mint as allowed +that `transferred(spender, …)` reverts. Fully described in the rc5 report and pinned by a regression test. The +rc6 split touched neither path. + +## I. Interface granularity + +### I-1. `IRule` demands two functions the engine never calls — stands + +`_checkRule` gates every rule on the flattened `IRULE_INTERFACE_ID` (`0x2497d6cb`). Flattened, `IRule` requires +eight functional selectors. The engine calls six of them: + +| Selector | Called by the engine? | Call site | +|---|---|---| +| `transferred(address,address,uint256)` | yes | `RulesManagementModule.sol:214` | +| `transferred(address,address,address,uint256)` | yes | `RulesManagementModule.sol:233` | +| `detectTransferRestriction(address,address,uint256)` | yes | `RuleEngineBase.sol:169` | +| `detectTransferRestrictionFrom(address,address,address,uint256)` | yes | `RuleEngineBase.sol:193` | +| `canReturnTransferRestrictionCode(uint8)` | yes | `RuleEngineBase.sol:217` | +| `messageForTransferRestriction(uint8)` | yes | `RuleEngineBase.sol:218` | +| `canTransfer(address,address,uint256)` | **no** | — | +| `canTransferFrom(address,address,address,uint256)` | **no** | — | + +`canTransfer` and `canTransferFrom` arrive through `IERC3643ComplianceRead` and `IERC7551Compliance`, inherited +via CMTAT's `IRuleEngineERC1404`. The engine never asks a rule either question: `RuleEngineBase.canTransfer` +computes the boolean itself from its own `detectTransferRestriction` loop. + +Consequences, in the order they bite: + +- **Every rule author implements two views that nothing calls.** All four reference rules in `src/mocks/rules/` + do exactly that, and so must every rule in [CMTA/Rules](https://github.com/CMTA/Rules). +- **It is a least-privilege problem, not only an aesthetic one.** A rule that only needs to answer + "is this restricted" must also expose the compliance-read surface to pass the gate. +- **It pushes implementers toward stubs.** A stub that returns a constant advertises a capability the rule does + not have, which is worse than no check — an integrator reading the interface would reasonably call it. + +The remedy is the standard one: declare the six selectors the engine actually consumes as the required +interface, and gate on that id. Three details decide whether it is worth doing: + +- The two unused selectors come from **published standards** (ERC-3643, draft ERC-7551) reachable through + CMTAT's interface. Narrowing means `IRule` stops inheriting `IRuleEngineERC1404` and declares its six + selectors directly — this project's interface, not a standard, so it is legitimate to narrow. +- **`IRULE_INTERFACE_ID` changes**, and every existing rule must advertise the new id. Missing that step rejects + rules that work today — a self-inflicted outage. The rules live in a separate repository, which is what makes + this a coordinated release rather than a local edit. +- **ERC-165 expresses shape, never semantics.** A narrower id does not tell the engine an allow-list from a + deny-list. That remains configuration discipline and must stay documented; the change must not be presented + as closing that hole. + +**Verdict: decide**, at a release where CMTA/Rules moves in step. Not a defect today — the current gate is +sound, merely broader than necessary. The narrowed id and the migration are specified below, after a correction +that was itself wrong. + +### I-1 correction (superseded): "the two functions are called, by the token" + +The finding above is wrong, and the error was in its scope: it verified that *the engine* never calls +`canTransfer` / `canTransferFrom` on a rule, then concluded nothing does. A rule is not only reachable through +the engine. + +Every reference rule advertises **`RULE_ENGINE_INTERFACE_ID`** alongside `IRULE_INTERFACE_ID`: + +```solidity +// RuleWhitelistMock.supportsInterface, and identically in the three other rule mocks +return interfaceId == RULE_ENGINE_INTERFACE_ID || interfaceId == ERC1404EXTEND_INTERFACE_ID + || interfaceId == RuleInterfaceId.IRULE_INTERFACE_ID || AccessControl.supportsInterface(interfaceId); +``` + +Because `IRule is IRuleEngineERC1404`, a rule implements the whole engine-facing surface, and CMTAT's +`setRuleEngine(IRuleEngine)` accepts it with no further check. A single rule can therefore be attached to a +token **as its rule engine**, with no RuleEngine in between — a legitimate deployment for an issuer who needs +exactly one rule. In that configuration the token calls the rule directly: + +```solidity +// ValidationModuleRuleEngine._canTransferWithRuleEngine +return ruleEngine_.canTransfer(from, to, value); +``` + +That much is true: `canTransfer` and `canTransferFrom` are the token-facing half of the interface, and in the +standalone configuration a token does call them on a rule. + +**The conclusion drawn from it was wrong.** It slid from "some consumer calls them" to "the engine's gate must +demand them", and those are independent obligations: + +- **The engine's gate** is `_checkRule` -> `IRULE_INTERFACE_ID`. It exists so the engine knows the rule answers + the six questions *the engine* asks. +- **The standalone role** is not gated by that id at all. CMTAT's `setRuleEngine(IRuleEngine)` performs **no + ERC-165 check** — the obligation is a Solidity type at the call site. A rule that wants that role implements + `IRuleEngineERC1404` and advertises `RULE_ENGINE_INTERFACE_ID` on its own account, which is exactly what the + four reference rules already do. + +Narrowing `IRULE_INTERFACE_ID` therefore removes nothing. A rule that wants to be usable both behind an engine +and directly by a token keeps implementing both interfaces and advertising both ids — opt-in, per rule, as +today. What changes is that a rule which will only ever sit behind an engine stops being forced to implement +the token-facing half. **`I-1` stands as originally written.** + +### I-1 migration: the narrowed interface + +The six selectors the engine consumes, plus ERC-165, give: + +| Interface | ID | +|---|---| +| Current `IRULE_INTERFACE_ID` (8 selectors + ERC-165) | `0x2497d6cb` | +| Narrowed (6 selectors + ERC-165) | **`0xb1a69752`** | + +Cross-checked two ways, since the value is the whole point of the change: computing the candidate interface +directly gives `0xb0595ef5` for its six declared selectors, and `0xb0595ef5 ^ 0x01ffc9a7` (ERC-165) equals +`0x2497d6cb ^ canTransfer ^ canTransferFrom` — the same `0xb1a69752` from both directions. + +The migration is the part that makes this a coordinated release rather than a local edit: + +1. `IRule` stops inheriting `IRuleEngineERC1404` and declares the six selectors itself. +2. `RuleInterfaceId.IRULE_INTERFACE_ID` becomes `type(IRule).interfaceId ^ type(IERC165).interfaceId` — + computable outright, since the narrowed `IRule` inherits only `IERC165` (see `F-2`). +3. **Every rule in [CMTA/Rules](https://github.com/CMTA/Rules) must advertise the new id before an engine + running the new code will accept it.** Missing this step rejects rules that work today. Rules keep + advertising `RULE_ENGINE_INTERFACE_ID` as well if they support the standalone role. +4. The reference rules under `src/mocks/rules/` drop `canTransfer` / `canTransferFrom` only if they give up the + standalone role; otherwise they keep both interfaces and both ids, and gain the `I-2` tests. + +The limit stated in the original finding still holds: ERC-165 expresses shape, never semantics, so a narrower +id does not tell the engine an allow-list from a deny-list. That remains configuration discipline. + +### I-2. The standalone-rule configuration is advertised but never exercised — decide + +The capability above rests entirely on an ERC-165 id each rule advertises. Against that: + +- **No test attaches a rule directly to a token as its engine.** The suite always goes through a RuleEngine. +- **No document mentions it.** Neither `doc/technical/RuleEngine-with-CMTAT.md`, `RuleEngine-with-ERC3643.md` + nor `doc/README.md` describes a rule being used without an engine. +- The rules that matter in production live in [CMTA/Rules](https://github.com/CMTA/Rules), and whether they + advertise `RULE_ENGINE_INTERFACE_ID` is a separate question this review did not check. + +An advertised interface with no test is a claim nobody has verified: an integrator reading `supportsInterface` +is entitled to wire a rule straight into `setRuleEngine`, and nothing here proves the four reference rules +behave correctly in that role — in particular that a rule's `transferred` accepts being called by the token +rather than by the engine, and that its restriction codes and messages reach the token intact. + +**Verdict: decide.** Either support it deliberately — one integration test per reference rule plus a paragraph +in the CMTAT integration guide — or stop advertising `RULE_ENGINE_INTERFACE_ID` from rules that are only ever +meant to sit behind an engine. The first is the smaller change and matches what the interface already says. + +### I-3. Should the engine call the rule's `canTransfer` instead of `detectTransferRestriction`? — no + +Raised while reviewing `I-1`, and recorded because the answer is not obvious from the interfaces alone. + +**No, and the current direction is the correct one.** Three reasons: + +- **The code is the stronger primitive.** `bool` is derivable from the code (`code == 0`); the code is not + derivable from the bool. The engine's own `canTransfer` is exactly `detectTransferRestriction(...) == 0`. +- **The engine owes the token a code.** `RuleEngineBase` implements ERC-1404's `detectTransferRestriction` and + `messageForTransferRestriction`. If rules answered only a boolean, the engine could not produce a restriction + code at all, and the token would lose the reason a transfer was refused. +- **Calling both would double the external calls per rule and let a rule contradict itself.** `canTransfer` and + `detectTransferRestriction` are separately implemented in every rule; nothing forces them to agree. Today the + engine has one source of truth per rule per operation, and the answer cannot depend on which entrypoint the + caller used. + +**Verdict: leave as is.** + +## J. Modularity + +The rc6 release claims the binding registry is reusable outside this project. Two probe contracts were written +against that claim, compiled, and deleted; the results are below. + +### J-1. The registry embeds in a foreign host — verified, inconvenience only + +**Probe 1 — an unrelated host with its own ERC-2771 and RBAC:** + +```solidity +contract ProbeERC2771 is ERC2771Context, AccessControl, TokenBindingExtendedModule { + function _onlyTokenBindingManager() internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + // + the three Context overrides ERC-2771 always forces +} +``` + +**Result: compiles.** The only work the integrator does is implement one hook and the `_msgSender` / +`_msgData` / `_contextSuffixLength` trio that any ERC-2771 host must write regardless of this module. + +**Probe 2 — the harder case, a CMTAT token embedding the registry itself:** + +```solidity +contract ProbeCMTAT is CMTATStandardStandalone, TokenBindingModule { … } +``` + +**Result: compiles**, after the same three overrides — reported by solc as `Error (6480)` ("derived contract +must override") until they are supplied. Critically, **no `Error (5005)`**: there is no linearization conflict +between the module's bases and a CMTAT token's, which is the failure class that no amount of override glue can +fix and that would make the reuse claim false. + +The residual friction is that `TokenBindingModule` inherits OpenZeppelin's `Context` for `_msgSender()`. That is +the ecosystem convention — every OZ mixin does it — and removing it would mean inventing a project-specific +sender hook that integrators would have to wire anyway. **Verdict: leave as is**, with the two probes recorded +here as the evidence, and `TokenBindingStandaloneMock` + `test/TokenBinding/` standing as the compiling fixture +so the property cannot silently regress. + +### J-2. The ERC-3643 adapter costs 35 gas per binding operation — left as is + +`ERC3643ComplianceModule` overrides the generic `_onlyTokenBindingManager()` to call `_onlyComplianceManager()`, +adding one internal hop to every bind, unbind and approval call. Measured with two contracts, each exposing the +same `bindToken`, after an identical warm-up: + +| Variant | `bindToken` | +|---|---| +| Direct — deployment implements the generic hook | **73 250** gas | +| Indirect — the shipped ERC-3643 adapter layer | **73 285** gas | + +**35 gas**, on an administrative operation performed a handful of times in a deployment's life. That is the +price of keeping the ERC-3643 vocabulary at the ERC-3643 layer and the deployables' `_onlyComplianceManager` +API unchanged. Cheap enough to keep, and measured rather than estimated. + +## Method note + +The rc5 report warned that Aderyn writes absolute paths when run outside the repository. It happened again here: +the first rc6 run produced 84 links containing `/home//…` and had to be redone. It is now a checklist item +in the rc6 Aderyn feedback file. diff --git a/doc/security/audits/tools/v3.0.0-rc6/aderyn-report-feedback.md b/doc/security/audits/tools/v3.0.0-rc6/aderyn-report-feedback.md new file mode 100644 index 0000000..b68945c --- /dev/null +++ b/doc/security/audits/tools/v3.0.0-rc6/aderyn-report-feedback.md @@ -0,0 +1,163 @@ +# Aderyn Report — Assessment Feedback + +**Tool:** [Aderyn](https://github.com/Cyfrin/aderyn) 0.6.5 +**Report file:** `doc/security/audits/tools/v3.0.0-rc6/aderyn-report.md` +**Assessment date:** 2026-08-20 +**Scope:** `src/`, **mocks excluded** (`-x mocks`), 28 files analysed, 683 nSLOC, solc 0.8.36 / EVM Prague + +```bash +aderyn -x mocks --output doc/security/audits/tools/v3.0.0-rc6/aderyn-report.md +``` + +## Summary + +| ID | Finding | Tool Impact | Instances | Assessment | Decision | +|----|---------|-------------|-----------|------------|----------| +| L-1 | Centralization Risk | Low | 14 | The engine is by definition an admin-operated compliance controller | Accepted by design | +| L-2 | Unspecific Solidity Pragma | Low | 23 | `^0.8.20` is the supported range for integrators; the build pins 0.8.36 | Accepted by design | +| L-3 | PUSH0 Opcode | Low | 28 | EVM target is Prague, which includes PUSH0 | Accepted by design | +| L-4 | Modifier Invoked Only Once | Low | 1 | `onlyRulesLimitManager`, kept for symmetry with the hook pattern | Cosmetic, kept | +| L-5 | Empty Block | Low | 9 | The three access-control hooks × three variants; the modifier does the work | Accepted by design | +| L-6 | Loop Contains `require`/`revert` | Low | 4 | Admin batch operations where fail-fast is the wanted behaviour | Accepted by design | +| L-7 | Costly operations inside loop | Low | 4 | Admin-gated batch writes; inherent to a batch operation | Accepted by design | +| L-8 | Unchecked Return | Low | 1 | `_grantRole` cannot return `false` in a constructor | False positive | + +**0 High · 8 Low (84 instances). Nothing to fix.** + +## Scope verification + +| Check | Result | +|---|---| +| `grep -c 'lib/' aderyn-report.md` | **0** — no vendored dependency in scope | +| `grep -c 'src/mocks/' aderyn-report.md` | **0** — `-x mocks` applied correctly | +| `grep -c '/home/' aderyn-report.md` | **0** — no absolute paths committed | + +The last check caught a real mistake again. Aderyn computes its source links relative to the `--output` path, so a +run written outside the repository produces `../../../../../home//…/src/…` links — machine-specific paths +in a committed document. This run hit it exactly as rc5 did: the first attempt wrote to a scratch directory and +produced 84 such links, and it was re-run with the output inside the repository to get the correct relative +`../../../../../src/…` form. + +## Changes since v3.0.0-rc5 + +**Same 8 findings; 76 -> 84 instances, entirely from the file count.** + +| ID | rc5 | rc6 | Why | +|----|-----|-----|-----| +| L-2 Unspecific Solidity Pragma | 19 | **23** | one entry per file; 24 -> 28 files | +| L-3 PUSH0 Opcode | 24 | **28** | one entry per file; 24 -> 28 files | +| L-1, L-4, L-5, L-6, L-7, L-8 | 14/1/9/4/4/1 | **unchanged** | no new privileged function, hook, loop or ignored return | + +The token binding split added `ITokenBinding`, `ITokenBindingExtended`, `TokenBindingModule`, +`TokenBindingExtendedModule` and `TokenBindingModuleInvariantStorage`, and removed +`ERC3643ComplianceModuleInvariantStorage`: net +4 files, which is exactly the +4 seen on both per-file +detectors. This is the expected signature of a file-count change rather than a code change — worth stating, +because a jump in a static-analysis total is otherwise a scope-regression suspect. + +Two citations moved without changing count: L-6 and L-7 now point at `TokenBindingExtendedModule` lines 39, 46 +and 65 (`bindTokens`, `unbindTokens`, `setTokenSelfBindingApprovalBatch`) instead of +`ERC3643ComplianceExtendedModule`. The loops are the same code in a new file. + +Nothing new appeared from the refactor itself. In particular, removing the unused `onlyComplianceManager` +modifier did not add an L-4 instance elsewhere: `onlyTokenBindingManager` guards four functions, so it is not +"invoked only once". + +## Detailed triage + +### L-1: Centralization Risk (14 instances) + +Flags the `onlyRole` / `onlyOwner` privileged functions across the three deployable variants. + +The RuleEngine is a compliance controller: an operator must be able to add and remove rules and bind tokens, or +the contract has no purpose. The project deliberately ships three access-control shapes — RBAC (`RuleEngine`), +single-owner (`RuleEngineOwnable`) and two-step handover (`RuleEngineOwnable2Step`) — so the issuer can pick the +centralization profile that matches its governance. ERC-3643 itself specifies ERC-173 ownership for the +compliance contract. + +**Decision: accepted by design.** + +### L-2: Unspecific Solidity Pragma (23 instances) + +Every `src/` file declares `pragma solidity ^0.8.20;`, including the five files added this release. + +The caret is intentional: these contracts are consumed as a library by integrators compiling against their own +toolchain, and pinning an exact version would force their whole project onto it. The *build* is deterministic +regardless — `foundry.toml` pins `solc = "0.8.36"`. + +This matters slightly more now that `TokenBindingModule` is explicitly offered for reuse in other projects: the +permissive pragma is what lets it compile inside a consumer's build. + +**Decision: accepted by design.** + +### L-3: PUSH0 Opcode (28 instances) + +`foundry.toml` sets `evm_version = 'prague'`. PUSH0 has been available since Shanghai, so its presence is +expected for the declared target. A chain that has not adopted Shanghai must recompile with a lower +`evm_version` — a build-configuration decision, not a source change. + +**Decision: accepted by design**, with the deployment caveat recorded here. + +### L-4: Modifier Invoked Only Once (1 instance) + +`onlyRulesLimitManager` at `RulesManagementModule.sol:21` guards `setMaxRules` and nothing else. Inlining it +would break the documented pattern in which every protected operation goes through a virtual `_onlyX` hook +wrapped in an `onlyX` modifier, so each deployable variant can override the authorization independently. + +**Decision: cosmetic, kept deliberately.** + +### L-5: Empty Block (9 instances) + +All nine are the same three hooks across the three deployable contracts: + +```solidity +function _onlyComplianceManager() internal virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} +function _onlyRulesManager() internal virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} +function _onlyRulesLimitManager() internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} +``` + +The body is empty because the modifier performs the check — the core access-control pattern described in +`CLAUDE.md`. The split added one more indirection (`ERC3643ComplianceModule._onlyTokenBindingManager()` calls +`_onlyComplianceManager()`), but that body is not empty, so the count is unchanged. + +**Decision: accepted by design.** + +### L-6: Loop Contains `require`/`revert` (4 instances) + +`TokenBindingExtendedModule` lines 39, 46 and 65 (`bindTokens`, `unbindTokens`, +`setTokenSelfBindingApprovalBatch`) and `RulesManagementModule.setRules` line 62. + +These are administrative batch operations. Reverting the whole batch on a bad element is the wanted semantics: a +partially-applied compliance change is worse than a rejected one. + +**Decision: accepted by design.** + +### L-7: Costly operations inside loop (4 instances) + +The same four loops, flagged for storage writes inside the iteration. A batch operation cannot avoid writing +once per element. All four are gated on a privileged role, so the only party who can pass an oversized array is +the operator, and the only consequence is their own transaction running out of gas. + +Recorded as `A-3` in the rc5 `CLAUDE_ANALYSIS.md`, where the same conclusion was reached independently. + +**Decision: accepted by design.** + +### L-8: Unchecked Return (1 instance) — false positive + +`RuleEngine.sol:46`: + +```solidity +_grantRole(DEFAULT_ADMIN_ROLE, admin); +``` + +OpenZeppelin's `_grantRole` returns `true` when the role was newly granted and `false` when the account already +held it. This call is in the constructor, on a contract whose role storage is necessarily empty, so the return +value is invariably `true`. Checking it would add a branch that can never be taken. + +**Decision: false positive.** + +## Conclusion + +**No actionable security fixes are required from this Aderyn run.** Seven findings are by-design consequences of +what the RuleEngine is — an admin-operated, rule-iterating compliance controller distributed as a library — one +is a deliberately kept cosmetic, and one (`L-8`) is a verified false positive. No finding is exploitable, and +the token binding split introduced none. diff --git a/doc/security/audits/tools/v3.0.0-rc6/aderyn-report.md b/doc/security/audits/tools/v3.0.0-rc6/aderyn-report.md new file mode 100644 index 0000000..f0e653f --- /dev/null +++ b/doc/security/audits/tools/v3.0.0-rc6/aderyn-report.md @@ -0,0 +1,707 @@ +# Aderyn report — v3.0.0-rc6 + +| | | +|---|---| +| **Tool** | Aderyn 0.6.5 | +| **Scope** | `src/`, **mocks excluded** (`-x mocks`) | +| **Compiler** | solc 0.8.36, EVM Prague | +| **Files analysed** | 28 | +| **Date** | 2026-08-20 | + +```bash +aderyn -x mocks --output doc/security/audits/tools/v3.0.0-rc6/aderyn-report.md +``` + +**Result: 0 High · 8 Low (84 instances) — nothing to fix.** + +| ID | Finding | Severity | Instances | Assessment | +|----|---------|----------|-----------|------------| +| L-1 | Centralization Risk | Low | 14 | **By design** — the engine is an admin-operated compliance controller | +| L-2 | Unspecific Solidity Pragma | Low | 23 | **By design** — `^0.8.20` is the supported range; the build pins 0.8.36 | +| L-3 | PUSH0 Opcode | Low | 28 | **By design** — EVM target is Prague, which has PUSH0 | +| L-4 | Modifier Invoked Only Once | Low | 1 | **Cosmetic** — `onlyRulesLimitManager`, kept for symmetry with the hook pattern | +| L-5 | Empty Block | Low | 9 | **By design** — access-control hooks are intentionally empty bodies | +| L-6 | Loop Contains `require`/`revert` | Low | 4 | **By design** — admin batch operations, fail-fast is wanted | +| L-7 | Costly operations inside loop | Low | 4 | **By design** — admin-gated batch writes | +| L-8 | Unchecked Return | Low | 1 | **False positive** — `_grantRole` cannot return `false` in a constructor | + +**Scope verified:** `grep -c 'lib/'` = **0** and `grep -c 'src/mocks/'` = **0** in this report. + +**Delta from v3.0.0-rc5: same 8 findings, 76 -> 84 instances, entirely from the file count.** The token binding +split added five files and removed one (24 -> 28), and the two per-file detectors moved with it: L-2 pragma +19 -> 23, L-3 PUSH0 24 -> 28. Every other count is unchanged (14/1/9/4/4/1). L-6 and L-7 now cite +`TokenBindingExtendedModule` rather than `ERC3643ComplianceExtendedModule` — the batch loops moved file, the +code is the same. + +Triage and per-finding reasoning: [aderyn-report-feedback.md](./aderyn-report-feedback.md). +Overview of all analyses: [AUDIT_OVERVIEW.md](../../AUDIT_OVERVIEW.md). + +--- + +# Aderyn Analysis Report + +This report was generated by [Aderyn](https://github.com/Cyfrin/aderyn), a static analysis tool built by [Cyfrin](https://cyfrin.io), a blockchain security company. This report is not a substitute for manual audit or security review. It should not be relied upon for any purpose other than to assist in the identification of potential security vulnerabilities. +# Table of Contents + +- [Summary](#summary) + - [Files Summary](#files-summary) + - [Files Details](#files-details) + - [Issue Summary](#issue-summary) +- [Low Issues](#low-issues) + - [L-1: Centralization Risk](#l-1-centralization-risk) + - [L-2: Unspecific Solidity Pragma](#l-2-unspecific-solidity-pragma) + - [L-3: PUSH0 Opcode](#l-3-push0-opcode) + - [L-4: Modifier Invoked Only Once](#l-4-modifier-invoked-only-once) + - [L-5: Empty Block](#l-5-empty-block) + - [L-6: Loop Contains `require`/`revert`](#l-6-loop-contains-requirerevert) + - [L-7: Costly operations inside loop](#l-7-costly-operations-inside-loop) + - [L-8: Unchecked Return](#l-8-unchecked-return) + + +# Summary + +## Files Summary + +| Key | Value | +| --- | --- | +| .sol Files | 28 | +| Total nSLOC | 683 | + + +## Files Details + +| Filepath | nSLOC | +| --- | --- | +| src/RuleEngineBase.sol | 145 | +| src/RuleEngineOwnableShared.sol | 34 | +| src/deployment/RuleEngine.sol | 72 | +| src/deployment/RuleEngineOwnable.sol | 26 | +| src/deployment/RuleEngineOwnable2Step.sol | 38 | +| src/interfaces/IERC3643Compliance.sol | 8 | +| src/interfaces/IERC3643ComplianceExtended.sol | 4 | +| src/interfaces/IRule.sol | 5 | +| src/interfaces/IRulesManagementModule.sol | 14 | +| src/interfaces/ITokenBinding.sol | 8 | +| src/interfaces/ITokenBindingExtended.sol | 12 | +| src/modules/ERC2771ModuleStandalone.sol | 6 | +| src/modules/ERC3643ComplianceExtendedModule.sol | 18 | +| src/modules/ERC3643ComplianceModule.sol | 18 | +| src/modules/RulesManagementModule.sol | 109 | +| src/modules/TokenBindingExtendedModule.sol | 48 | +| src/modules/TokenBindingModule.sol | 51 | +| src/modules/VersionModule.sol | 8 | +| src/modules/library/ComplianceInterfaceId.sol | 6 | +| src/modules/library/ERC1404InterfaceId.sol | 4 | +| src/modules/library/ERC3643ComplianceRolesStorage.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 | +| src/modules/library/RulesManagementModuleRolesStorage.sol | 4 | +| src/modules/library/TokenBindingModuleInvariantStorage.sol | 7 | +| **Total** | **683** | + + +## Issue Summary + +| Category | No. of Issues | +| --- | --- | +| High | 0 | +| Low | 8 | + + +# Low Issues + +## L-1: Centralization Risk + +Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds. + +
14 Found Instances + + +- Found in src/deployment/RuleEngine.sol [Line: 25](../../../../../src/deployment/RuleEngine.sol#L25) + + ```solidity + AccessControlEnumerable, + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 112](../../../../../src/deployment/RuleEngine.sol#L112) + + ```solidity + function _onlyComplianceManager() internal virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 117](../../../../../src/deployment/RuleEngine.sol#L117) + + ```solidity + function _onlyRulesManager() internal virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 122](../../../../../src/deployment/RuleEngine.sol#L122) + + ```solidity + function _onlyRulesLimitManager() internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 12](../../../../../src/deployment/RuleEngineOwnable.sol#L12) + + ```solidity + contract RuleEngineOwnable is RuleEngineOwnableShared, Ownable { + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 28](../../../../../src/deployment/RuleEngineOwnable.sol#L28) + + ```solidity + function transferOwnership(address newOwner) public virtual override onlyOwner { + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 37](../../../../../src/deployment/RuleEngineOwnable.sol#L37) + + ```solidity + function _onlyRulesManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 42](../../../../../src/deployment/RuleEngineOwnable.sol#L42) + + ```solidity + function _onlyRulesLimitManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 47](../../../../../src/deployment/RuleEngineOwnable.sol#L47) + + ```solidity + function _onlyComplianceManager() internal virtual override onlyOwner {} + ``` + +- 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: 32](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L32) + + ```solidity + function transferOwnership(address newOwner) public virtual override onlyOwner { + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 58](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L58) + + ```solidity + function _onlyRulesManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 63](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L63) + + ```solidity + function _onlyRulesLimitManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 68](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L68) + + ```solidity + function _onlyComplianceManager() internal virtual override onlyOwner {} + ``` + +
+ + + +## L-2: Unspecific Solidity Pragma + +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;` + +
23 Found Instances + + +- Found in src/RuleEngineBase.sol [Line: 3](../../../../../src/RuleEngineBase.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/RuleEngineOwnableShared.sol [Line: 3](../../../../../src/RuleEngineOwnableShared.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 3](../../../../../src/deployment/RuleEngine.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 3](../../../../../src/deployment/RuleEngineOwnable.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 3](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/IERC3643Compliance.sol [Line: 3](../../../../../src/interfaces/IERC3643Compliance.sol#L3) + + ```solidity + 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 + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/IRulesManagementModule.sol [Line: 3](../../../../../src/interfaces/IRulesManagementModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/ITokenBinding.sol [Line: 3](../../../../../src/interfaces/ITokenBinding.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/ITokenBindingExtended.sol [Line: 3](../../../../../src/interfaces/ITokenBindingExtended.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/ERC2771ModuleStandalone.sol [Line: 3](../../../../../src/modules/ERC2771ModuleStandalone.sol#L3) + + ```solidity + 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 + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/RulesManagementModule.sol [Line: 3](../../../../../src/modules/RulesManagementModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 3](../../../../../src/modules/TokenBindingExtendedModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/TokenBindingModule.sol [Line: 3](../../../../../src/modules/TokenBindingModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/VersionModule.sol [Line: 3](../../../../../src/modules/VersionModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/ERC3643ComplianceRolesStorage.sol [Line: 3](../../../../../src/modules/library/ERC3643ComplianceRolesStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RuleEngineInvariantStorage.sol [Line: 3](../../../../../src/modules/library/RuleEngineInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RulesManagementModuleInvariantStorage.sol [Line: 3](../../../../../src/modules/library/RulesManagementModuleInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RulesManagementModuleRolesStorage.sol [Line: 3](../../../../../src/modules/library/RulesManagementModuleRolesStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/TokenBindingModuleInvariantStorage.sol [Line: 3](../../../../../src/modules/library/TokenBindingModuleInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +
+ + + +## L-3: PUSH0 Opcode + +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. + +
28 Found Instances + + +- Found in src/RuleEngineBase.sol [Line: 3](../../../../../src/RuleEngineBase.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/RuleEngineOwnableShared.sol [Line: 3](../../../../../src/RuleEngineOwnableShared.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 3](../../../../../src/deployment/RuleEngine.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 3](../../../../../src/deployment/RuleEngineOwnable.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 3](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/IERC3643Compliance.sol [Line: 3](../../../../../src/interfaces/IERC3643Compliance.sol#L3) + + ```solidity + 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 + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/IRulesManagementModule.sol [Line: 3](../../../../../src/interfaces/IRulesManagementModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/ITokenBinding.sol [Line: 3](../../../../../src/interfaces/ITokenBinding.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/interfaces/ITokenBindingExtended.sol [Line: 3](../../../../../src/interfaces/ITokenBindingExtended.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/ERC2771ModuleStandalone.sol [Line: 3](../../../../../src/modules/ERC2771ModuleStandalone.sol#L3) + + ```solidity + 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 + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/RulesManagementModule.sol [Line: 3](../../../../../src/modules/RulesManagementModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 3](../../../../../src/modules/TokenBindingExtendedModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/TokenBindingModule.sol [Line: 3](../../../../../src/modules/TokenBindingModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/VersionModule.sol [Line: 3](../../../../../src/modules/VersionModule.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/ComplianceInterfaceId.sol [Line: 3](../../../../../src/modules/library/ComplianceInterfaceId.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/ERC1404InterfaceId.sol [Line: 3](../../../../../src/modules/library/ERC1404InterfaceId.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/ERC3643ComplianceRolesStorage.sol [Line: 3](../../../../../src/modules/library/ERC3643ComplianceRolesStorage.sol#L3) + + ```solidity + 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 + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RuleEngineInvariantStorage.sol [Line: 3](../../../../../src/modules/library/RuleEngineInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RuleInterfaceId.sol [Line: 3](../../../../../src/modules/library/RuleInterfaceId.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RulesManagementModuleInvariantStorage.sol [Line: 3](../../../../../src/modules/library/RulesManagementModuleInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/RulesManagementModuleRolesStorage.sol [Line: 3](../../../../../src/modules/library/RulesManagementModuleRolesStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +- Found in src/modules/library/TokenBindingModuleInvariantStorage.sol [Line: 3](../../../../../src/modules/library/TokenBindingModuleInvariantStorage.sol#L3) + + ```solidity + pragma solidity ^0.8.20; + ``` + +
+ + + +## L-4: Modifier Invoked Only Once + +Consider removing the modifier or inlining the logic into the calling function. + +
1 Found Instances + + +- Found in src/modules/RulesManagementModule.sol [Line: 21](../../../../../src/modules/RulesManagementModule.sol#L21) + + ```solidity + modifier onlyRulesLimitManager() { + ``` + +
+ + + +## L-5: Empty Block + +Consider removing empty blocks. + +
9 Found Instances + + +- Found in src/deployment/RuleEngine.sol [Line: 112](../../../../../src/deployment/RuleEngine.sol#L112) + + ```solidity + function _onlyComplianceManager() internal virtual override onlyRole(COMPLIANCE_MANAGER_ROLE) {} + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 117](../../../../../src/deployment/RuleEngine.sol#L117) + + ```solidity + function _onlyRulesManager() internal virtual override onlyRole(RULES_MANAGEMENT_ROLE) {} + ``` + +- Found in src/deployment/RuleEngine.sol [Line: 122](../../../../../src/deployment/RuleEngine.sol#L122) + + ```solidity + function _onlyRulesLimitManager() internal virtual override onlyRole(DEFAULT_ADMIN_ROLE) {} + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 37](../../../../../src/deployment/RuleEngineOwnable.sol#L37) + + ```solidity + function _onlyRulesManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 42](../../../../../src/deployment/RuleEngineOwnable.sol#L42) + + ```solidity + function _onlyRulesLimitManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable.sol [Line: 47](../../../../../src/deployment/RuleEngineOwnable.sol#L47) + + ```solidity + function _onlyComplianceManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 58](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L58) + + ```solidity + function _onlyRulesManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 63](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L63) + + ```solidity + function _onlyRulesLimitManager() internal virtual override onlyOwner {} + ``` + +- Found in src/deployment/RuleEngineOwnable2Step.sol [Line: 68](../../../../../src/deployment/RuleEngineOwnable2Step.sol#L68) + + ```solidity + function _onlyComplianceManager() internal virtual override onlyOwner {} + ``` + +
+ + + +## L-6: Loop Contains `require`/`revert` + +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 + +
4 Found Instances + + +- Found in src/modules/RulesManagementModule.sol [Line: 62](../../../../../src/modules/RulesManagementModule.sol#L62) + + ```solidity + for (uint256 i = 0; i < rules_.length; ++i) { + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 39](../../../../../src/modules/TokenBindingExtendedModule.sol#L39) + + ```solidity + for (uint256 i = 0; i < tokens.length; ++i) { + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 46](../../../../../src/modules/TokenBindingExtendedModule.sol#L46) + + ```solidity + for (uint256 i = 0; i < tokens.length; ++i) { + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 65](../../../../../src/modules/TokenBindingExtendedModule.sol#L65) + + ```solidity + for (uint256 i = 0; i < tokens.length; ++i) { + ``` + +
+ + + +## L-7: Costly operations inside loop + +Invoking `SSTORE` operations in loops may waste gas. Use a local variable to hold the loop computation result. + +
4 Found Instances + + +- Found in src/modules/RulesManagementModule.sol [Line: 62](../../../../../src/modules/RulesManagementModule.sol#L62) + + ```solidity + for (uint256 i = 0; i < rules_.length; ++i) { + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 39](../../../../../src/modules/TokenBindingExtendedModule.sol#L39) + + ```solidity + for (uint256 i = 0; i < tokens.length; ++i) { + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 46](../../../../../src/modules/TokenBindingExtendedModule.sol#L46) + + ```solidity + for (uint256 i = 0; i < tokens.length; ++i) { + ``` + +- Found in src/modules/TokenBindingExtendedModule.sol [Line: 65](../../../../../src/modules/TokenBindingExtendedModule.sol#L65) + + ```solidity + for (uint256 i = 0; i < tokens.length; ++i) { + ``` + +
+ + + +## L-8: Unchecked Return + +Function returns a value but it is ignored. Consider checking the return value. + +
1 Found Instances + + +- Found in src/deployment/RuleEngine.sol [Line: 46](../../../../../src/deployment/RuleEngine.sol#L46) + + ```solidity + _grantRole(DEFAULT_ADMIN_ROLE, admin); + ``` + +
+ + + diff --git a/doc/security/audits/tools/v3.0.0-rc6/slither-report-feedback.md b/doc/security/audits/tools/v3.0.0-rc6/slither-report-feedback.md new file mode 100644 index 0000000..a09ef2c --- /dev/null +++ b/doc/security/audits/tools/v3.0.0-rc6/slither-report-feedback.md @@ -0,0 +1,106 @@ +# Slither Report — Assessment Feedback + +**Tool:** [Slither](https://github.com/crytic/slither) 0.11.5 +**Report file:** `doc/security/audits/tools/v3.0.0-rc6/slither-report.md` +**Assessment date:** 2026-08-20 +**Scope:** `src/`, **mocks excluded**, 108 contracts analysed, solc 0.8.36 / EVM Prague + +```bash +slither . --checklist --filter-paths "openzeppelin-contracts|test|CMTAT|forge-std|mocks" \ + > doc/security/audits/tools/v3.0.0-rc6/slither-report.md +``` + +## Summary + +| IDs | Detector | Tool Impact | Assessment | Decision | +|-----|----------|-------------|------------|----------| +| 0-9 | `calls-loop` | Low | Inherent to pluggable rule-engine dispatch; bounded by `maxRules` | Accepted by design | +| 10-11 | `unindexed-event-address` | Informational | The ERC-3643 reference declares these events unindexed — matching it is conformance | Accepted by design | + +**0 High · 0 Medium · 10 Low · 2 Informational. Nothing to fix.** + +## Scope verification + +Confirmed before triage, because a `--filter-paths` entry that matches nothing fails open and silently pulls +the whole vendored tree into scope: + +| Check | Result | +|---|---| +| `grep -c 'lib/\|node_modules/' slither-report.md` | **0** — no vendored dependency in scope | +| `grep -c 'src/mocks/' slither-report.md` | **0** — mocks correctly excluded | + +The filter lists dependency names (`openzeppelin-contracts`, `CMTAT`, `forge-std`) rather than filtering `lib` +wholesale. That still resolves correctly here — each entry matches its `lib/` path as a substring, and +`lib/CMTATv3.0.0` and `lib/openzeppelin-contracts-upgradeable` are caught by the `CMTAT` and +`openzeppelin-contracts` entries. `lib/ERC-3643` matches no entry, but nothing under `src/` imports it, so the +compiler never pulls it in — confirmed by the zero `lib/` count above. Worth revisiting if that ever changes. + +## Changes since v3.0.0-rc5 + +**No change in counts:** 10 `calls-loop` + 2 `unindexed-event-address`, identical to rc5. Contracts analysed +went 103 -> 108 with the modules and interfaces added by the token binding split. + +The split moved code without adding findings, and it moved two citations: + +- `unindexed-event-address` now reports `ITokenBinding.TokenBound` / `TokenUnbound` + (`src/interfaces/ITokenBinding.sol#L20,L26`) instead of `IERC3643Compliance`. The events were relocated to the + standard-agnostic interface; their signature is unchanged, so the disposition below is unchanged with it. +- `calls-loop` still reports the same ten sites in `RulesManagementModule._transferred` (both overloads), + `RuleEngineBase._detectTransferRestriction`, `_detectTransferRestrictionFrom` and + `_messageForTransferRestriction`. None of these files changed behaviourally this release. + +Note what did **not** appear: the new `TokenBindingModule` / `TokenBindingExtendedModule` produced no finding of +their own, and the error rename (`RuleEngine_ERC3643Compliance_*` -> `TokenBinding_*`) is invisible to Slither. + +## Detailed triage + +### IDs 0-9: `calls-loop` + +Ten instances across `RulesManagementModule._transferred` (both overloads), +`RuleEngineBase._detectTransferRestriction`, `_detectTransferRestrictionFrom` and +`_messageForTransferRestriction`. + +The engine exists to call a configurable list of rule contracts, so an external call inside a loop is the +product, not a defect. The risk the detector points at — unbounded iteration — is bounded on-chain by +`maxRules` (default **10**, `DEFAULT_MAX_RULES`), and the cap is emitted at deployment so it is visible from the +event log alone. + +Rules are trusted business logic by convention: the engine refuses to grant a role to an address currently +configured as a rule, and the documentation states that rule contracts must not hold `RULES_MANAGEMENT_ROLE`. + +**Decision: accepted by design.** Documented in `doc/technical/RuleEngine-with-CMTAT.md` §4.1 and +`RuleEngine-with-ERC3643.md` §4.4, and recorded as `A-3` in the rc5 `CLAUDE_ANALYSIS.md`. + +### IDs 10-11: `unindexed-event-address` + +`TokenBound(address token)` and `TokenUnbound(address token)`, now declared in `ITokenBinding.sol` (lines 20 and +26), pass their address parameter without `indexed`. + +The ERC-3643 reference implementation declares the same two events **unindexed**: + +```solidity +// lib/ERC-3643/contracts/compliance/modular/IModularCompliance.sol:82,89 +event TokenBound(address _token); +event TokenUnbound(address _token); +``` + +(identically in `compliance/legacy/ICompliance.sol:85,92`.) + +Adding `indexed` would move the parameter from the data section into a topic, changing the topic layout and +breaking any indexer written against the ERC-3643 interface. The correct place to change this is the standard. + +The move to `ITokenBinding` does not weaken that argument: `IERC3643Compliance` inherits the interface, so an +ERC-3643 consumer still sees exactly the reference signature. The generic interface deliberately kept it rather +than "improving" it, precisely so a binding registry reused elsewhere emits the same event an ERC-3643 indexer +already understands. + +Note the contrast with `TokenSelfBindingApprovalSet`, which *is* indexed: it is specific to this project's +extended module and carries no conformance obligation, so indexing it was free. + +**Decision: accepted by design (spec conformance).** + +## Conclusion + +**No actionable security fixes are required from this Slither run.** Both detectors are architectural by-design +outcomes, and neither is exploitable: `calls-loop` describes the engine's core dispatch, bounded by an on-chain +cap, and `unindexed-event-address` reflects deliberate conformance with the ERC-3643 event signatures. diff --git a/doc/security/audits/tools/v3.0.0-rc6/slither-report.md b/doc/security/audits/tools/v3.0.0-rc6/slither-report.md new file mode 100644 index 0000000..7adf5f1 --- /dev/null +++ b/doc/security/audits/tools/v3.0.0-rc6/slither-report.md @@ -0,0 +1,139 @@ +# Slither report — v3.0.0-rc6 + +| | | +|---|---| +| **Tool** | Slither 0.11.5 | +| **Scope** | `src/`, **mocks excluded** | +| **Compiler** | solc 0.8.36, EVM Prague | +| **Contracts analysed** | 108 | +| **Date** | 2026-08-20 | + +```bash +slither . --checklist --filter-paths "openzeppelin-contracts|test|CMTAT|forge-std|mocks" \ + > doc/security/audits/tools/v3.0.0-rc6/slither-report.md +``` + +**Result: 0 High · 0 Medium · 10 Low · 2 Informational — nothing to fix.** + +| Detector | Severity | Instances | Assessment | +|---|---|---|---| +| `calls-loop` | Low | 10 | **By design** — the engine iterates its rule set; bounded by `maxRules` (default 10) | +| `unindexed-event-address` | Informational | 2 | **By design (spec conformance)** — `TokenBound` / `TokenUnbound` keep the unindexed signature of the ERC-3643 reference | + +**Scope verified:** `grep -c 'lib/\|node_modules/'` = **0** and `grep -c 'src/mocks/'` = **0** in this report, so +no vendored dependency or mock contract entered the analysis. + +**Delta from v3.0.0-rc5: no change in counts** — same two detectors, same 10 + 2 instances. The token binding +split moved two citations without adding any: `unindexed-event-address` now points at +`ITokenBinding.TokenBound` / `TokenUnbound` instead of `IERC3643Compliance`, since the events moved to the +standard-agnostic interface. Contracts analysed went from 103 to 108 with the new modules and interfaces. + +Triage and per-finding reasoning: [slither-report-feedback.md](./slither-report-feedback.md). +Overview of all analyses: [AUDIT_OVERVIEW.md](../../AUDIT_OVERVIEW.md). + +--- + +**THIS CHECKLIST IS NOT COMPLETE**. Use `--show-ignored-findings` to show all the results. +Summary + - [calls-loop](#calls-loop) (10 results) (Low) + - [unindexed-event-address](#unindexed-event-address) (2 results) (Informational) +## calls-loop +Impact: Low +Confidence: Medium + - [ ] ID-0 +[RulesManagementModule._transferred(address,address,uint256)](src/modules/RulesManagementModule.sol#L211-L216) has external calls inside a loop: [IRule(_rules.pos(i)).transferred(from,to,value)](src/modules/RulesManagementModule.sol#L214) + Calls stack containing the loop: + RuleEngineBase.transferred(address,address,uint256) + +src/modules/RulesManagementModule.sol#L211-L216 + + + - [ ] ID-1 +[RuleEngineBase._messageForTransferRestriction(uint8)](src/RuleEngineBase.sol#L211-L222) has external calls inside a loop: [IRule(rule(i)).canReturnTransferRestrictionCode(restrictionCode)](src/RuleEngineBase.sol#L217) + Calls stack containing the loop: + RuleEngineBase.messageForTransferRestriction(uint8) + +src/RuleEngineBase.sol#L211-L222 + + + - [ ] ID-2 +[RuleEngineBase._detectTransferRestriction(address,address,uint256)](src/RuleEngineBase.sol#L166-L175) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestriction(from,to,value)](src/RuleEngineBase.sol#L169) + Calls stack containing the loop: + RuleEngineBase.canTransfer(address,address,uint256) + RuleEngineBase.detectTransferRestriction(address,address,uint256) + +src/RuleEngineBase.sol#L166-L175 + + + - [ ] ID-3 +[RulesManagementModule._transferred(address,address,uint256)](src/modules/RulesManagementModule.sol#L211-L216) has external calls inside a loop: [IRule(_rules.pos(i)).transferred(from,to,value)](src/modules/RulesManagementModule.sol#L214) + Calls stack containing the loop: + RuleEngineBase.destroyed(address,uint256) + +src/modules/RulesManagementModule.sol#L211-L216 + + + - [ ] ID-4 +[RulesManagementModule._transferred(address,address,address,uint256)](src/modules/RulesManagementModule.sol#L230-L235) has external calls inside a loop: [IRule(_rules.pos(i)).transferred(spender,from,to,value)](src/modules/RulesManagementModule.sol#L233) + Calls stack containing the loop: + RuleEngineBase.transferred(address,address,address,uint256) + +src/modules/RulesManagementModule.sol#L230-L235 + + + - [ ] ID-5 +[RuleEngineBase._messageForTransferRestriction(uint8)](src/RuleEngineBase.sol#L211-L222) has external calls inside a loop: [IRule(rule(i)).messageForTransferRestriction(restrictionCode)](src/RuleEngineBase.sol#L218) + Calls stack containing the loop: + RuleEngineBase.messageForTransferRestriction(uint8) + +src/RuleEngineBase.sol#L211-L222 + + + - [ ] ID-6 +[RulesManagementModule._transferred(address,address,uint256)](src/modules/RulesManagementModule.sol#L211-L216) has external calls inside a loop: [IRule(_rules.pos(i)).transferred(from,to,value)](src/modules/RulesManagementModule.sol#L214) + Calls stack containing the loop: + RuleEngineBase.created(address,uint256) + +src/modules/RulesManagementModule.sol#L211-L216 + + + - [ ] ID-7 +[RuleEngineBase._detectTransferRestrictionFrom(address,address,address,uint256)](src/RuleEngineBase.sol#L185-L199) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender,from,to,value)](src/RuleEngineBase.sol#L193) + Calls stack containing the loop: + RuleEngineBase.canTransferFrom(address,address,address,uint256) + RuleEngineBase.detectTransferRestrictionFrom(address,address,address,uint256) + +src/RuleEngineBase.sol#L185-L199 + + + - [ ] ID-8 +[RuleEngineBase._detectTransferRestrictionFrom(address,address,address,uint256)](src/RuleEngineBase.sol#L185-L199) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestrictionFrom(spender,from,to,value)](src/RuleEngineBase.sol#L193) + Calls stack containing the loop: + RuleEngineBase.detectTransferRestrictionFrom(address,address,address,uint256) + +src/RuleEngineBase.sol#L185-L199 + + + - [ ] ID-9 +[RuleEngineBase._detectTransferRestriction(address,address,uint256)](src/RuleEngineBase.sol#L166-L175) has external calls inside a loop: [restriction = IRule(rule(i)).detectTransferRestriction(from,to,value)](src/RuleEngineBase.sol#L169) + Calls stack containing the loop: + RuleEngineBase.detectTransferRestriction(address,address,uint256) + +src/RuleEngineBase.sol#L166-L175 + + +## unindexed-event-address +Impact: Informational +Confidence: High + - [ ] ID-10 +Event [ITokenBinding.TokenBound(address)](src/interfaces/ITokenBinding.sol#L20) has address parameters but no indexed parameters + +src/interfaces/ITokenBinding.sol#L20 + + + - [ ] ID-11 +Event [ITokenBinding.TokenUnbound(address)](src/interfaces/ITokenBinding.sol#L26) has address parameters but no indexed parameters + +src/interfaces/ITokenBinding.sol#L26 + + diff --git a/doc/technical/RuleEngine-with-CMTAT.md b/doc/technical/RuleEngine-with-CMTAT.md index 0fdb1fb..43e643a 100644 --- a/doc/technical/RuleEngine-with-CMTAT.md +++ b/doc/technical/RuleEngine-with-CMTAT.md @@ -197,6 +197,7 @@ Both are bound to the same engine implementation. ## 6. Related documents +- [TokenBinding-module.md](./TokenBinding-module.md) — the standard-agnostic binding registry used to bind the token to the engine - [RuleEngine-with-ERC3643.md](./RuleEngine-with-ERC3643.md) — the ERC-3643 counterpart - [../README.md](../README.md) — full interface and API reference - [CLAUDE_ANALYSIS.md](../security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md) — code-quality review, findings `H-1` and `F-2` diff --git a/doc/technical/RuleEngine-with-ERC3643.md b/doc/technical/RuleEngine-with-ERC3643.md index 3342de4..4ad0026 100644 --- a/doc/technical/RuleEngine-with-ERC3643.md +++ b/doc/technical/RuleEngine-with-ERC3643.md @@ -113,6 +113,14 @@ engine.setTokenSelfBindingApprovalBatch(tokens, true); engine.getTokenBounds(); // every bound token ``` +### 3.6 Where binding is implemented + +The binding registry itself is standard-agnostic and lives in `TokenBindingModule` / +`TokenBindingExtendedModule`; `ERC3643ComplianceModule` / `ERC3643ComplianceExtendedModule` are thin +ERC-3643 adapters over it, adding `getTokenBound()` and the compliance manager vocabulary. This matters +here only for reading the code and for reusing the registry elsewhere: the engine's external API is +unchanged. See [TokenBinding-module.md](./TokenBinding-module.md). + ## 4. Warnings and limitations ### 4.1 The mint pre-check fails open for spender-dependent rules @@ -159,7 +167,7 @@ A gas-heavy rule affects every operation on every bound token. ### 4.5 Only bound tokens may call the callbacks `transferred`, `created` and `destroyed` all revert with -`RuleEngine_ERC3643Compliance_UnauthorizedCaller` for any caller that is not a bound token. Verified by +`TokenBinding_UnauthorizedCaller` for any caller that is not a bound token. Verified by `testUnboundCallerCannotCallTransferred` and `testUnboundCallerCannotCallCreatedOrDestroyed`. ### 4.6 Restriction codes must be unique across the rule set @@ -212,6 +220,7 @@ The end-to-end suite covers, using a token that drives the engine exactly as `To ## 6. Related documents - [RuleEngine-with-CMTAT.md](./RuleEngine-with-CMTAT.md) — the CMTAT counterpart +- [TokenBinding-module.md](./TokenBinding-module.md) — the standard-agnostic binding registry underneath - [../README.md](../README.md) — full interface and API reference - [CLAUDE_ANALYSIS.md](../security/audits/tools/v3.0.0-rc5/CLAUDE_ANALYSIS.md) — code-quality review, findings `H-1` and `F-2` - Production rules: [github.com/CMTA/Rules](https://github.com/CMTA/Rules) diff --git a/doc/technical/TokenBinding-module.md b/doc/technical/TokenBinding-module.md new file mode 100644 index 0000000..2651e33 --- /dev/null +++ b/doc/technical/TokenBinding-module.md @@ -0,0 +1,130 @@ +# The token binding module + +`TokenBindingModule` is the allowlist of token contracts allowed to call back into an engine. It is +deliberately kept **standard-agnostic**: it contains no rule, ERC-1404 or ERC-3643 logic, and depends +on nothing but OpenZeppelin. Any project that has to bind tokens — a compliance engine, a document +engine, a transfer engine — can embed it as-is; the RuleEngine is one consumer among possible others. + +For how the RuleEngine uses it as an ERC-3643 compliance contract, see +[RuleEngine-with-ERC3643.md](./RuleEngine-with-ERC3643.md). + +## 1. Layering + +``` +TokenBindingModule — registry: storage, bind/unbind/isTokenBound, onlyBoundToken +└── TokenBindingExtendedModule — batch bind/unbind, token self-binding, getTokenBounds + +ERC3643ComplianceModule — ERC-3643 adapter: getTokenBound(), compliance manager naming +└── ERC3643ComplianceExtendedModule — ERC-3643 flavour of the extended registry +``` + +Everything ERC-3643 specific lives in the two right-hand contracts, and they are thin: + +| Concern | Where | +|---|---| +| Bound token set, `bindToken` / `unbindToken` / `isTokenBound`, `TokenBound` / `TokenUnbound`, `onlyBoundToken` | `TokenBindingModule` | +| Batch binding, self-binding approval, `getTokenBounds()` | `TokenBindingExtendedModule` | +| `getTokenBound()` (single-token ERC-3643 view), compliance manager naming | `ERC3643ComplianceModule` | +| `transferred` / `created` / `destroyed` | `RuleEngineBase` (they depend on the rules, not on the binding) | + +Interfaces follow the same split: [`ITokenBinding`](../../src/interfaces/ITokenBinding.sol) and +[`ITokenBindingExtended`](../../src/interfaces/ITokenBindingExtended.sol) are standard-agnostic; +[`IERC3643Compliance`](../../src/interfaces/IERC3643Compliance.sol) and +[`IERC3643ComplianceExtended`](../../src/interfaces/IERC3643ComplianceExtended.sol) extend them with the +ERC-3643 parts. The advertised ERC-165 interface IDs are unchanged by this split, since the function +sets are the same. + +## 2. What the module provides + +```solidity +function bindToken(address token) public virtual; // authorized by _authorizeTokenBindingChange +function unbindToken(address token) public virtual; // authorized by _authorizeTokenBindingChange +function isTokenBound(address token) public view virtual returns (bool); + +modifier onlyBoundToken(); // guards the bound-token entry points +modifier onlyTokenBindingManager(); // guards binding administration +``` + +Errors, in [`TokenBindingModuleInvariantStorage`](../../src/modules/library/TokenBindingModuleInvariantStorage.sol): +`TokenBinding_InvalidTokenAddress`, `TokenBinding_TokenAlreadyBound`, `TokenBinding_TokenNotBound`, +`TokenBinding_UnauthorizedCaller`. + +Bound tokens are stored in an OpenZeppelin `EnumerableSet.AddressSet`, so add, remove and lookup are +O(1) and the set is enumerable. `_bindToken` and `_unbindToken` rely on the set mutation return value, +which keeps the `TokenAlreadyBound` / `TokenNotBound` diagnostics without a second lookup. + +## 3. Reusing it in another project + +Two hooks are left to the embedding contract: + +| Hook | Purpose | Default | +|---|---|---| +| `_onlyTokenBindingManager()` | access control for binding administration | abstract, must be implemented | +| `_authorizeTokenBindingChange(address token)` | authorizes one bind/unbind | manager check | + +A minimal deployment therefore only has to wire its access control model: + +```solidity +contract MyEngine is TokenBindingModule, Ownable { + constructor(address owner_) Ownable(owner_) {} + + // A bound-token entry point: only a bound token may call it. + function notify() public onlyBoundToken { /* ... */ } + + function _onlyTokenBindingManager() internal virtual override onlyOwner {} +} +``` + +That is exactly [`TokenBindingStandaloneMock`](../../src/mocks/TokenBindingStandaloneMock.sol), the +reference implementation used by `test/TokenBinding/TokenBindingStandalone.t.sol` to pin that the +registry works with no compliance code around it. Like every contract under `src/mocks/`, it is a +reference for testing and examples, not a production contract. + +Embedding `TokenBindingExtendedModule` instead adds batch operations and self-binding: a token whose +self-binding has been approved may call `bindToken(address(this))` itself, which is what ERC-3643 +`setCompliance` needs. Self-binding is opt-in per token; without approval, only the binding manager can +bind. When both an extended module and another parent bring in the binding authorization hook — as in +`ERC3643ComplianceExtendedModule` — Solidity requires the most derived contract to resolve it +explicitly, which it does by delegating to `TokenBindingExtendedModule`. + +To rename the manager in the vocabulary of your own domain, do what `ERC3643ComplianceModule` does: +declare your own abstract hook and wire the generic one to it. + +```solidity +function _onlyTokenBindingManager() internal virtual override { + _onlyComplianceManager(); +} + +function _onlyComplianceManager() internal virtual; +``` + +## 4. Operational warnings + +These are properties of the registry itself, not of any standard built on it. + +- **Multi-tenant binding shares state.** Every bound token drives the same downstream logic. For the + RuleEngine, stateful rules keep per-address accounting that is shared across all bound tokens, and the + ERC-3643 callbacks do not carry the calling token address to the rules, so binding tokens from + different issuers silently cross-contaminates their accounting. Only bind tokens that are equally + trusted and governed together. +- **Unbinding is administrative.** It stops future calls; it does not erase state already accumulated + while the token was bound. +- **`address(0)` cannot be bound.** It could never call the engine, and binding it would only emit a + `TokenBound` event that indexers would have to filter out. +- **Binding and unbinding are not idempotent.** Re-binding a bound token reverts with + `TokenBinding_TokenAlreadyBound`, and unbinding an unbound one with `TokenBinding_TokenNotBound`, so a + redundant administrative call is reported rather than silently accepted. + +## 5. What is tested + +| Area | File | Tests | +|---|---|---| +| Standalone reuse, outside any compliance context | `test/TokenBinding/TokenBindingStandalone.t.sol` | 10 | +| Registry through the ERC-3643 surface, RBAC variant | `test/RuleEngine/ERC3643Compliance.t.sol` | 30 | +| Registry through the ERC-3643 surface, ownable variant | `test/RuleEngineOwnable/ERC3643Compliance.t.sol` | 29 | + +## 6. Related documents + +- [RuleEngine-with-ERC3643.md](./RuleEngine-with-ERC3643.md) — the ERC-3643 integration +- [RuleEngine-with-CMTAT.md](./RuleEngine-with-CMTAT.md) — the CMTAT integration +- [../README.md](../README.md) — full interface and API reference diff --git a/package-lock.json b/package-lock.json index 850ed90..4d77cf1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,11 +4,11 @@ "requires": true, "packages": { "": { - "name": "RuleEngineNew", "devDependencies": { "@nomicfoundation/hardhat-foundry": "^1.2.1", "@nomicfoundation/hardhat-toolbox": "^6.1.2", "hardhat": "^2.28.6", + "sol2uml": "^2.5.26", "surya": "^0.4.13" } }, @@ -17,8 +17,39 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", "dev": true, + "license": "MIT" + }, + "node_modules/@aduh95/viz.js": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@aduh95/viz.js/-/viz.js-3.7.0.tgz", + "integrity": "sha512-20Pk2Z98fbPLkECcrZSJszKos/OgtvJJR3NcbVfgCJ6EQjDNzW2P1BKqImOz3tJ952dvO2DWEhcLhQ1Wz1e9ng==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -1282,6 +1313,85 @@ "node": ">=14" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/@scure/base": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", @@ -1435,6 +1545,13 @@ "antlr4ts": "^0.5.0-alpha.4" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -1573,6 +1690,16 @@ "@types/chai": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -1601,13 +1728,19 @@ "license": "MIT", "peer": true }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.19.0" } @@ -1642,6 +1775,17 @@ "@types/node": "*" } }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -1715,8 +1859,7 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/agent-base": { "version": "6.0.2", @@ -1911,6 +2054,26 @@ "node": "*" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1935,8 +2098,7 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -1972,7 +2134,6 @@ "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", @@ -1980,12 +2141,126 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/axios-debug-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/axios-debug-log/-/axios-debug-log-1.0.0.tgz", + "integrity": "sha512-ZjMaEBEij9w+Vbk2Uc3XflchTT7j9rZdYD/snN+XQ5FRDq1QjZNhh0Izb3KSyarU5vTkiCvJyg1xDiQBHZZB9w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0" + }, + "peerDependencies": { + "axios": ">=1.0.0" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.0.tgz", + "integrity": "sha512-fM+MhCvdQhZ7NV6S95a07gPSqjIYKn6mFaXfx266wN3ajZGl/+1AzH+ubkXQ0fFZvOe2nk9VHkzdYkQE5zMV3Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.2.tgz", + "integrity": "sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base-x": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", @@ -1997,6 +2272,16 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -2025,6 +2310,13 @@ "dev": true, "license": "MIT" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, "node_modules/boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -2142,6 +2434,16 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2197,7 +2499,6 @@ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2224,6 +2525,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -2325,6 +2636,60 @@ "node": "*" } }, + "node_modules/cheerio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", + "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.1.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.19.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2340,6 +2705,20 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chromium-bidi": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", + "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -2383,6 +2762,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-color": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz", + "integrity": "sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.64", + "es6-iterator": "^2.0.3", + "memoizee": "^0.4.15", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/cli-table3": { "version": "0.6.5", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", @@ -2445,7 +2841,6 @@ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2617,23 +3012,109 @@ "license": "MIT", "peer": true }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "node_modules/convert-svg-core": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/convert-svg-core/-/convert-svg-core-0.7.1.tgz", + "integrity": "sha512-qlQlT2pHMCG0NmZsh2yuYNYO9zKbOmHoWPT+ibuvpVjvA7l9aNhHS4debQeZGuR0mA4x/0a38zOTqBkkdYoTXQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/neocotic" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/neocotic" + } + ], + "license": "MIT", + "dependencies": { + "cheerio": "^1.1.0", + "file-url": "^4.0.0", + "puppeteer-core": "^24.10.1", + "tmp": "^0.2.3" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/convert-svg-core/node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/convert-svg-to-png": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/convert-svg-to-png/-/convert-svg-to-png-0.7.1.tgz", + "integrity": "sha512-XgLC/EmK0/GvdaHpCpEHCHL/ty/TDeezk8+AKWmUfEgUrYiwR9Tqrih9zfVWVzQYvn8mtjLvROv9xRQ7FHBo/Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/neocotic" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/neocotic" + } + ], + "license": "MIT", + "dependencies": { + "convert-svg-core": "^0.7.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT", "peer": true }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -2715,6 +3196,60 @@ "node": "*" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/death": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", @@ -2803,13 +3338,73 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/degenerator/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/degenerator/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -2823,6 +3418,13 @@ "node": ">= 0.8" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/diff": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", @@ -2833,6 +3435,13 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/difflib": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", @@ -2860,13 +3469,71 @@ "node": ">=8" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2911,6 +3578,43 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -2924,6 +3628,19 @@ "node": ">=8.6" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -2933,13 +3650,22 @@ "node": ">=6" } }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -2950,7 +3676,6 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -2961,7 +3686,6 @@ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -2975,7 +3699,6 @@ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -2986,6 +3709,62 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "dev": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3045,6 +3824,22 @@ "node": ">=0.8.0" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", @@ -3076,7 +3871,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3177,7 +3971,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@adraffy/ens-normalize": "1.11.1", "@noble/curves": "1.2.0", @@ -3197,7 +3990,6 @@ "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "1.3.2" }, @@ -3211,7 +4003,6 @@ "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 16" }, @@ -3225,7 +4016,6 @@ "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.19.2" } @@ -3235,16 +4025,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true, - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/ethers/node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/ethers/node_modules/ws": { "version": "8.21.0", @@ -3252,7 +4040,6 @@ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -3293,6 +4080,17 @@ "license": "MIT", "peer": true }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -3301,6 +4099,16 @@ "license": "MIT", "peer": true }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -3313,6 +4121,37 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dev": true, + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3321,6 +4160,13 @@ "license": "MIT", "peer": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -3376,6 +4222,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3393,6 +4249,19 @@ } } }, + "node_modules/file-url": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/file-url/-/file-url-4.0.0.tgz", + "integrity": "sha512-vRCdScQ6j3Ku6Kd7W1kZk9c++5SqD6Xz5Jotrjr/nkY714M14RFHy/AAVA2WQvpsqVAVgTbDrYyBpU205F0cLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3506,7 +4375,6 @@ "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3566,7 +4434,6 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3597,7 +4464,6 @@ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3623,7 +4489,6 @@ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3632,6 +4497,37 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ghost-testrpc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", @@ -3872,7 +4768,6 @@ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -4314,7 +5209,6 @@ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -4328,7 +5222,6 @@ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -4449,7 +5342,6 @@ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -4485,6 +5377,39 @@ "minimalistic-crypto-utils": "^1.0.1" } }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -4505,6 +5430,30 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -4560,6 +5509,23 @@ "dev": true, "license": "MIT" }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -4615,6 +5581,23 @@ "fp-ts": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz", + "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4715,6 +5698,13 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -4804,12 +5794,29 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-graph-algorithms": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/js-graph-algorithms/-/js-graph-algorithms-1.0.18.tgz", + "integrity": "sha512-Gu1wtWzXBzGeye/j9BuyplGHscwqKRZodp/0M1vyBc19RJpblSwKGu099KwwaTx9cRIV+Qupk8xUMfEiGfFqSA==", + "dev": true, + "license": "MIT", + "bin": { + "js-graphs": "src/jsgraphs.js" + } + }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "dev": true }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", @@ -4833,6 +5840,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -4918,6 +5932,16 @@ "node": ">=0.10.0" } }, + "node_modules/klaw": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-4.1.0.tgz", + "integrity": "sha512-1zGZ9MF9H22UnkpVeuaGKOjfA2t6WrfdrJmGjy16ykcjnKQDmHVX+KI477rpbGevz/5FD4MC3xf1oxylBgcaQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -4944,6 +5968,13 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5039,6 +6070,16 @@ "dev": true, "license": "ISC" }, + "node_modules/lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es5-ext": "~0.10.2" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5068,7 +6109,6 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -5086,6 +6126,26 @@ "safe-buffer": "^5.1.2" } }, + "node_modules/memoizee": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", + "dev": true, + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "es5-ext": "^0.10.64", + "es6-weak-map": "^2.0.3", + "event-emitter": "^0.3.5", + "is-promise": "^2.2.2", + "lru-queue": "^0.1.0", + "next-tick": "^1.1.0", + "timers-ext": "^0.1.7" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", @@ -5193,7 +6253,6 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5204,7 +6263,6 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5259,6 +6317,13 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -5421,6 +6486,23 @@ "license": "MIT", "peer": true }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "dev": true, + "license": "ISC" + }, "node_modules/node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", @@ -5484,6 +6566,19 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/number-to-bn": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", @@ -5519,7 +6614,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "peer": true, "dependencies": { "wrappy": "1" } @@ -5677,46 +6771,189 @@ "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" + "parse5": "^7.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5811,6 +7048,13 @@ "node": ">= 0.10" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5887,6 +7131,16 @@ "license": "MIT", "peer": true }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -5902,17 +7156,151 @@ "node": ">= 6" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-agent/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.1.tgz", + "integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1608973", + "puppeteer-core": "24.43.1", + "typed-query-selector": "^2.12.2" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.13.2", + "chromium-bidi": "14.0.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", + "webdriver-bidi-protocol": "0.4.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6093,6 +7481,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -6606,6 +8004,99 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/sol2uml": { + "version": "2.5.26", + "resolved": "https://registry.npmjs.org/sol2uml/-/sol2uml-2.5.26.tgz", + "integrity": "sha512-lq7ktw4yLDcgF8em5NXaCyKJmd6qgDIxSG/onWT4CGLQeWufauBnGHuTRf/yYY1DCPmpF9PENQ5hcRPSAIqRcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@aduh95/viz.js": "^3.7.0", + "@solidity-parser/parser": "^0.20.1", + "axios": "^1.13.6", + "axios-debug-log": "^1.0.0", + "cli-color": "^2.0.4", + "commander": "^12.1.0", + "convert-svg-to-png": "^0.7.1", + "debug": "^4.4.1", + "diff-match-patch": "^1.0.5", + "ethers": "^6.16.0", + "js-graph-algorithms": "^1.0.18", + "klaw": "^4.1.0", + "puppeteer": "^24.37.5" + }, + "bin": { + "sol2uml": "lib/sol2uml.js" + } + }, + "node_modules/sol2uml/node_modules/@solidity-parser/parser": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.20.2.tgz", + "integrity": "sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/sol2uml/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/solc": { "version": "0.8.26", "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.26.tgz", @@ -6862,6 +8353,18 @@ "node": ">= 0.8" } }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -7090,6 +8593,44 @@ "node": ">=8" } }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, "node_modules/temp": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/temp/-/temp-0.4.0.tgz", @@ -7099,6 +8640,16 @@ "node >=0.4.0" ] }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/through2": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", @@ -7110,6 +8661,20 @@ "readable-stream": "3" } }, + "node_modules/timers-ext": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", + "dev": true, + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -7281,6 +8846,13 @@ "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", "dev": true }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "dev": true, + "license": "ISC" + }, "node_modules/type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -7423,6 +8995,13 @@ "node": ">= 0.4" } }, + "node_modules/typed-query-selector": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", + "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -7481,8 +9060,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/universalify": { "version": "0.1.2", @@ -7785,6 +9363,50 @@ "@scure/bip39": "1.3.0" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz", + "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -7926,8 +9548,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "peer": true + "dev": true }, "node_modules/ws": { "version": "7.5.13", @@ -8004,6 +9625,17 @@ "node": ">=10" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -8026,6 +9658,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 302bb1d..2b94dbc 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@nomicfoundation/hardhat-foundry": "^1.2.1", "@nomicfoundation/hardhat-toolbox": "^6.1.2", "hardhat": "^2.28.6", + "sol2uml": "^2.5.26", "surya": "^0.4.13" } } diff --git a/script/CMTATWithRuleEngineScript.s.sol b/script/CMTATWithRuleEngineScript.s.sol index 782fdd1..93bc7c7 100644 --- a/script/CMTATWithRuleEngineScript.s.sol +++ b/script/CMTATWithRuleEngineScript.s.sol @@ -8,8 +8,8 @@ import {Script, console} from "forge-std/Script.sol"; import {ICMTATConstructor, CMTATStandardStandalone} from "CMTAT/deployment/CMTATStandardStandalone.sol"; import {IERC1643CMTAT} from "CMTAT/interfaces/tokenization/draft-IERC1643CMTAT.sol"; import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; -import {RuleEngine} from "src/deployment/RuleEngine.sol"; -import {RuleWhitelistMock} from "src/mocks/rules/validation/RuleWhitelistMock.sol"; +import {RuleEngine} from "../src/deployment/RuleEngine.sol"; +import {RuleWhitelistMock} from "../src/mocks/rules/validation/RuleWhitelistMock.sol"; /** * @title Example deployment of a CMTAT, a mock RuleWhitelistMock and a RuleEngine @@ -17,6 +17,12 @@ import {RuleWhitelistMock} from "src/mocks/rules/validation/RuleWhitelistMock.so * It is not a production deployment recipe for rule contracts. */ contract CMTATWithRuleEngineScript is Script { + /** + * @notice Deploys a CMTAT token, the demo whitelist rule and a RuleEngine, and wires them + * together. + * @dev Reads the deployer key from `PRIVATE_KEY`; the deployer becomes the token and engine + * admin. + */ function run() external { // Get env variable uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); diff --git a/script/RuleEngineScript.s.sol b/script/RuleEngineScript.s.sol index 9ea7086..17cec89 100644 --- a/script/RuleEngineScript.s.sol +++ b/script/RuleEngineScript.s.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.20; import {Script, console} from "forge-std/Script.sol"; -import {RuleEngine} from "src/deployment/RuleEngine.sol"; -import {RuleWhitelistMock} from "src/mocks/rules/validation/RuleWhitelistMock.sol"; +import {RuleEngine} from "../src/deployment/RuleEngine.sol"; +import {RuleWhitelistMock} from "../src/mocks/rules/validation/RuleWhitelistMock.sol"; import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; import { ValidationModuleRuleEngine @@ -21,7 +21,7 @@ import { * on that token, otherwise {setRuleEngine} reverts. * * The token is bound to the engine through the constructor: without it, every transfer, mint and burn - * reverts with `RuleEngine_ERC3643Compliance_UnauthorizedCaller`, because the compliance callbacks are + * reverts with `TokenBinding_UnauthorizedCaller`, because the compliance callbacks are * guarded by `onlyBoundToken`. * * The deployer and the zero address are added to the whitelist so the resulting deployment is usable @@ -29,6 +29,11 @@ import { * participant. Replace this with the real address list for anything beyond a demo. */ contract RuleEngineScript is Script { + /** + * @notice Deploys the demo whitelist rule and a RuleEngine bound to `CMTAT_ADDRESS`, then sets + * the engine on that token. + * @dev Reads the deployer key from `PRIVATE_KEY` and the token address from `CMTAT_ADDRESS`. + */ function run() external { // Get env variable uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); diff --git a/src/interfaces/IERC3643Compliance.sol b/src/interfaces/IERC3643Compliance.sol index 6e98898..2f698d0 100644 --- a/src/interfaces/IERC3643Compliance.sol +++ b/src/interfaces/IERC3643Compliance.sol @@ -4,53 +4,26 @@ pragma solidity ^0.8.20; /* ==== CMTAT === */ import {IERC3643ComplianceRead, IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; +/* ==== Interface === */ +import {ITokenBinding} from "./ITokenBinding.sol"; /** * @title IERC3643Compliance * @notice Compliance interface implemented by the RuleEngine for ERC-3643 tokens. + * @dev Token binding (`bindToken`, `unbindToken`, `isTokenBound`, and the `TokenBound` / + * `TokenUnbound` events) is inherited from the standard-agnostic {ITokenBinding}, so the + * registry can be reused outside a compliance context. Only the ERC-3643 specific parts are + * declared here. + * + * Security note: a "multi-tenant" setup means multiple token contracts share one RuleEngine + * instance (all are bound via {ITokenBinding-bindToken}). ERC-3643 callbacks do not carry the + * token address to rules, so stateful rules with per-address accounting are unsafe across + * mutually untrusted tokens. In that setup, all bound tokens must be equally trusted and + * governed together, and unbinding does not retroactively isolate rule state accumulated while + * they were shared. */ -interface IERC3643Compliance is IERC3643ComplianceRead, IERC3643IComplianceContract { - /* ============ Events ============ */ - /** - * @notice Emitted when a token is successfully bound to the compliance contract. - * @param token The address of the token that was bound. - */ - event TokenBound(address token); - - /** - * @notice Emitted when a token is successfully unbound from the compliance contract. - * @param token The address of the token that was unbound. - */ - event TokenUnbound(address token); - +interface IERC3643Compliance is IERC3643ComplianceRead, IERC3643IComplianceContract, ITokenBinding { /* ============ Functions ============ */ - /** - * @notice Associates a token contract with this compliance contract. - * @dev The compliance contract may restrict operations on the bound token - * according to the compliance logic. - * Security note: a "multi-tenant" setup means multiple token contracts - * share one RuleEngine instance (all are bound via {bindToken}). - * In that setup, all bound tokens must be equally trusted and governed together. - * ERC-3643 callbacks do not carry the token address to rules, so stateful - * rules with per-address accounting are unsafe across mutually untrusted tokens. - * Reverts if the token is already bound. - * Complexity: O(1). - * @param token The address of the token to bind. - */ - function bindToken(address token) external; - - /** - * @notice Removes the association of a token contract from this compliance contract. - * @dev Security note: unbinding does not retroactively isolate rule state from - * previously shared multi-token operation. "Multi-tenant" means one RuleEngine - * shared by multiple token contracts. Avoid multi-tenant binding unless - * all tokens are equally trusted and governed together. - * Reverts if the token is not currently bound. - * Complexity: O(1). - * @param token The address of the token to unbind. - */ - function unbindToken(address token) external; - /** * @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. @@ -69,17 +42,6 @@ interface IERC3643Compliance is IERC3643ComplianceRead, IERC3643IComplianceContr */ function destroyed(address from, uint256 value) external; - /** - * @notice Checks whether a token is currently bound to this compliance contract. - * @dev - * Complexity: O(1). - * Note that there are no guarantees on the ordering of values inside the array, - * and it may change when more values are added or removed. - * @param token The token address to verify. - * @return isBound True if the token is bound, false otherwise. - */ - function isTokenBound(address token) external view returns (bool isBound); - /** * @notice Returns the single token currently bound to this compliance contract. * @dev If multiple tokens are supported, consider using getTokenBounds(). diff --git a/src/interfaces/IERC3643ComplianceExtended.sol b/src/interfaces/IERC3643ComplianceExtended.sol index bd72577..74bf25a 100644 --- a/src/interfaces/IERC3643ComplianceExtended.sol +++ b/src/interfaces/IERC3643ComplianceExtended.sol @@ -3,75 +3,20 @@ pragma solidity ^0.8.20; import {IERC3643Compliance} from "./IERC3643Compliance.sol"; +import {ITokenBindingExtended} from "./ITokenBindingExtended.sol"; /** * @title IERC3643ComplianceExtended - * @notice Extends the ERC-3643 compliance interface with token self-binding management. + * @notice Extends the ERC-3643 compliance interface with the batch binding and token + * self-binding management provided by {ITokenBindingExtended}. + * @dev WARNING: this interface declares no function of its own, so `type(IERC3643ComplianceExtended).interfaceId` + * is `0x00000000` and must never be used for an ERC-165 check. The advertised ID is + * `ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID`, computed from + * {ITokenBindingExtended}, which declares the extended surface in full. + * + * None of the extended functions is part of the original ERC-3643 compliance interface; + * they belong to the token binding registry and are therefore declared in the standard-agnostic + * {ITokenBindingExtended}. This interface only ties them to the ERC-3643 compliance contract, + * where they must be restricted by implementation-specific compliance manager access control. */ -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); -} +interface IERC3643ComplianceExtended is IERC3643Compliance, ITokenBindingExtended {} diff --git a/src/interfaces/ITokenBinding.sol b/src/interfaces/ITokenBinding.sol new file mode 100644 index 0000000..760f5e1 --- /dev/null +++ b/src/interfaces/ITokenBinding.sol @@ -0,0 +1,61 @@ +//SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + +/** + * @title ITokenBinding + * @notice Minimal token binding registry: the set of token contracts allowed to call back + * into the contract implementing this interface. + * @dev Standard-agnostic. It carries no ERC-3643, ERC-1404 or RuleEngine semantics, so it can + * be reused by any engine (compliance, document, transfer...) that has to maintain an allowlist + * of tokens. The ERC-3643 specific part lives in {IERC3643Compliance}, which extends this + * interface. + */ +interface ITokenBinding { + /* ============ Events ============ */ + /** + * @notice Emitted when a token is successfully bound. + * @param token The address of the token that was bound. + */ + event TokenBound(address token); + + /** + * @notice Emitted when a token is successfully unbound. + * @param token The address of the token that was unbound. + */ + event TokenUnbound(address token); + + /* ============ Functions ============ */ + /** + * @notice Binds a token contract, allowing it to call the bound-token entry points. + * @dev Must be restricted by implementation-specific access control. + * Reverts on the zero address and if the token is already bound. + * Security note: every bound token shares the same contract state. A "multi-tenant" + * setup, where several token contracts are bound to the same instance, is only safe + * when all bound tokens are equally trusted and governed together, since the + * bound-token entry points do not necessarily carry the calling token address to the + * downstream logic. + * Complexity: O(1). + * @param token The address of the token to bind. + */ + function bindToken(address token) external; + + /** + * @notice Unbinds a token contract, revoking its access to the bound-token entry points. + * @dev Must be restricted by implementation-specific access control. + * Reverts if the token is not currently bound. + * Security note: unbinding is an administrative operation. It does not erase any state + * already accumulated for that token by the downstream logic. + * Complexity: O(1). + * @param token The address of the token to unbind. + */ + function unbindToken(address token) external; + + /** + * @notice Checks whether a token is currently bound. + * @dev Complexity: O(1). + * @param token The token address to verify. + * @return isBound True if the token is bound, false otherwise. + */ + function isTokenBound(address token) external view returns (bool isBound); +} diff --git a/src/interfaces/ITokenBindingExtended.sol b/src/interfaces/ITokenBindingExtended.sol new file mode 100644 index 0000000..eadfab7 --- /dev/null +++ b/src/interfaces/ITokenBindingExtended.sol @@ -0,0 +1,78 @@ +//SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + +import {ITokenBinding} from "./ITokenBinding.sol"; + +/** + * @title ITokenBindingExtended + * @notice Extends the token binding registry with batch operations, token self-binding + * and enumeration of the bound tokens. + * @dev Standard-agnostic, see {ITokenBinding}. None of these functions belongs to a token + * standard: they are conveniences of the registry itself. + */ +interface ITokenBindingExtended is ITokenBinding { + /* ============ Events ============ */ + /** + * @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); + + /* ============ Functions ============ */ + /** + * @notice Binds several token contracts in a single call. + * @dev Must be restricted by implementation-specific 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 Unbinds several token contracts in a single call. + * @dev Must be restricted by implementation-specific 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 bind and unbind itself. + * @dev Must be restricted by implementation-specific 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 Must be restricted by implementation-specific 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 bind and unbind itself. + * @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. + * @dev 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/TokenBindingStandaloneMock.sol b/src/mocks/TokenBindingStandaloneMock.sol new file mode 100644 index 0000000..658a217 --- /dev/null +++ b/src/mocks/TokenBindingStandaloneMock.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {TokenBindingModule} from "../modules/TokenBindingModule.sol"; + +/** + * @title TokenBindingStandaloneMock + * @notice Minimal engine reusing {TokenBindingModule} on its own, without any rule, ERC-1404 or + * ERC-3643 code. + * @dev Reference implementation showing what another project has to provide to embed the binding + * registry: an access control model wired to {_onlyTokenBindingManager}, and the bound-token + * entry points guarded by the `onlyBoundToken` modifier. It also covers the module's default + * {_authorizeTokenBindingChange}, which the RuleEngine replaces with the self-binding aware + * variant of {TokenBindingExtendedModule}. + * This contract is a reference implementation for testing and examples, not a production + * contract. + */ +contract TokenBindingStandaloneMock is TokenBindingModule, Ownable { + /** + * @notice Number of calls received on the bound-token entry point. + */ + uint256 public callCount; + + /** + * @notice Deploys the standalone binding registry. + * @param owner_ Address allowed to bind and unbind tokens. + */ + constructor(address owner_) Ownable(owner_) {} + + /** + * @notice Example bound-token entry point: only a bound token can call it. + */ + function notify() public virtual onlyBoundToken { + ++callCount; + } + + /** + * @dev Access control check using the Ownable pattern. + */ + function _onlyTokenBindingManager() internal virtual override onlyOwner {} +} diff --git a/src/modules/ERC3643ComplianceExtendedModule.sol b/src/modules/ERC3643ComplianceExtendedModule.sol index 4cbdd6f..ed80d62 100644 --- a/src/modules/ERC3643ComplianceExtendedModule.sol +++ b/src/modules/ERC3643ComplianceExtendedModule.sol @@ -2,83 +2,38 @@ pragma solidity ^0.8.20; -import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +/* ==== Modules === */ +import {ERC3643ComplianceModule} from "./ERC3643ComplianceModule.sol"; +import {TokenBindingExtendedModule} from "./TokenBindingExtendedModule.sol"; +import {TokenBindingModule} from "./TokenBindingModule.sol"; /* ==== Interface and other library === */ import {IERC3643ComplianceExtended} from "../interfaces/IERC3643ComplianceExtended.sol"; -import {ERC3643ComplianceModule} from "./ERC3643ComplianceModule.sol"; /** * @title ERC3643ComplianceExtendedModule - * @notice Extends the core ERC-3643 compliance module with batch binding and token self-binding. + * @notice ERC-3643 flavour of the extended token binding registry: it combines the ERC-3643 + * adapter {ERC3643ComplianceModule} with the batch binding and token self-binding provided by + * {TokenBindingExtendedModule}. + * @dev No logic of its own. Batch binding, self-binding approval and {getTokenBounds} are + * standard-agnostic and therefore implemented in {TokenBindingExtendedModule}; this contract only + * declares that the ERC-3643 deployment exposes them through {IERC3643ComplianceExtended}. */ -abstract contract ERC3643ComplianceExtendedModule is ERC3643ComplianceModule, IERC3643ComplianceExtended { - using EnumerableSet for EnumerableSet.AddressSet; - - /** - * @notice Tracks which tokens are allowed to bind and unbind themselves. - */ - mapping(address token => bool approved) private _tokenSelfBindingApproval; - +abstract contract ERC3643ComplianceExtendedModule is + TokenBindingExtendedModule, + ERC3643ComplianceModule, + IERC3643ComplianceExtended +{ /** - * @inheritdoc IERC3643ComplianceExtended - * @custom:security-note See {bindToken} for multi-tenant accounting risks. All tokens bound - * in this batch share the same rule state. Only bind tokens that are equally trusted and - * governed together. + * @dev Resolves the two inherited definitions of the binding authorization hook, reached + * through {TokenBindingExtendedModule} and through {ERC3643ComplianceModule}. The extended + * behaviour wins: the compliance manager, or an approved token binding itself. + * @param token The token being bound or unbound. */ - 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 + function _authorizeTokenBindingChange(address token) + internal virtual - override - onlyComplianceManager + override(TokenBindingModule, TokenBindingExtendedModule) { - 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. - * @param token The token being bound or unbound. - */ - function _authorizeComplianceBindingChange(address token) internal virtual override { - if (_msgSender() == token && _tokenSelfBindingApproval[token]) { - return; - } - _onlyComplianceManager(); + TokenBindingExtendedModule._authorizeTokenBindingChange(token); } } diff --git a/src/modules/ERC3643ComplianceModule.sol b/src/modules/ERC3643ComplianceModule.sol index 5473f1d..81b7c48 100644 --- a/src/modules/ERC3643ComplianceModule.sol +++ b/src/modules/ERC3643ComplianceModule.sol @@ -4,72 +4,39 @@ pragma solidity ^0.8.20; /* ==== OpenZeppelin === */ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +/* ==== Modules === */ +import {TokenBindingModule} from "./TokenBindingModule.sol"; /* ==== Interface and other library === */ import {IERC3643Compliance} from "../interfaces/IERC3643Compliance.sol"; -import {ERC3643ComplianceModuleInvariantStorage} from "./library/ERC3643ComplianceModuleInvariantStorage.sol"; /** * @title ERC3643ComplianceModule - * @notice Core ERC-3643 compliance module: tracks the tokens bound to this engine. + * @notice ERC-3643 adapter over the standard-agnostic {TokenBindingModule}: it adds the + * ERC-3643 specific view {getTokenBound} and names the binding manager in compliance terms. + * @dev The binding registry itself (storage, `bindToken` / `unbindToken` / `isTokenBound`, the + * `onlyBoundToken` modifier) lives in {TokenBindingModule} and can be reused outside any + * compliance context. Everything ERC-3643 specific is here: + * - {getTokenBound}, the single-token view required by the ERC-3643 compliance interface; + * - {_onlyComplianceManager}, the access control hook the deployable contracts implement, wired + * to the generic {_onlyTokenBindingManager} hook. + * + * The ERC-3643 compliance callbacks themselves (`transferred`, `created`, `destroyed`) are + * implemented by `RuleEngineBase`, since they depend on the rules rather than on the binding. + * @custom:security-note Operation rules (stateful rules such as `RuleConditionalTransferLightMock` + * or `RuleMintAllowanceMock`) maintain per-address accounting that is shared across all bound + * tokens, and the ERC-3643 callbacks do not carry the calling token address to the rules. + * Binding tokens from different issuers to the same engine will silently cross-contaminate their + * accounting. Only bind tokens that are equally trusted and governed together. */ -abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance, ERC3643ComplianceModuleInvariantStorage { +abstract contract ERC3643ComplianceModule is TokenBindingModule, IERC3643Compliance { /* ==== Type declaration === */ using EnumerableSet for EnumerableSet.AddressSet; - /* ==== State Variables === */ - // Token binding tracking - /** - * @notice Set of tokens allowed to call the compliance callbacks. - */ - EnumerableSet.AddressSet internal _boundTokens; - - /* ==== Modifier === */ - modifier onlyBoundToken() { - _checkBoundToken(); - _; - } - - modifier onlyComplianceManager() { - _onlyComplianceManager(); - _; - } /*////////////////////////////////////////////////////////////// PUBLIC/public FUNCTIONS //////////////////////////////////////////////////////////////*/ - /* ============ State functions ============ */ - /** - * @inheritdoc 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. - * @custom:security-note Operation rules (stateful rules such as `RuleConditionalTransferLightMock` - * or `RuleMintAllowanceMock`) maintain per-address accounting that is shared across all bound tokens. - * Binding tokens from different issuers to the same engine will silently cross-contaminate - * their accounting. Only bind tokens that are equally trusted and governed together. - */ - function bindToken(address token) public virtual override { - _authorizeComplianceBindingChange(token); - _bindToken(token); - } - - /** - * @inheritdoc 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. - */ - function unbindToken(address token) public virtual override { - _authorizeComplianceBindingChange(token); - _unbindToken(token); - } - - /// @inheritdoc IERC3643Compliance - function isTokenBound(address token) public view virtual override returns (bool) { - return _boundTokens.contains(token); - } - + /* ============ View functions ============ */ /// @inheritdoc IERC3643Compliance function getTokenBound() public view virtual override returns (address) { if (_boundTokens.length() > 0) { @@ -86,46 +53,18 @@ abstract contract ERC3643ComplianceModule is Context, IERC3643Compliance, ERC364 //////////////////////////////////////////////////////////////*/ /** - * @dev Removes a token from the bound set. - * @param token The token to unbind; reverts when it is not currently bound. - */ - function _unbindToken(address token) internal virtual { - // remove() returns false when the token was not bound, so a separate - // contains() lookup is unnecessary. - require(_boundTokens.remove(token), RuleEngine_ERC3643Compliance_TokenNotBound()); - - emit TokenUnbound(token); - } - - /** - * @dev Adds a token to the bound set. - * @param token The token to bind; reverts on the zero address or when already bound. + * @dev In an ERC-3643 deployment, the account managing the token bindings is the compliance + * manager, so the generic binding manager hook delegates to {_onlyComplianceManager}. */ - function _bindToken(address token) internal virtual { - require(token != address(0), RuleEngine_ERC3643Compliance_InvalidTokenAddress()); - // add() returns false when the token is already bound, so a separate - // contains() lookup is unnecessary. - require(_boundTokens.add(token), RuleEngine_ERC3643Compliance_TokenAlreadyBound()); - emit TokenBound(token); + function _onlyTokenBindingManager() internal virtual override { + _onlyComplianceManager(); } /** - * @dev Authorization hook for bind/unbind, implemented by the deployable contracts. - * @param token The token being bound or unbound. - */ - function _authorizeComplianceBindingChange(address token) internal virtual; - - /** - * @dev Access control hook guarding compliance management operations. + * @dev Access control hook guarding compliance management operations, implemented by the + * deployable contracts. Binding management is gated by this hook through + * {_onlyTokenBindingManager}; the generic `onlyTokenBindingManager` modifier of + * {TokenBindingModule} is therefore the compliance manager check in an ERC-3643 deployment. */ function _onlyComplianceManager() internal virtual; - - /** - * @dev Reverts when the caller is not a bound token. - */ - function _checkBoundToken() internal view virtual { - if (!_boundTokens.contains(_msgSender())) { - revert RuleEngine_ERC3643Compliance_UnauthorizedCaller(); - } - } } diff --git a/src/modules/TokenBindingExtendedModule.sol b/src/modules/TokenBindingExtendedModule.sol new file mode 100644 index 0000000..f0eb976 --- /dev/null +++ b/src/modules/TokenBindingExtendedModule.sol @@ -0,0 +1,99 @@ +//SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + +/* ==== OpenZeppelin === */ +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +/* ==== Interface and other library === */ +import {ITokenBindingExtended} from "../interfaces/ITokenBindingExtended.sol"; +import {TokenBindingModule} from "./TokenBindingModule.sol"; + +/** + * @title TokenBindingExtendedModule + * @notice Extends the standard-agnostic {TokenBindingModule} with batch binding, token + * self-binding and enumeration of the bound tokens. + * @dev Like its parent, this module carries no token-standard semantics: self-binding exists + * because a token contract may want to register itself (as ERC-3643 `setCompliance` does), but + * nothing here is specific to ERC-3643. + */ +abstract contract TokenBindingExtendedModule is TokenBindingModule, ITokenBindingExtended { + using EnumerableSet for EnumerableSet.AddressSet; + + /** + * @notice Tracks which tokens are allowed to bind and unbind themselves. + */ + mapping(address token => bool approved) private _tokenSelfBindingApproval; + + /*////////////////////////////////////////////////////////////// + PUBLIC/public FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /* ============ State functions ============ */ + /** + * @inheritdoc ITokenBindingExtended + * @custom:security-note See {bindToken} for multi-tenant state risks. All tokens bound + * in this batch share the same downstream state. Only bind tokens that are equally trusted + * and governed together. + */ + function bindTokens(address[] calldata tokens) public virtual override onlyTokenBindingManager { + for (uint256 i = 0; i < tokens.length; ++i) { + _bindToken(tokens[i]); + } + } + + /// @inheritdoc ITokenBindingExtended + function unbindTokens(address[] calldata tokens) public virtual override onlyTokenBindingManager { + for (uint256 i = 0; i < tokens.length; ++i) { + _unbindToken(tokens[i]); + } + } + + /// @inheritdoc ITokenBindingExtended + function setTokenSelfBindingApproval(address token, bool approved) public virtual override onlyTokenBindingManager { + require(token != address(0), TokenBinding_InvalidTokenAddress()); + _tokenSelfBindingApproval[token] = approved; + emit TokenSelfBindingApprovalSet(token, approved); + } + + /// @inheritdoc ITokenBindingExtended + function setTokenSelfBindingApprovalBatch(address[] calldata tokens, bool approved) + public + virtual + override + onlyTokenBindingManager + { + for (uint256 i = 0; i < tokens.length; ++i) { + address token = tokens[i]; + require(token != address(0), TokenBinding_InvalidTokenAddress()); + _tokenSelfBindingApproval[token] = approved; + } + emit TokenSelfBindingApprovalBatchSet(tokens, approved); + } + + /* ============ View functions ============ */ + /// @inheritdoc ITokenBindingExtended + function isTokenSelfBindingApproved(address token) public view virtual override returns (bool) { + return _tokenSelfBindingApproval[token]; + } + + /// @inheritdoc ITokenBindingExtended + function getTokenBounds() public view virtual override returns (address[] memory) { + return _boundTokens.values(); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL/PRIVATE FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Authorizes bind/unbind operations. + * Allows the binding manager, or approved token self-calls (used by ERC-3643 `setCompliance`). + * @param token The token being bound or unbound. + */ + function _authorizeTokenBindingChange(address token) internal virtual override { + if (_msgSender() == token && _tokenSelfBindingApproval[token]) { + return; + } + _onlyTokenBindingManager(); + } +} diff --git a/src/modules/TokenBindingModule.sol b/src/modules/TokenBindingModule.sol new file mode 100644 index 0000000..f42d085 --- /dev/null +++ b/src/modules/TokenBindingModule.sol @@ -0,0 +1,148 @@ +//SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + +/* ==== OpenZeppelin === */ +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +/* ==== Interface and other library === */ +import {ITokenBinding} from "../interfaces/ITokenBinding.sol"; +import {TokenBindingModuleInvariantStorage} from "./library/TokenBindingModuleInvariantStorage.sol"; + +/** + * @title TokenBindingModule + * @notice Standard-agnostic token binding registry (an allowlist) implementing {ITokenBinding}: + * it stores the set of tokens allowed to call the bound-token entry points of the contract + * embedding it. + * @dev This module deliberately knows nothing about ERC-3643, ERC-1404, rules or the RuleEngine. + * It only depends on OpenZeppelin's {Context} and {EnumerableSet}, so it can be reused as-is by + * any project that has to bind tokens. It provides: + * - the allowlist storage and `bindToken` / `unbindToken` / `isTokenBound`; + * - the {onlyBoundToken} modifier gating the bound-token entry points; + * - two access control hooks left to the deployment: {_authorizeTokenBindingChange}, which + * authorizes a bind/unbind, and {_onlyTokenBindingManager}, the manager check it defaults to. + * + * The ERC-3643 vocabulary (`compliance`, `getTokenBound`, `created` / `destroyed`) lives in + * {ERC3643ComplianceModule}, which is a thin adapter over this module. + */ +abstract contract TokenBindingModule is Context, ITokenBinding, TokenBindingModuleInvariantStorage { + /* ==== Type declaration === */ + using EnumerableSet for EnumerableSet.AddressSet; + + /* ==== State Variables === */ + // Token binding tracking + /** + * @notice Set of tokens allowed to call the bound-token entry points. + */ + EnumerableSet.AddressSet internal _boundTokens; + + /* ==== Modifier === */ + /** + * @dev Restricts a function to the tokens currently bound. + */ + modifier onlyBoundToken() { + _checkBoundToken(); + _; + } + + /** + * @dev Restricts a function to the account allowed to manage the bindings. + */ + modifier onlyTokenBindingManager() { + _onlyTokenBindingManager(); + _; + } + + /*////////////////////////////////////////////////////////////// + PUBLIC/public FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /* ============ State functions ============ */ + /** + * @inheritdoc ITokenBinding + * @dev Authorized by {_authorizeTokenBindingChange}. + * @custom:security-note "Multi-tenant" means one instance is shared by multiple token + * contracts. Downstream state (for the RuleEngine: the per-address accounting held by + * stateful rules) is shared across all bound tokens, so binding tokens from different + * issuers silently cross-contaminates it. Only bind tokens that are equally trusted and + * governed together. + */ + function bindToken(address token) public virtual override { + _authorizeTokenBindingChange(token); + _bindToken(token); + } + + /** + * @inheritdoc ITokenBinding + * @dev Authorized by {_authorizeTokenBindingChange}. + * Operator warning: unbinding is an administrative operation and does not erase any state + * already stored downstream in a previously shared ("multi-tenant") setup. + */ + function unbindToken(address token) public virtual override { + _authorizeTokenBindingChange(token); + _unbindToken(token); + } + + /* ============ View functions ============ */ + /// @inheritdoc ITokenBinding + function isTokenBound(address token) public view virtual override returns (bool) { + return _boundTokens.contains(token); + } + + /*////////////////////////////////////////////////////////////// + INTERNAL/PRIVATE FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /** + * @dev Adds a token to the bound set. + * @param token The token to bind; reverts on the zero address or when already bound. + */ + function _bindToken(address token) internal virtual { + require(token != address(0), TokenBinding_InvalidTokenAddress()); + // add() returns false when the token is already bound, so a separate + // contains() lookup is unnecessary. + require(_boundTokens.add(token), TokenBinding_TokenAlreadyBound()); + emit TokenBound(token); + } + + /** + * @dev Removes a token from the bound set. + * @param token The token to unbind; reverts when it is not currently bound. + */ + function _unbindToken(address token) internal virtual { + // remove() returns false when the token was not bound, so a separate + // contains() lookup is unnecessary. + require(_boundTokens.remove(token), TokenBinding_TokenNotBound()); + + emit TokenUnbound(token); + } + + /** + * @dev Authorization hook for bind/unbind, receiving the token being bound or unbound. + * Defaults to the binding manager check, which ignores the token; {TokenBindingExtendedModule} + * overrides it to also allow approved token self-calls. + */ + function _authorizeTokenBindingChange( + address /* token */ + ) + internal + virtual + { + _onlyTokenBindingManager(); + } + + /** + * @dev Access control hook guarding binding management operations, implemented by the + * deployable contracts. + */ + function _onlyTokenBindingManager() internal virtual; + + /** + * @dev Reverts when the caller is not a bound token. + */ + function _checkBoundToken() internal view virtual { + if (!_boundTokens.contains(_msgSender())) { + revert TokenBinding_UnauthorizedCaller(); + } + } +} diff --git a/src/modules/library/ComplianceInterfaceId.sol b/src/modules/library/ComplianceInterfaceId.sol index e4f4c29..40ec008 100644 --- a/src/modules/library/ComplianceInterfaceId.sol +++ b/src/modules/library/ComplianceInterfaceId.sol @@ -2,23 +2,52 @@ pragma solidity ^0.8.20; +/* ==== CMTAT === */ +import {IERC3643ComplianceRead, IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; +import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; +/* ==== Interfaces === */ +import {IERC3643Compliance} from "../../interfaces/IERC3643Compliance.sol"; +import {ITokenBinding} from "../../interfaces/ITokenBinding.sol"; +import {ITokenBindingExtended} from "../../interfaces/ITokenBindingExtended.sol"; + /** * @title ComplianceInterfaceId * @dev ERC-165 interface IDs used by RuleEngine for compliance interfaces. + * + * Each ID is computed from the interfaces themselves rather than hardcoded, because + * `type(I).interfaceId` covers only the functions declared *directly* on `I`, never the inherited + * ones. For an interface that inherits, the flattened ID is the XOR of its own ID with the IDs of + * its parents, which is what the expressions below spell out. A marker interface that declares + * nothing of its own, such as {IERC3643ComplianceExtended}, has a `type(...).interfaceId` of + * `0x00000000` — use the constants here, never that expression. + * + * The values are unchanged from the hardcoded literals they replace, and the tests pin them + * against both the flattened helper interfaces and the literal wire values. */ library ComplianceInterfaceId { /** * @notice ERC-165 interface ID of the core ERC-3643 compliance interface. + * @dev Flattened: `IERC3643Compliance` (created, destroyed, getTokenBound) with its parents + * `ITokenBinding` (bindToken, unbindToken, isTokenBound), `IERC3643ComplianceRead` (canTransfer) + * and `IERC3643IComplianceContract` (transferred). Equals `0x3144991c`. */ - bytes4 public constant ERC3643_COMPLIANCE_INTERFACE_ID = 0x3144991c; + bytes4 public constant ERC3643_COMPLIANCE_INTERFACE_ID = + type(IERC3643Compliance).interfaceId ^ type(ITokenBinding).interfaceId + ^ type(IERC3643ComplianceRead).interfaceId ^ type(IERC3643IComplianceContract).interfaceId; /** * @notice ERC-165 interface ID of the extended ERC-3643 compliance interface. + * @dev The extended surface is declared in full by `ITokenBindingExtended` — batch binding, + * token self-binding and `getTokenBounds` — so its own ID is already the flattened one. + * `IERC3643ComplianceExtended` adds no function of its own. Equals `0x646ba2be`. */ - bytes4 public constant ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID = 0x646ba2be; + bytes4 public constant ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID = type(ITokenBindingExtended).interfaceId; /** * @notice ERC-165 interface ID of the ERC-7551 compliance interface. + * @dev `IERC7551Compliance` declares only `canTransferFrom`; `canTransfer`, inherited from + * `IERC3643ComplianceRead`, is advertised through {ERC3643_COMPLIANCE_INTERFACE_ID} instead. + * This is the subset interface CMTAT uses. Equals `0x7157797f`. */ - bytes4 public constant IERC7551_COMPLIANCE_INTERFACE_ID = 0x7157797f; + bytes4 public constant IERC7551_COMPLIANCE_INTERFACE_ID = type(IERC7551Compliance).interfaceId; } diff --git a/src/modules/library/ERC1404InterfaceId.sol b/src/modules/library/ERC1404InterfaceId.sol index 48d375c..b92e8ac 100644 --- a/src/modules/library/ERC1404InterfaceId.sol +++ b/src/modules/library/ERC1404InterfaceId.sol @@ -2,13 +2,20 @@ pragma solidity ^0.8.20; +/* ==== CMTAT === */ +import {IERC1404} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; + /** * @title ERC1404InterfaceId * @dev ERC-165 interface IDs for ERC-1404 interfaces. + * Computed from the interface itself rather than hardcoded. + * The ID of the ERC-1404 *extension* is provided by CMTAT in `ERC1404ExtendInterfaceId`. */ library ERC1404InterfaceId { /** * @notice ERC-165 interface ID of the ERC-1404 restriction interface. + * @dev `IERC1404` inherits nothing, so its own ID is already the flattened one: + * detectTransferRestriction and messageForTransferRestriction. Equals `0xab84a5c8`. */ - bytes4 public constant IERC1404_INTERFACE_ID = 0xab84a5c8; + bytes4 public constant IERC1404_INTERFACE_ID = type(IERC1404).interfaceId; } diff --git a/src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol b/src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol deleted file mode 100644 index d6c9c04..0000000 --- a/src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -pragma solidity ^0.8.20; - -/** - * @title ERC3643ComplianceModuleInvariantStorage - * @notice Holds the custom errors raised by the ERC-3643 compliance module. - */ -abstract contract ERC3643ComplianceModuleInvariantStorage { - /* ==== Errors === */ - error RuleEngine_ERC3643Compliance_InvalidTokenAddress(); - error RuleEngine_ERC3643Compliance_TokenAlreadyBound(); - error RuleEngine_ERC3643Compliance_TokenNotBound(); - error RuleEngine_ERC3643Compliance_UnauthorizedCaller(); -} diff --git a/src/modules/library/RuleInterfaceId.sol b/src/modules/library/RuleInterfaceId.sol index 8456240..31998b5 100644 --- a/src/modules/library/RuleInterfaceId.sol +++ b/src/modules/library/RuleInterfaceId.sol @@ -2,15 +2,40 @@ pragma solidity ^0.8.20; +/* ==== OpenZeppelin === */ +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +/* ==== CMTAT === */ +import {IRuleEngine} from "CMTAT/interfaces/engine/IRuleEngine.sol"; +import {IERC1404, IERC1404Extend} from "CMTAT/interfaces/tokenization/draft-IERC1404.sol"; +import {IERC7551Compliance} from "CMTAT/interfaces/tokenization/draft-IERC7551.sol"; +import {IERC3643ComplianceRead, IERC3643IComplianceContract} from "CMTAT/interfaces/tokenization/IERC3643Partial.sol"; +/* ==== Interfaces === */ +import {IRule} from "../../interfaces/IRule.sol"; + /** * @title RuleInterfaceId * @dev ERC-165 interface ID for the full IRule hierarchy (XOR of all function selectors). - * Computed from the flattened IRuleAllFunctions mock interface. - * See src/mocks/IRuleInterfaceIdHelper.sol for the detailed computation. + * + * Computed from the interfaces themselves rather than hardcoded. `type(IRule).interfaceId` alone + * covers only `canReturnTransferRestrictionCode`, the single function `IRule` declares directly, so + * the flattened ID XORs in every parent of the hierarchy. `IRuleEngineERC1404` contributes nothing + * of its own — it declares no function, and its `type(...).interfaceId` is `0x00000000` — so it does + * not appear below; its parents do. + * + * The value is unchanged from the hardcoded literal it replaces, and is pinned by the tests against + * both the flattened `IRuleAllFunctions` helper and the literal wire value. */ library RuleInterfaceId { /** * @notice ERC-165 interface ID advertised by every rule usable by the RuleEngine. + * @dev Flattened over: `IRule` (canReturnTransferRestrictionCode), `IRuleEngine` + * (transferred with spender), `IERC7551Compliance` (canTransferFrom), `IERC3643ComplianceRead` + * (canTransfer), `IERC3643IComplianceContract` (transferred), `IERC1404` + * (detectTransferRestriction, messageForTransferRestriction), `IERC1404Extend` + * (detectTransferRestrictionFrom) and `IERC165` (supportsInterface). Equals `0x2497d6cb`. */ - bytes4 public constant IRULE_INTERFACE_ID = 0x2497d6cb; + bytes4 public constant IRULE_INTERFACE_ID = + type(IRule).interfaceId ^ type(IRuleEngine).interfaceId ^ type(IERC7551Compliance).interfaceId + ^ type(IERC3643ComplianceRead).interfaceId ^ type(IERC3643IComplianceContract).interfaceId + ^ type(IERC1404).interfaceId ^ type(IERC1404Extend).interfaceId ^ type(IERC165).interfaceId; } diff --git a/src/modules/library/TokenBindingModuleInvariantStorage.sol b/src/modules/library/TokenBindingModuleInvariantStorage.sol new file mode 100644 index 0000000..e96f83d --- /dev/null +++ b/src/modules/library/TokenBindingModuleInvariantStorage.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MPL-2.0 + +pragma solidity ^0.8.20; + +/** + * @title TokenBindingModuleInvariantStorage + * @notice Holds the custom errors raised by the token binding module. + * @dev Standard-agnostic: these errors describe the binding registry itself, not the + * compliance standard built on top of it. Reused as-is by any project embedding + * {TokenBindingModule}. + */ +abstract contract TokenBindingModuleInvariantStorage { + /* ==== Errors === */ + /// @notice Thrown when the zero address is passed as a token to bind or to approve. + error TokenBinding_InvalidTokenAddress(); + /// @notice Thrown when binding a token that is already bound. + error TokenBinding_TokenAlreadyBound(); + /// @notice Thrown when unbinding a token that is not currently bound. + error TokenBinding_TokenNotBound(); + /// @notice Thrown when a caller that is not a bound token calls a bound-token entry point. + error TokenBinding_UnauthorizedCaller(); +} diff --git a/test/HelperContract.sol b/test/HelperContract.sol index ca4ddb4..4a62f93 100644 --- a/test/HelperContract.sol +++ b/test/HelperContract.sol @@ -13,7 +13,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 {ERC3643ComplianceModuleInvariantStorage} from "src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol"; +import {TokenBindingModuleInvariantStorage} from "src/modules/library/TokenBindingModuleInvariantStorage.sol"; // RuleConditionalTransfer import {RuleConditionalTransferLightMock} from "src/mocks/rules/operation/RuleConditionalTransferLightMock.sol"; import { diff --git a/test/HelperContractOwnable.sol b/test/HelperContractOwnable.sol index 15e788c..ee1acce 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 {ERC3643ComplianceModuleInvariantStorage} from "src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol"; +import {TokenBindingModuleInvariantStorage} from "src/modules/library/TokenBindingModuleInvariantStorage.sol"; // RuleConditionalTransfer import {RuleConditionalTransferLightMock} from "src/mocks/rules/operation/RuleConditionalTransferLightMock.sol"; import { diff --git a/test/RuleEngine/AccessControl/RuleEngineAccessControl.sol b/test/RuleEngine/AccessControl/RuleEngineAccessControl.sol index e28845c..4cc83a1 100644 --- a/test/RuleEngine/AccessControl/RuleEngineAccessControl.sol +++ b/test/RuleEngine/AccessControl/RuleEngineAccessControl.sol @@ -91,9 +91,7 @@ contract RuleEngineTest is Test, HelperContract { function testCannotAttackerOperateOnTransfer() public { // Act vm.prank(ATTACKER); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngineMock.transferred(address(0), ADDRESS1, ADDRESS2, 10); } diff --git a/test/RuleEngine/ERC3643Compliance.t.sol b/test/RuleEngine/ERC3643Compliance.t.sol index d9c075d..1457b47 100644 --- a/test/RuleEngine/ERC3643Compliance.t.sol +++ b/test/RuleEngine/ERC3643Compliance.t.sol @@ -6,9 +6,8 @@ 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"; -import { - ERC3643ComplianceModuleInvariantStorage -} from "../../src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol"; +import {ITokenBinding} from "../../src/interfaces/ITokenBinding.sol"; +import {TokenBindingModuleInvariantStorage} from "../../src/modules/library/TokenBindingModuleInvariantStorage.sol"; // Minimal mock ERC-3643 token to simulate calls to RuleEngine contract ERC3643MockToken { @@ -86,15 +85,15 @@ contract RuleEngineTest is Test, HelperContract { vm.startPrank(operator); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenBound(address(token1)); + emit ITokenBinding.TokenBound(address(token1)); ruleEngine.bindToken(address(token1)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenBound(address(token2)); + emit ITokenBinding.TokenBound(address(token2)); ruleEngine.bindToken(address(token2)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenBound(address(token3)); + emit ITokenBinding.TokenBound(address(token3)); ruleEngine.bindToken(address(token3)); vm.stopPrank(); @@ -126,15 +125,15 @@ contract RuleEngineTest is Test, HelperContract { // Expect events for each unbind vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenUnbound(address(token2)); + emit ITokenBinding.TokenUnbound(address(token2)); ruleEngine.unbindToken(address(token2)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenUnbound(address(token1)); + emit ITokenBinding.TokenUnbound(address(token1)); ruleEngine.unbindToken(address(token1)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenUnbound(address(token3)); + emit ITokenBinding.TokenUnbound(address(token3)); ruleEngine.unbindToken(address(token3)); vm.stopPrank(); @@ -168,15 +167,13 @@ contract RuleEngineTest is Test, HelperContract { } function testCannotBoundIfInvalidAddress() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(admin); ruleEngine.bindToken(address(ZERO_ADDRESS)); } function testCannotUnBoundIfTokenIsNotBound() public { - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenNotBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenNotBound.selector); vm.prank(admin); ruleEngine.unbindToken(address(0x100)); } @@ -187,7 +184,7 @@ contract RuleEngineTest is Test, HelperContract { ruleEngine.bindToken(address(0x1)); // Assert - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenAlreadyBound.selector); vm.prank(admin); ruleEngine.bindToken(address(0x1)); } @@ -272,9 +269,7 @@ contract RuleEngineTest is Test, HelperContract { } function testCannotSetTokenSelfBindingApprovalForZeroAddress() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(operator); ruleEngine.setTokenSelfBindingApproval(address(0), true); } @@ -323,9 +318,7 @@ contract RuleEngineTest is Test, HelperContract { tokens[0] = address(token1); tokens[1] = address(0); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(operator); ruleEngine.setTokenSelfBindingApprovalBatch(tokens, true); } @@ -390,9 +383,7 @@ contract RuleEngineTest is Test, HelperContract { tokens[0] = address(token1); tokens[1] = address(0); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(operator); ruleEngine.bindTokens(tokens); } @@ -405,7 +396,7 @@ contract RuleEngineTest is Test, HelperContract { vm.prank(operator); ruleEngine.bindToken(address(token1)); - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenAlreadyBound.selector); vm.prank(operator); ruleEngine.bindTokens(tokens); } @@ -418,29 +409,23 @@ contract RuleEngineTest is Test, HelperContract { vm.prank(operator); ruleEngine.bindToken(address(token1)); - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenNotBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenNotBound.selector); vm.prank(operator); ruleEngine.unbindTokens(tokens); } function testCannotCreatedIfNotBound() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngine.created(user1, 100); } function testCannotDestroyedIfNotBound() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngine.destroyed(user2, 50); } function testCannotTransferredIfNotBound() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngine.transferred(user1, user2, 200); } diff --git a/test/RuleEngine/ERC3643TokenIntegration.t.sol b/test/RuleEngine/ERC3643TokenIntegration.t.sol index aa863bb..0e8f222 100644 --- a/test/RuleEngine/ERC3643TokenIntegration.t.sol +++ b/test/RuleEngine/ERC3643TokenIntegration.t.sol @@ -8,7 +8,7 @@ import {ERC3643TokenMock} from "src/mocks/ERC3643TokenMock.sol"; import {RuleWhitelistMock} from "src/mocks/rules/validation/RuleWhitelistMock.sol"; import {RuleMintAllowanceMock} from "src/mocks/rules/operation/RuleMintAllowanceMock.sol"; import {IRule} from "src/interfaces/IRule.sol"; -import {ERC3643ComplianceModuleInvariantStorage} from "src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol"; +import {TokenBindingModuleInvariantStorage} from "src/modules/library/TokenBindingModuleInvariantStorage.sol"; /** * @title ERC3643TokenIntegrationTest @@ -19,7 +19,7 @@ import {ERC3643ComplianceModuleInvariantStorage} from "src/modules/library/ERC36 * burn. An ERC-3643 token never reaches the 4-argument `transferred(spender, ...)` overload, which * belongs to CMTAT's `IRuleEngine`. */ -contract ERC3643TokenIntegrationTest is Test, ERC3643ComplianceModuleInvariantStorage { +contract ERC3643TokenIntegrationTest is Test, TokenBindingModuleInvariantStorage { RuleEngine engine; ERC3643TokenMock token; RuleWhitelistMock whitelist; @@ -108,7 +108,7 @@ contract ERC3643TokenIntegrationTest is Test, ERC3643ComplianceModuleInvariantSt /// @notice Only a bound token may call the ERC-3643 callbacks. function testUnboundCallerCannotCallTransferred() public { vm.prank(CAROL); - vm.expectRevert(RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector); + vm.expectRevert(TokenBinding_UnauthorizedCaller.selector); engine.transferred(ALICE, BOB, 1); } @@ -130,10 +130,10 @@ contract ERC3643TokenIntegrationTest is Test, ERC3643ComplianceModuleInvariantSt /// @notice created() and destroyed() are restricted to bound tokens. function testUnboundCallerCannotCallCreatedOrDestroyed() public { vm.startPrank(CAROL); - vm.expectRevert(RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector); + vm.expectRevert(TokenBinding_UnauthorizedCaller.selector); engine.created(BOB, 1); - vm.expectRevert(RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector); + vm.expectRevert(TokenBinding_UnauthorizedCaller.selector); engine.destroyed(ALICE, 1); vm.stopPrank(); } diff --git a/test/RuleEngine/IRuleInterfaceId.t.sol b/test/RuleEngine/IRuleInterfaceId.t.sol index f5cefbc..b97f30e 100644 --- a/test/RuleEngine/IRuleInterfaceId.t.sol +++ b/test/RuleEngine/IRuleInterfaceId.t.sol @@ -3,6 +3,11 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {IRuleInterfaceIdHelper} from "src/mocks/IRuleInterfaceIdHelper.sol"; +import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.sol"; +import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol"; +import {IERC3643ComplianceExtended} from "src/interfaces/IERC3643ComplianceExtended.sol"; +import {ITokenBindingExtended} from "src/interfaces/ITokenBindingExtended.sol"; +import {RuleInterfaceId} from "src/modules/library/RuleInterfaceId.sol"; /** * @title Tests to verify IRule ERC-165 interface ID computation @@ -63,4 +68,39 @@ contract IRuleInterfaceIdTest is Test { emit log_named_bytes32("IERC7551Compliance", bytes32(iERC7551Compliance)); emit log_named_bytes32("IERC165", bytes32(iERC165)); } + + /** + * @notice Pins every advertised interface ID to the wire value integrators depend on. + * @dev The constants are computed from the interfaces (see `ComplianceInterfaceId`, + * `RuleInterfaceId`, `ERC1404InterfaceId`) rather than hardcoded, so this test is what turns an + * upstream interface change into a failure here instead of a silent change in what + * `supportsInterface` answers. These literals must never change without a major version. + */ + function testInterfaceIdConstantsMatchTheirWireValues() public pure { + assertEq(RuleInterfaceId.IRULE_INTERFACE_ID, bytes4(0x2497d6cb), "IRule"); + assertEq(ComplianceInterfaceId.ERC3643_COMPLIANCE_INTERFACE_ID, bytes4(0x3144991c), "IERC3643Compliance"); + assertEq( + ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID, + bytes4(0x646ba2be), + "IERC3643ComplianceExtended" + ); + assertEq(ComplianceInterfaceId.IERC7551_COMPLIANCE_INTERFACE_ID, bytes4(0x7157797f), "IERC7551Compliance"); + assertEq(ERC1404InterfaceId.IERC1404_INTERFACE_ID, bytes4(0xab84a5c8), "IERC1404"); + } + + /** + * @notice Pins the reason the extended compliance ID is computed from {ITokenBindingExtended}. + * @dev `IERC3643ComplianceExtended` declares no function of its own, so `type(...).interfaceId` + * is `0x00000000` — a trap for an integrator who uses it instead of the constant. The extended + * surface is declared in full by `ITokenBindingExtended`, whose own ID is therefore already the + * flattened one. + */ + function testMarkerInterfaceHasZeroNaiveIdAndIsNotUsedAsSuch() public pure { + assertEq(type(IERC3643ComplianceExtended).interfaceId, bytes4(0x00000000), "marker declares nothing"); + assertEq( + ComplianceInterfaceId.ERC3643_COMPLIANCE_EXTENDED_INTERFACE_ID, + type(ITokenBindingExtended).interfaceId, + "extended ID comes from ITokenBindingExtended" + ); + } } diff --git a/test/RuleEngineOwnable/ERC3643Compliance.t.sol b/test/RuleEngineOwnable/ERC3643Compliance.t.sol index d252689..fbc0355 100644 --- a/test/RuleEngineOwnable/ERC3643Compliance.t.sol +++ b/test/RuleEngineOwnable/ERC3643Compliance.t.sol @@ -7,9 +7,8 @@ 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"; -import { - ERC3643ComplianceModuleInvariantStorage -} from "../../src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol"; +import {ITokenBinding} from "../../src/interfaces/ITokenBinding.sol"; +import {TokenBindingModuleInvariantStorage} from "../../src/modules/library/TokenBindingModuleInvariantStorage.sol"; // Minimal mock ERC-3643 token to simulate calls to RuleEngine contract ERC3643MockTokenOwnable { @@ -55,15 +54,15 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { vm.startPrank(OWNER_ADDRESS); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenBound(address(token1)); + emit ITokenBinding.TokenBound(address(token1)); ruleEngineMock.bindToken(address(token1)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenBound(address(token2)); + emit ITokenBinding.TokenBound(address(token2)); ruleEngineMock.bindToken(address(token2)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenBound(address(token3)); + emit ITokenBinding.TokenBound(address(token3)); ruleEngineMock.bindToken(address(token3)); vm.stopPrank(); @@ -95,15 +94,15 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { // Expect events for each unbind vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenUnbound(address(token2)); + emit ITokenBinding.TokenUnbound(address(token2)); ruleEngineMock.unbindToken(address(token2)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenUnbound(address(token1)); + emit ITokenBinding.TokenUnbound(address(token1)); ruleEngineMock.unbindToken(address(token1)); vm.expectEmit(true, false, false, true); - emit IERC3643Compliance.TokenUnbound(address(token3)); + emit ITokenBinding.TokenUnbound(address(token3)); ruleEngineMock.unbindToken(address(token3)); vm.stopPrank(); @@ -137,15 +136,13 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { } function testCannotBoundIfInvalidAddress() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.bindToken(address(ZERO_ADDRESS)); } function testCannotUnBoundIfTokenIsNotBound() public { - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenNotBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenNotBound.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.unbindToken(address(0x100)); } @@ -156,7 +153,7 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { ruleEngineMock.bindToken(address(0x1)); // Assert - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenAlreadyBound.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.bindToken(address(0x1)); } @@ -221,9 +218,7 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { } function testCannotSetTokenSelfBindingApprovalForZeroAddress() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.setTokenSelfBindingApproval(address(0), true); } @@ -268,9 +263,7 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { tokens[0] = address(token1); tokens[1] = address(0); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true); } @@ -327,9 +320,7 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { tokens[0] = address(token1); tokens[1] = address(0); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.bindTokens(tokens); } @@ -342,7 +333,7 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { vm.prank(OWNER_ADDRESS); ruleEngineMock.bindToken(address(token1)); - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenAlreadyBound.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.bindTokens(tokens); } @@ -355,29 +346,23 @@ contract RuleEngineOwnableERC3643Test is Test, HelperContractOwnable { vm.prank(OWNER_ADDRESS); ruleEngineMock.bindToken(address(token1)); - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenNotBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenNotBound.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.unbindTokens(tokens); } function testCannotCreatedIfNotBound() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngineMock.created(user1, 100); } function testCannotDestroyedIfNotBound() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngineMock.destroyed(user2, 50); } function testCannotTransferredIfNotBound() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_UnauthorizedCaller.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_UnauthorizedCaller.selector); ruleEngineMock.transferred(user1, user2, 200); } } diff --git a/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol b/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol index bab72d8..9f8dd69 100644 --- a/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol +++ b/test/RuleEngineOwnable2Step/RuleEngineOwnable2Step.t.sol @@ -18,7 +18,7 @@ import {ComplianceInterfaceId} from "src/modules/library/ComplianceInterfaceId.s import {ERC1404InterfaceId} from "src/modules/library/ERC1404InterfaceId.sol"; import {OwnableInterfaceId} from "src/modules/library/OwnableInterfaceId.sol"; import {Ownable2StepInterfaceId} from "src/modules/library/Ownable2StepInterfaceId.sol"; -import {ERC3643ComplianceModuleInvariantStorage} from "src/modules/library/ERC3643ComplianceModuleInvariantStorage.sol"; +import {TokenBindingModuleInvariantStorage} from "src/modules/library/TokenBindingModuleInvariantStorage.sol"; import {RulesManagementModuleInvariantStorage} from "src/modules/library/RulesManagementModuleInvariantStorage.sol"; import {RuleEngineOwnable2StepExposed} from "src/mocks/RuleEngineExposed.sol"; // forge-lint: disable-next-line(unaliased-plain-import) @@ -225,9 +225,7 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step { } function testCannotSetTokenSelfBindingApprovalForZeroAddress() public { - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.setTokenSelfBindingApproval(address(0), true); } @@ -272,9 +270,7 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step { tokens[0] = TOKEN_1; tokens[1] = address(0); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.setTokenSelfBindingApprovalBatch(tokens, true); } @@ -331,9 +327,7 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step { tokens[0] = TOKEN_1; tokens[1] = address(0); - vm.expectRevert( - ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_InvalidTokenAddress.selector - ); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_InvalidTokenAddress.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.bindTokens(tokens); } @@ -346,7 +340,7 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step { vm.prank(OWNER_ADDRESS); ruleEngineMock.bindToken(TOKEN_1); - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenAlreadyBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenAlreadyBound.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.bindTokens(tokens); } @@ -359,7 +353,7 @@ contract RuleEngineOwnable2StepTest is Test, HelperContractOwnable2Step { vm.prank(OWNER_ADDRESS); ruleEngineMock.bindToken(TOKEN_1); - vm.expectRevert(ERC3643ComplianceModuleInvariantStorage.RuleEngine_ERC3643Compliance_TokenNotBound.selector); + vm.expectRevert(TokenBindingModuleInvariantStorage.TokenBinding_TokenNotBound.selector); vm.prank(OWNER_ADDRESS); ruleEngineMock.unbindTokens(tokens); } diff --git a/test/TokenBinding/TokenBindingStandalone.t.sol b/test/TokenBinding/TokenBindingStandalone.t.sol new file mode 100644 index 0000000..edb2733 --- /dev/null +++ b/test/TokenBinding/TokenBindingStandalone.t.sol @@ -0,0 +1,108 @@ +//SPDX-License-Identifier: MPL-2.0 +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ITokenBinding} from "src/interfaces/ITokenBinding.sol"; +import {TokenBindingModuleInvariantStorage} from "src/modules/library/TokenBindingModuleInvariantStorage.sol"; +import {TokenBindingStandaloneMock} from "src/mocks/TokenBindingStandaloneMock.sol"; + +/** + * @title TokenBindingStandaloneTest + * @notice Tests {TokenBindingModule} used outside the RuleEngine, through + * {TokenBindingStandaloneMock}, to pin that the binding registry works without any ERC-3643 code. + */ +contract TokenBindingStandaloneTest is Test, TokenBindingModuleInvariantStorage { + TokenBindingStandaloneMock public engine; + + address constant OWNER_ADDRESS = address(1); + address constant ATTACKER = address(4); + address constant ADDRESS1 = address(5); + address constant ADDRESS2 = address(6); + + function setUp() public { + engine = new TokenBindingStandaloneMock(OWNER_ADDRESS); + } + + function testCanBindToken() public { + vm.expectEmit(true, true, true, true); + emit ITokenBinding.TokenBound(ADDRESS1); + vm.prank(OWNER_ADDRESS); + engine.bindToken(ADDRESS1); + + assertTrue(engine.isTokenBound(ADDRESS1)); + assertFalse(engine.isTokenBound(ADDRESS2)); + } + + function testCanUnbindToken() public { + vm.prank(OWNER_ADDRESS); + engine.bindToken(ADDRESS1); + + vm.expectEmit(true, true, true, true); + emit ITokenBinding.TokenUnbound(ADDRESS1); + vm.prank(OWNER_ADDRESS); + engine.unbindToken(ADDRESS1); + + assertFalse(engine.isTokenBound(ADDRESS1)); + } + + function testCannotBindTokenIfNotManager() public { + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, ATTACKER)); + vm.prank(ATTACKER); + engine.bindToken(ADDRESS1); + } + + function testCannotUnbindTokenIfNotManager() public { + vm.prank(OWNER_ADDRESS); + engine.bindToken(ADDRESS1); + + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, ATTACKER)); + vm.prank(ATTACKER); + engine.unbindToken(ADDRESS1); + } + + function testCannotSelfBindWithoutTheExtendedModule() public { + // The core module authorizes bind/unbind through the manager check only, + // so a token cannot bind itself as it can on the RuleEngine. + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, ADDRESS1)); + vm.prank(ADDRESS1); + engine.bindToken(ADDRESS1); + } + + function testCannotBindZeroAddress() public { + vm.expectRevert(TokenBinding_InvalidTokenAddress.selector); + vm.prank(OWNER_ADDRESS); + engine.bindToken(address(0)); + } + + function testCannotBindTokenAlreadyBound() public { + vm.prank(OWNER_ADDRESS); + engine.bindToken(ADDRESS1); + + vm.expectRevert(TokenBinding_TokenAlreadyBound.selector); + vm.prank(OWNER_ADDRESS); + engine.bindToken(ADDRESS1); + } + + function testCannotUnbindTokenNotBound() public { + vm.expectRevert(TokenBinding_TokenNotBound.selector); + vm.prank(OWNER_ADDRESS); + engine.unbindToken(ADDRESS1); + } + + function testCanCallBoundTokenEntryPointIfBound() public { + vm.prank(OWNER_ADDRESS); + engine.bindToken(ADDRESS1); + + vm.prank(ADDRESS1); + engine.notify(); + + assertEq(engine.callCount(), 1); + } + + function testCannotCallBoundTokenEntryPointIfNotBound() public { + vm.expectRevert(TokenBinding_UnauthorizedCaller.selector); + vm.prank(ADDRESS1); + engine.notify(); + } +}